以前同じような質問をしましたが、ご指摘をいただいたので
出直してきました。
VB.NetでFTPのファイル検索処理を行いたいのですができません。
おそらく原因は”マーシャリング”だと思います。
マーシャリングの処理はどうやればいいのでしょうか?
(記述方法等まったくわかりません)
ヒントになるようなことでもかまいません。
ご存知の方がいらっしゃいましたら、教えてください。
以下のコードは、FTPサーバに接続して、
ファイルの一覧を取得しようというものの一部です。
(必要と思われるコードは書いたつもりです。)
どうかよろしくお願いします。
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System
Imports System.IO
Imports System.Text
Imports System.Net
Imports System.Net.Sockets
インターネット接続用API
Public Declare Function InternetOpen Lib "winInet.DLL" Alias "InternetOpenA" ( _
ByVal lpszAgent As String, _
ByVal dwAccessType As Integer, _
ByVal lpszProxyName As String, _
ByVal lpszProxyBypass As String, _
ByVal dwFlags As Integer) As Integer
FTPセッションの取得用API
Public Declare Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" ( _
ByVal hInternetSession As Long, _
ByVal sServerName As String, _
ByVal nServerPort As Integer, _
ByVal sUsername As String, _
ByVal sPassword As String, _
ByVal lService As Long, _
ByVal lFlags As Long, _
ByVal lContext As Long) As Long
'ファイル検索用API
Public Declare Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" _
(ByVal hFtpSession As Long, _
ByVal lpszSearchFile As String, _
ByVal lpFindFileData As WIN32_FIND_DATA, _
ByVal dwFlags As Long, _
ByVal dwContent As Integer) As Long
ファイルデータ取得用の構造体
Public Structure WIN32_FIND_DATA
Dim dwFileAttributes As Long
Dim ftCreationTime As FILETIME
Dim ftLastAccessTime As FILETIME
Dim ftLastWriteTime As FILETIME
Dim nFileSizeHigh As Long
Dim nFileSizeLow As Long
Dim dwReserved0 As Long
Dim dwReserved1 As Long
<MarshalAs(UnmanagedType.IUnknown, SizeConst:=MAX_PATH - 1)> Dim cFileName() As Byte
<VBFixedArray((14 - 1))> Dim cAlternate() As Byte
Public Sub Initialize()
ReDim cFileName((MAX_PATH - 1))
ReDim cAlternate((14 - 1))
End Sub
End Structure
-----
InternetOpenでインターネットに接続。
※ハンドルの取得OK
-----
FTPセッションの格納
LngFTPSession=InternetConnect (InternetOpen,省略)
※ここもOK
-----
Dim GtFindData as WIN32_FIND_DATA
GtFindData.Initialize()
最初のファイル名の取得
※↓ここでエラーが発生します。↓
AAA= FtpFindFirstFile(LngFTPSession, vbNullString, GtFindData, INTERNET_FLAG_RELOAD, 0)
エラー内容
”'System.TypeLoadException' のハンドルされていない例外が XYZ.exe で発生しました。
追加情報 : 型 WIN32_FIND_DATA のフィールド cFileNameをマーシャリングできません : この型は構造体フィールドとして、マーシャリングできません。”
# 質問を見落としていた……。
> Public Declare Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" ( _
パラメータの多くが、Int64型(=Long型)で宣言されてしまっています。
きちんと、Int32型(=Integer型)に修正してください。
> Public Declare Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" _
第3引数をByRefにしておかないと、WIN32_FIND_DATAを受け取れませんよ。
それと、こちらもInt64型(=Long型)が使われていますので、適宜、Int32型(=Integer型)に。
> 'ファイルデータ取得用の構造体
> Public Structure WIN32_FIND_DATA
「FtpFindFirstFileA」に関するヘッダファイル (WinInet.h)を見ると、
#pragma pack(push, wininet, 4)
という宣言がありますので、WIN32_FIND_DATA の Structure 自体に、
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, Pack:=4)>
といった属性を与えておいてください。(StructLayoutAttribute.Pack が
指定されていなかった場合は、8バイトパッキングと見なされてしまいます)
> <MarshalAs(UnmanagedType.IUnknown, SizeConst:=MAX_PATH - 1)> Dim cFileName() As Byte
今回使用している関数は OLE APIでは無いので、IUnknown インターフェイスのポインタとして
マーシャリングしてはまずいと思いますよ……。それと、SizeConstフィールドの宣言も違っています。
今回の場合、cFileNameに関しては、Byte型でマーシャリングしても良いですけれど、
この場合は、Stringの方が扱いやすいのではないでしょうか。例えば、
<MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_PATH)> _
Public FileName As String
という感じですね。(当方で試した限りでは、これで正しく受信できています)
もし、文字コード変換の都合上、Stringだと正しく受信できないという事であれば、
データ型はByte型のままにして、以下のような宣言にすれば良いと思います。
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=MAX_PATH)> _
Public FileName() As Byte
ただしこの場合は、受信したファイル名を得るために Byte() → String 変換が必要になります。
具体的には、サーバ側の文字コードにあわせた System.Text.Encodingクラスを用意して、
S = System.Text.Encoding.GetEncoding("EUC-JP").GetString(GtFindData.cFileName)
S = System.Text.Encoding.GetEncoding("Shift_JIS").GetString(GtFindData.cFileName)
などのように、そのGetStringメソッドで変換する必要が生じます。
魔界の仮面弁士様本当にありがとうございます。
現在では別の方法でFTPへの接続等の処理をしていますが、
あまり良いとはいえない方法なので、APIでの処理方法がずっと気になっていました。
とても勉強になりました。ありがとうございます。
今後の参考にさせていただきます。
すみません。
解決ボタンにチェックをするのを忘れていました。
ツイート | ![]() |