お世話になっています。
最近、VB.netをはじめたばかりなのですが、FTPクライアントを作成しなければならず困っています。
目的はFTPにより指定したファイルをGet・Putすることなのですが、その前段階で詰まってしまいました。
いろいろな資料を参考にしながら下のようなサンプルを作ったのですが、接続確認のためにFTPサーバーのディレクトリ情報を取得しようとしたところ戻り値が「False」になってしまいます。
もちろん、ディレクトリ情報は取得できず、そのあとに行っているファイルの取得ではエラーは出ませんが、ローカルにファイルが取得できません。
あちこちのサンプルも参考にしながら考えたのですが、問題点を絞り込めません。
どこを直せばいいのか、ご教授願います。
乱文で、不明なところもあるかと思いますが、お願いいたします。
== 以下コード ==
bytDir = System.Text.Encoding.GetEncoding(932).GetBytes("/・・・/")
' WININETの初期化
hInternet = InternetOpen("Shigeru", _
INTERNET_OPEN_TYPE_DIRECT, vbNull, vbNull, 0)
' FTPセッションの確立 */
hFtpSession = InternetConnect(hInternet, _
"ftpserver.co.jp",INTERNET_DEFAULT_FTP_PORT, _
"userID", "userPass", INTERNET_SERVICE_FTP, 0, 0)
'FTP(サーバー)のディレクトリ変更
Call FtpSetCurrentDirectory(hFtpSession, bytDir(0))
'FTP(サーバー)の現在のディレクトリを表示
MsgBox(FtpGetCurrentDirectory(hFtpSession, strDir, 1024))
MsgBox(strDir)
' リモートにあるファイルをローカルへ転送
MsgBox(FtpGetFile(hFtpSession, "index.htm",_
"C:\Ftp_Samp\getfile.htm", _
False, FILE_ATTRIBUTE_NORMAL, _
INTERNET_FLAG_RELOAD, 0) )
' 後処理
InternetCloseHandle(hFtpSession)
InternetCloseHandle(hInternet)
どこまでが上手くいって、どこから駄目なんでしょうか?
まずhInternetは出来ていますか?
hFtpSession のハンドルは?
Call FtpSetCurrentDirectory(hFtpSession, bytDir(0))
は
r=FtpSetCurrentDirectory(hFtpSession,bytDir(0))
とやってTrueが返るかどうか見たらいかがですか。
ねろ様、御回答ありがとうございます。
hInternet = 13369348
hFtpSession = 7135853227592908800
が返ってきています。
FtpSetCurrentDirectoryの戻り値はFalseで、セットできていないみたいなんですが、
hFtpSessionに値が返ってきているということはセッションは確立できている、と判断
できると思っているのですが、この辺で間違っているのでしょうか?
ちなみに、FtpSetCurrentDirectoryの定義なんですが、
Private Declare Function FtpSetCurrentDirectory Lib "wininet.dll" Alias "FtpSetCurrentDirectoryA" _
(ByVal hFtpSession As Long, ByRef lpszDirectory As Byte) As Boolean
であっているのでしょうか?
凡例を探していると、第2引数がString型だったり戻り値がLong型だったりとする例も見つかるのですが。
実際、いろいろな形式を試してみても、すべてFalseが返ってきてしまいます。
(Long型にすると5330037234466816が返ってきます)
よろしくお願いいたします。
オリジナルは
FtpSetCurrentDirectory
BOOL FtpSetCurrentDirectory(
IN HINTERNET hFtpSession,
IN LPCSTR lpszDirectory
);
lpszDirectory
Address of a null-terminated string that contains the name of the directory
となっていますね。
VBの場合はByValで
Public Declare Function FtpSetCurrentDirectory Lib "wininet.dll" Alias _
"FtpSetCurrentDirectoryA" (ByVal hFtpSession As Long, _
ByVal lpszDirectory As String) As Boolean
この宣言で良いのでは
r = FtpSetCurrentDirectory(hFtpSession, "/")
こうしたらいかがですか。
教えていただいた内容で宣言を見直し、実行してみましたが、結果が変わりません。
r = FtpSetCurrentDirectory(hFtpSession, "/")
の結果もFalseが返ってきます。
ほかの端末なのですが、VB6で作成されたFTPクライアントを実行し、ハンドルの値を確認してみたところ、
hInternet = 13369348
hFtpSession = 13369353
でした。VB.netの方は
hInternet = 13369348
hFtpSession = 7135853227592908800
となるので、hFtpSession が正常に接続できていないのでしょうか?
VB6の流れと見比べても違いがわかりません。
InternetConnectの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
>hFtpSession が正常に接続できていないのでしょうか?
セッションのハンドルが返ってきているので、接続は出来ていると
思いますが。接続出来ないとNullが返ります。
FtpGetCurrentDirectoryこちらの方も同じくFalseが返りますか?
たびたびありがとうございます。
確認してみましたが、リモートのディレクトリ取得もFalseが返ってきます。
セッションが確立できていることは戻り値でしか確認できないのでしょうか?
リモートの情報が何か取得できればそこまでは問題ないことが確認できるのですが・・・
リモートのディレクトリ情報を取得している部分です。
何が問題なのやら・・・
'API宣言部分
Private Declare Function FtpGetCurrentDirectory Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" ( _
ByVal hFtpSession As Long, _
ByVal lpszDirectory As String, _
ByRef lpdwCurrentDirectory As Long) As Boolean
'取得部分(strDirは文字型変数)
r = FtpGetCurrentDirectory(hFtpSession, strDir, 1024)
>セッションが確立できていることは戻り値でしか確認できないのでしょうか?
通常セッションが確立できない場合は err.LastDllErrorで原因が特定できます。
この場合はFalseが返ってきていませんので、Falseが返ってくる
FtpSetCurrentDirectoryの後でerr.LastDllErrorをやってみたらどうでしょう。
>Private Declare Function FtpGetCurrentDirectory Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" ( _
> ByVal hFtpSession As Long, _
> ByVal lpszDirectory As String, _
> ByRef lpdwCurrentDirectory As Long) As Boolean
>r = FtpGetCurrentDirectory(hFtpSession, strDir, 1024)
strDirの宣言はどのようになっているでしょうか、Vb.Netは固定長のStringは
使えないと思いますが。
FtpSetCurrentDirectoryの後でerr.LastDllErrorの値を確認してみたところ、
6が得られましたが、それがどういうことなのか・・・?
strDirの宣言は
Dim strDir As String
で行っています。第三パラメータの「1024」はバッファーサイズが必要とのことだったのでサンプルを参考に書いてみました。
書き方が悪いのでしょうか?
同一端末で、フリーのDLLを用いて同一の動作を行ってみたところ、問題なく動作しましたので、やはりプログラムに問題があると思うのですが・・・
できたら、外部のDLLは用いずに完成させたいので、よろしくお願いいたします。
> InternetConnectのAPI宣言は以下のようにしています。
VB.NETなのですよね。As Long / As Int64 が使われる事は、あまり無いかと。
この場合は、As Integer / As Int32 でしょう。
> err.LastDllErrorの値を確認してみたところ、
> 6が得られましたが、それがどういうことなのか・・・?
6は、『ハンドルが無効です。』という意味です。
魔界の仮面弁士 様、ご回答ありがとうございます。
> 6は、『ハンドルが無効です。』という意味です。
ということは、やはりFTP接続時のハンドルの取得が出来ていない、ということですね。
ディレクトリの取得とか以前にその辺の精査が必要なようですね。
いろいろ試してみます。
>この場合は、As Integer / As Int32 でしょう。
Longで宣言したのでハンドルに32ビット分のごみが付いたのかしら。(^^;
昔作った動作する実績のあるコードを載せます。
早く出せ!と怒られるかも知れませんが、実は相性の問題で
現在使用しているシステムは.Netの環境を抜いてあります。
昔のハードディスクを引っ張り出し現在の物とchangeしました。
リムーバブルになってます(^^ 関係有りませんが。
これがめんどくさかったので今までVB6のコードを見てレスしてました。
Imports System.Text
Imports System.Runtime.InteropServices
Public Class Form1
Inherits System.Windows.Forms.Form
Private Const INTERNET_OPEN_TYPE_DIRECT As Integer = 1
Private Const INTERNET_DEFAULT_FTP_PORT As Integer = 21
Private Const INTERNET_SERVICE_FTP As Integer = 1
Private Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" _
(ByVal lpszAgent As String, ByVal dwAccessType As Integer, ByVal lpszProxy As String, _
ByVal lpszProxyBypass As String, ByVal dwFlags As Integer) As Integer
Private Declare Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" _
(ByVal hInternet As Integer, ByVal lpszServerName As String, ByVal nServerPort As Integer, _
ByVal lpszUserName As String, ByVal lpszPassword As String, ByVal dwService As Int32, _
ByVal dwFlags As Integer, ByVal dwContext As Integer) As Integer
Private Declare Function InternetCloseHandle Lib "wininet.dll" _
(ByVal hInternet As Integer) As Boolean
Private Declare Function FtpSetCurrentDirectory Lib "wininet.dll" Alias "FtpSetCurrentDirectoryA" _
(ByVal hConnect As Integer, ByVal lpszDirectory As String) As Boolean
Private Declare Function FtpGetCurrentDirectory Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" _
(ByVal hConnect As Integer, ByVal lpszCurrentDirectory As StringBuilder, _
ByRef lpszCurrentDirectory As Integer) As Boolean
Private Const MAX_PATH As Integer = 1024
Private hInternet As Integer
Private hFtp As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim stDir As String
Dim stTemp As New StringBuilder
Dim nLen As Integer
Dim r As Boolean
stTemp.Capacity = MAX_PATH
nLen = MAX_PATH
hInternet = InternetOpen(vbNullString, INTERNET_OPEN_TYPE_DIRECT, vbNullString, vbNullString, 0)
hFtp = InternetConnect(hInternet, "ftpurl*****", _
INTERNET_DEFAULT_FTP_PORT, "name******", "Pass********", _
INTERNET_SERVICE_FTP, 0, 0)
stDir = TextBox1.Text
r = FtpSetCurrentDirectory(hFtp, stDir)
If FtpGetCurrentDirectory(hFtp, stTemp, nLen) Then
TextBox2.Text = stTemp.ToString()
End If
r = InternetCloseHandle(hFtp)
End Sub
End Class
それと
err.LastDllErrorで得た番号はInternetGetLastResponseInfoで何のエラーかわかります。
魔界の仮面弁士様、宣言に関する指摘、ありがとうございました!
ねろ様、ソースをありがとうございます!
おかげさまで、接続->ディレクトリ移動->ディレクトリの取得->ファイルの取得まで、行うことができました。
そこで、また問題が発生してしまいました。
ファイルの取得コマンドであるFtpGetFileの取得ファイル名にワイルドカード、書き出しファイル名にディレクトリ指定ができれば、ここまでで思っているものはできるのですが、試してみた限り、双方ともファイル名を指定してやらなければ動かないのですね?
FTPサーバーにあるファイル名が一部流動的に変化する(時間等を含んでいる)のでFTPサーバーのファイル名を取得してやらなければファイルを取得することができません。
そこで、"20040101122334.dat"というファイル名を取得しようとプログラムをを作ってみたのですが、取得される結果が".dat"となってしまいます。
'宣言部分:
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, Pack:=4)> 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.ByValTStr, SizeConst:=MAX_PATH)> Public FileName As String
<VBFixedArray(14 - 1)> Dim cAlternate() As Byte
Public Sub Initialize()
FileName = ""
'ReDim FileName(MAX_PATH - 1)
ReDim cAlternate(14 - 1)
End Sub
End Structure
Public Declare Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" _
(ByVal hFtpSession As Integer, ByVal lpszSearchFile As String, _
ByRef lpFindFileData As WIN32_FIND_DATA, ByVal dwFlags As Integer, ByVal dwContent As Integer) As Integer
'実行部分:
Dim i As Integer
i = FtpFindFirstFile(hFtpSession, "*.dat", GtFindData, INTERNET_FLAG_RELOAD, 0)
MsgBox(GtFindData.FileName)'ファイルネームが表示されるはず・・・
iは正常に取得されているようです。
いろいろ調べてみて、FileNameを配列型にして宣言部を以下のようにもしてみました。
(前略)
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=MAX_PATH)> Public FileName() As String
<VBFixedArray(14 - 1)> Dim cAlternate() As Byte
Public Sub Initialize()
'FileName = ""
ReDim FileName(MAX_PATH - 1)
ReDim cAlternate(14 - 1)
End Sub
End Structure
このようにした場合、情報取得時に
”'System.TypeLoadException' のハンドルされていない例外が ******.exe で発生しました。
追加情報 : 型 WIN32_FIND_DATA のフィールド FileName をマーシャリングできません : この型は構造体フィールドとして、マーシャリングできません。”
というエラーが発生します。
これを何とかしないとファイルの取得ができそうにないのですが、試行錯誤してみても進展がありません。
たびたびすみませんが、よろしくお願いいたします。
> FTPサーバーのファイル名を取得してやらなければ
FtpFindFirstFile などの API は、サーバーによっては望むべき結果とはならない可能性があります。
http://madia.world.coocan.jp/cgi-bin/VBBBS2/wwwlng.cgi?print+200409/04090078.txt
> FileName をマーシャリングできません
修正案。
<System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=MAX_PATH), Microsoft.VisualBasic.VBFixedString(MAX_PATH)> Public cFileName As String
マーシャリングですね。(^^;
とりあえず
> Public Declare Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" _
> (ByVal hFtpSession As Integer, ByVal lpszSearchFile As String, _
> ByRef lpFindFileData As WIN32_FIND_DATA, ByVal dwFlags As Integer, ByVal dwContent As Integer) As Integer
もともと lpFindFileDataはポインター渡しですからByRefでは無くByValでは。
基本的には FtpFindFirstFile はFindFirstFileと同じですから
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpguide/html/cpconstructssample.asp
をそのまま殆ど同じでいけるのでは。
> もともと lpFindFileDataはポインター渡しですからByRefでは無くByValでは。
ん? ポインタなら、ByRef(参照渡し)でOKですよ。
ByValだと、値渡しになってしまいます。
そもそも、構造体の値渡しが必要とされる関数は、さほど多くありません。
# (Child)WindowFromPoint ぐらいかな。
WIN32_FIND_DATA型を、StructではなくClassとして宣言しているのであれば、
Declare側を ByVal として定義する必要があるかと思いますが、今回は
Struct(値型)で宣言してあるので、ByRef で OK でしょう。
>ん? ポインタなら、ByRef(参照渡し)でOKですよ。
>ByValだと、値渡しになってしまいます。
間違でした。m(_ _)m
Option Strict Off
Option Explicit On
Imports System.Runtime.InteropServices
Module Module1
Public Const MAX_PATH As Short = 260
Structure FILETIME
Dim dwLowDateTime As Integer
Dim dwHighDateTime As Integer
End Structure
Structure WIN32_FIND_DATA
Dim dwFileAttributes As Integer
Dim ftCreationTime As FILETIME
Dim ftLastAccessTime As FILETIME
Dim ftLastWriteTime As FILETIME
Dim nFileSizeHigh As Integer
Dim nFileSizeLow As Integer
Dim dwReserved0 As Integer
Dim dwReserved1 As Integer
<VBFixedString(MAX_PATH), System.Runtime.InteropServices.MarshalAs _
(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=MAX_PATH)> _
Public cFileName As String
<VBFixedString(14), System.Runtime.InteropServices.MarshalAs _
(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=14)> _
Public cAlternate As String
End Structure
Public Const INTERNET_OPEN_TYPE_DIRECT As Short = 1
Public Const INTERNET_INVALID_PORT_NUMBER As Short = 0
Public Const INTERNET_SERVICE_FTP As Short = 1
Public Declare Function InternetConnect Lib "wininet.dll" Alias _
"InternetConnectA" (ByVal hInternetSession As Integer, ByVal _
sServerName As String, ByVal nServerPort As Short, ByVal _
sUsername As String, ByVal sPassword As String, ByVal _
lService As Integer, ByVal lFlags As Integer, ByVal lContext As Integer) As Integer
Public Declare Function InternetFindNextFile Lib "wininet.dll" _
Alias "InternetFindNextFileA" (ByVal hFind As Integer, ByRef _
lpvFindData As WIN32_FIND_DATA) As Integer
Public Declare Function FtpFindFirstFile Lib "wininet.dll" _
Alias "FtpFindFirstFileA" (ByVal hFtpSession As Integer, _
ByVal lpszSearchFile As String, ByRef lpFindFileData As WIN32_FIND_DATA, _
ByVal dwFlags As Integer, ByVal dwContent As Integer) As Integer
Public Declare Function FtpGetFile Lib "wininet.dll" Alias "FtpGetFileA" _
(ByVal hFtpSession As Integer, ByVal lpszRemoteFile As String, ByVal _
lpszNewFile As String, ByVal fFailIfExists As Boolean, ByVal _
dwFlagsAndAttributes As Integer, ByVal dwFlags As Integer, _
ByVal dwContext As Integer) As Boolean
Public Declare Function FtpPutFile Lib "wininet.dll" Alias "FtpPutFileA" _
(ByVal hFtpSession As Integer, ByVal lpszLocalFile As String, ByVal _
lpszRemoteFile As String, ByVal dwFlags As Integer, ByVal dwContext As Integer) As Boolean
Public Declare Function FtpSetCurrentDirectory Lib "wininet.dll" _
Alias "FtpSetCurrentDirectoryA" (ByVal hFtpSession As Integer, ByVal _
lpszDirectory As String) As Boolean
Declare Function FtpGetCurrentDirectory Lib "wininet.dll" _
Alias "FtpGetCurrentDirectoryA" (ByVal hFtpSession As Integer, ByVal _
lpszCurrentDirectory As String, ByRef lpdwCurrentDirectory As Integer) As Integer
' Initializes an application's use of the Win32 Internet functions
Public Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" _
(ByVal sAgent As String, ByVal lAccessType As Integer, ByVal sProxyName As String, _
ByVal sProxyBypass As String, ByVal lFlags As Integer) As Integer
Public Declare Function FtpCreateDirectory Lib "wininet.dll" _
Alias "FtpCreateDirectoryA" (ByVal hFtpSession As Integer, ByVal _
lpszDirectory As String) As Boolean
Public Declare Function InternetReadFile Lib "wininet.dll" _
(ByVal hFile As Integer, ByVal sBuffer As String, ByVal lNumBytesToRead As Integer, _
ByRef lNumberOfBytesRead As Integer) As Short
' Closes a single Internet handle or a subtree of Internet handles.
Public Declare Function InternetCloseHandle Lib "wininet.dll" (ByVal hInet As Integer) As Short
Public Declare Function CloseHandle Lib "Kernel32" (ByVal hObject As Integer) As Integer
End Module
Option Strict Off
Option Explicit On
Friend Class Form1
Inherits System.Windows.Forms.Form
Private Sub Command1_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles Command1.Click
Dim Pdata As WIN32_FIND_DATA
'バッファの初期化
Dim hopen As Integer
Dim hConnection As Integer
Dim hFind As Integer
Dim nFlag As Integer
Pdata.cFileName = New String(Chr(0), MAX_PATH)
hopen = InternetOpen("vb wininet", INTERNET_OPEN_TYPE_DIRECT, vbNullString, vbNullString, 0)
hConnection = InternetConnect(hopen, "XXXXX.com", INTERNET_INVALID_PORT_NUMBER, "xxxx", "pasxxxxx", INTERNET_SERVICE_FTP, nFlag, 0)
hFind = FtpFindFirstFile(hConnection, "/*.*", Pdata, 0, 0) 'ファイルが有るか調べる
Text1.Text = Pdata.cFileName
InternetCloseHandle(hConnection)
End Sub
End Class
私の環境ではこれでいけてます。
インデントがめちゃくちゃになってしまいました。
読みにくくてごめんなさい。
魔界の仮面弁士様、ねろ様、たびたびありがとうございます。
教えていただいたとおりにすることで、FTP通信を実現することができました。
ポイントはAPI宣言の型が変わっていたことと、WIN32_FIND_DATA型の構造体としての宣言の仕方ですね。
まだまだわからないところ・・・以前はちゃんと動いていたFtpGetCurrentDirectory が動かなくなったみたり・・・(^^;もありますが、何とかやってみようと思います。
ホントにいろいろとありがとうございました!