Win32/64ネットワーク開発 003 wininetを使ったftp通信

提供:yonewiki

Win32/64 ネットワーク処理開発へ戻る。

前の記事:Win32/64ネットワーク開発 002 wininetを使ったhttp通信

次の記事:Win32/64ネットワーク開発 004 winsockを使ったhttp通信

概要

 wininet.Libやwininet.hのインクルードについては前の記事の通り、同じように設定する必要があります。


 まずは、ホスト名やユーザID、パスワード、接続時に最初に表示するディレクトリ名を聞いてくるようなインタフェースを持たない固定値での接続を試してみましょう。接続して、FTPでカレントディレクトリを設定してカレントディレクトリが今何かを返してくれるとこまでのプログラムを書いてみます。以下のとおりです。


HINTERNET HINTERNETinet = InternetOpen(L"yo-net.jp ftp", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET HINTERNEThost = InternetConnect(
    HINTERNETinet,
	L"ftp.xxxx.com",  INTERNET_DEFAULT_FTP_PORT,
	L"ftp_user_name", L"ftp_user_password",
	INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, 0); 
FtpSetCurrentDirectory(HINTERNEThost, L"/");
DWORD DWORDsize = 0;
FtpGetCurrentDirectory(HINTERNEThost, NULL, &DWORDsize);
wchar_t* pwchCurrentDir = new wchar_t[DWORDsize/sizeof(wchar_t)];
FtpGetCurrentDirectory(HINTERNEThost, pwchCurrentDir, &DWORDsize);


 上記のプログラムにより、pwchCurrentDirにFTP接続したときの現在のディレクトリを取得できます。


 InternetOpen関数についての説明はコチラのリンクから確認して下さい。FTP通信をするときはホスト名やログイン名を使うので第2引数のフラグはINTERNET_OPEN_TYPE_DIRECTを指定します。


 URLを指定してhttp通信をするときは、InternetOpenUrl関数を使いましたが、FTP通信をするときはInternetConnect関数を使います。

InternetConnect

■InternetConnect(HINTERNET, LPCWSTR, INTERNET_PORT, LPCWSTR, LPCWSTR, DWORD, DWORD, DWORD_PTR)


第1引数:HINTERNET hInternet

 InternetOpen関数によって得られる、HINTERNETハンドルです。


第2引数:LPCWSTR lpszServerName

 FTPサーバへのアドレスまたはホスト名を文字列で指定します。


第3引数:INTERNET_PORT nServerPort

 wininet.hで INTERNET_DEFAULT_FTP_PORT には通常FTPで使われるポート番号21が設定されていますので、このキーワードを使うのが普通ですが、違うPORT番号でサーバ側がFTP通信をリスニング、受け入れ待ちをしている場合は、特定の番号を指定する必要があります。DWORD型の別名がINTERNET_PROTとなっています。0~65535の間の16bitの整数値です。


第4引数:LPCWSTR lpszUserName

 FTPログインをするときのID名です。


第5引数:LPCWSTR lpszPassword

 FTPログインをするときのID名に対して関連付けられたパスワード文字列です。


第6引数:DWORD dwService

 アクセスするサービスの種類です。FTP(INTERNET_SERVICE_FTP)とGOPHER(INTERNET_SERVICE_GOPHER)とHTTP(INTERNET_SERVICE_HTTP)があります。FTP通信時は通常はFTPを指定します。


第7引数:DWORD dwFlags

 INTERNET_FLAG_PASSIVE(0x0800 0x0000)を指定します。


第8引数:DWORD_PTR dwContext

  返されるハンドルと共にコールバック関数に渡されるアプリケーション定義値を指定する変数へのポインター。一瞬なんのことをいっているかわからない説明だったと思いますが、DWORD dwContext=123(関数はDWORD_PTRの形式を必要としているので、&dwContextのように設定します)のように特定の識別値を与えることで、このあと使う関数で、この値によって複数の通信を切り分けて使うのに利用する目的で準備されている仕組みです。wininetを使うときにそこまで複雑な通信をするとは思えませんが、winsockなどで通信するときも、こういった複数のハンドル値を管理するうまい仕組みが必要だという予備知識にもなるかと思います。切り分けをするためにもこういった使い方をする手法があるということくらいは頭の隅においておいたほうがいいかもしれません。この仕組みを使わないで、適当に0と設定しておいてもいいわけです。

FtpSetCurrentDirectory

■FtpSetCurrentDirectory(HINTERNET, LPCWSTR)


第1引数:HINTERNET hConnect

 InternetConnect関数で得られるHINTERNET型ハンドルです。


第2引数:LPCWSTR lpszDirectory

 接続しているFTP通信で、現在のディレクトリとして設定したい文字列を設定します。FTPルートディレクトリからの絶対パスである必要があります。


FtpGetCurrentDirectory

■FtpGetCurrentDirectory(HINTERNET, LPCWSTR)


第1引数:HINTERNET hConnect

 InternetConnect関数で得られるHINTERNET型ハンドルです。


第2引数:LPWSTR lpszCurrentDirectory

 接続しているFTP通信で、現在のディレクトリがどういう名前なのか取得したい文字列取得領域を設定します。FTPルートディレクトリからの絶対パスが取得できます。文字列の確保領域が取得できる文字列より小さいあるいはNULLである場合は、第3引数に必要な文字列バイト長が返却されます。UTF-16で受け取ろうとする場合は、文字列の長さの2倍となっています。


第3引数:LPDWORD lpdwCurrentDirectory

 取得した文字列の長さを受け取るWORD型の変数へのアドレスを指定します。


    DWORD DWORDcontext0 = 0;
    DWORD DWORDcontext1 = 1;
    DWORD DWORDcontext2 = 2;
    DWORD DWORDcontext3 = 3;

    DWORD DWORDsizeHigh = 0;
    DWORD DWORDsizeLow  = 0;
    DWORD DWORDwritesize = 0;

    FtpCreateDirectory(HINTERNEThost, L"NewFolder230101011111");
    FtpRemoveDirectory(HINTERNEThost, L"NewFolder230101011111");
    FtpPutFile(HINTERNEThost,                               //FTPのインターネットハンドル
		L"File230101011111.lzh",                            //アップロードするファイル
		L"/public_html/www.yo-net.jp/File230101011111.lzh", //アップロード先のパス付ファイル名
		FTP_TRANSFER_TYPE_BINARY, 	                        //バイナリファイルとしてダウンロード
		(DWORD_PTR)&DWORDcontext0);
    FtpRenameFile(HINTERNEThost, L"File230101011111.lzh", L"File230101011112.lzh");
    FtpRenameFile(HINTERNEThost, L"File230101011112.lzh", L"File230101011111.lzh");
    WIN32_FIND_DATA WIN32_FIND_DATAfind;
    FtpFindFirstFile(HINTERNEThost, L"File230101011111.lzh", &WIN32_FIND_DATAfind, 0, 0);
    FtpDeleteFile(HINTERNEThost, L"File230101011111.lzh");
    HINTERNET HINTERNETopen = FtpOpenFile(HINTERNEThost, L"Test_SJIS.css", GENERIC_READ, FTP_TRANSFER_TYPE_ASCII, (DWORD_PTR)&DWORDcontext1);
    DWORDsizeLow = FtpGetFileSize(HINTERNETopen, (LPDWORD)&DWORDsizeHigh);

    DWORD DWORDBufSize = 1024;
    DWORD DWORDTotal = 0;
    HGLOBAL HGLOBALMem;
    HGLOBALMem = GlobalAlloc(GHND, 1);
    char* pchOpenFileStrTotal = nullptr;
    char* pchOpenFileStr = new char[DWORDBufSize + 1];
	while(1){
		BOOL bResult = InternetReadFile(HINTERNETopen, pchOpenFileStr, DWORDBufSize, &DWORDcontext2);
        pchOpenFileStr[DWORDcontext2] = '\0';
        if (DWORDcontext4 == 0) {
			break;
		}
        DWORDTotal += DWORDcontext4;
		HGLOBALMem = GlobalReAlloc(HGLOBALMem, DWORDTotal + 1, GMEM_MOVEABLE);
		pchOpenFileStrTotal = (char*)GlobalLock(HGLOBALMem);
		strcat_s(pchOpenFileStrTotal, DWORDTotal + 1, pchOpenFileStr);
    }
	int iSize = MultiByteToWideChar(932, 0U, pchOpenFileStrTotal, -1, nullptr, 0);
	wchar_t* pwchOpenFileStr = new wchar_t[iSize];
	MultiByteToWideChar(932, 0U, pchOpenFileStrTotal, -1, pwchOpenFileStr, iSize);

	InternetCloseHandle(HINTERNEThost);
	InternetCloseHandle(HINTERNETinet);
    HINTERNETinet = InternetOpen(L"yo-net.jp ftp", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    HINTERNEThost = InternetConnect(
        HINTERNETinet,
	    L"ftp.xxxx.com",  INTERNET_DEFAULT_FTP_PORT,
	    L"ftp_user_name", L"ftp_user_password",
	    INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, 0); 
    FtpSetCurrentDirectory(HINTERNEThost, L"/public_html/www.yo-net.jp/");
    HINTERNET HINTERNETopen = FtpOpenFile(HINTERNEThost, L"Test_SJIS.css", GENERIC_READ, FTP_TRANSFER_TYPE_ASCII, (DWORD_PTR)&DWORDcontext3);

    //sjisのcssファイルをUTF-8に変換する処理。
	wchar_t* pwchutf16String = nullptr;
	int utf16Length = MultiByteToWideChar(932, 0, pchOpenFileStrTotal, -1, NULL, 0);
	if (utf16Length > 0) {
		pwchutf16String = new wchar_t[utf16Length];
		MultiByteToWideChar(932, 0, pchOpenFileStrTotal, -1, pwchutf16String, utf16Length);
	}

	char* pchutf8String = nullptr;
	int iutf8Length = WideCharToMultiByte(CP_UTF8, 0, pwchutf16String, -1, nullptr, 0, nullptr, nullptr);
	pchutf8String = new char[iutf8Length];  // NULL 終端文字を含めないサイズに修正
	std::string stringutf8String(iutf8Length, 0);
	if (iutf8Length > 0) {
		WideCharToMultiByte(CP_UTF8, 0, pwchutf16String, -1, &stringutf8String[0], iutf8Length, nullptr, nullptr);
		stringutf8String[iutf8Length - 1] = '\0';
	}

    InternetWriteFile(HINTERNETopen, stringutf8String.c_str(), stringutf8String.size() - 1, &DWORDwritesize);

    delete[] pwchutf16String;
    delete[] pchutf8String;


FtpCreateDirectory

■FtpCreateDirectory(HINTERNET, LPCWSTR)


第1引数:HINTERNET hConnect

 InternetConnect関数で得られるHINTERNET型ハンドルです。


第2引数:LPCWSTR lpszDirectory

 作成するディレクトリの名前の文字列を保持した配列変数の先頭を差す文字型のポインタです。


FtpRemoveDirectory

■FtpRemoveDirectory(HINTERNET, LPCWSTR)


第1引数:HINTERNET hConnect

 InternetConnect関数で得られるHINTERNET型ハンドルです。


第2引数:LPCWSTR lpszDirectory

 削除するディレクトリの名前の文字列を保持した配列変数の先頭を差す文字型のポインタです。


FtpPutFile

■FtpPutFile(HINTERNET, LPCWSTR, LPCWSTR, DWORD, DWORD_PTR)


第1引数:HINTERNET hConnect

 InternetConnect関数で得られるHINTERNET型ハンドルです。


第2引数:LPCWSTR lpszLocalFile

 アップロードするファイル名の文字列を保持した配列変数の先頭を差す文字型のポインタです。


第3引数:LPCWSTR lpszNewRemoteFile

 アップロードしたファイルをアップロード先でのファイル名を指定したルートディレクトリからのファイルパスを含めた文字列を保持した配列変数の先頭を差す文字型のポインタです。


第4引数:DWORD dwFlags

 アップロードするファイルの通信方式を定義するフラグです。FTP_TRANSFER_TYPE_BINARY(2)とFTP_TRANSFER_TYPE_ASCII(1)を主に指定します。FTP_TRANSFER_TYPE_BINARYの場合はファイルはそのまま送られ、FTP_TRANSFER_TYPE_ASCIIの場合は改行コードを変換する処理が入ります。CR+LFをLFだけにする変換を行います。現代においては、サーバー側に配置されるテキストがCR+LFの形式であっても問題なく表示・プログラム実行処理されるので、変換する必要はなくなってきています。ただし、変換してアップロードしたファイルがあって、取得する場合にまた変換する必要があったりはしますので、ファイルの扱い方の状況次第では必要になってきます。正しくない通信によって、改行コードがごちゃごちゃしてきている可能性があります。WindowsではCR+LFにUnix系ではLFにという変換を管理する必要があります。作るならば、そういったことを手助けするようなFTPツールであるべきですね。


第5引数:DWORD_PTR dwContext

 返されるハンドルと共にコールバック関数に渡されるアプリケーション定義値を指定する変数へのポインター。一瞬なんのことをいっているかわからない説明だったと思いますが、DWORD dwContext=123(関数はDWORD_PTRの形式を必要としているので、&dwContextのように設定します)のように特定の識別値を与えることで、このあと使う関数で、この値によって複数の通信を切り分けて使うのに利用する目的で準備されている仕組みです。wininetを使うときにそこまで複雑な通信をするとは思えませんが、winsockなどで通信するときも、こういった複数のハンドル値を管理するうまい仕組みが必要だという予備知識にもなるかと思います。切り分けをするためにもこういった使い方をする手法があるということくらいは頭の隅においておいたほうがいいかもしれません。この仕組みを使わないで、適当に0と設定しておいてもいいわけです。毎回同じことを書いてますので、以降では説明は省略しましょうかね。


FtpRenameFile

■FtpRenameFile(HINTERNET, LPCWSTR, LPCWSTR)


第1引数:HINTERNET hConnect

 InternetConnect関数で得られるHINTERNET型ハンドルです。


第2引数:LPCWSTR lpszExisting

 変更するファイルの元の名前の文字列を保持した配列変数の先頭を差す文字型のポインタです。


第3引数:LPCWSTR lpszNew

 変更するファイルの変更後の名前の文字列を保持した配列変数の先頭を差す文字型のポインタです。


FtpFindFirstFile

■FtpFindFirstFile(HINTERNET, LPCWSTR, LPWIN32_FIND_DATAW, DWORD, DWORD_PTR)


第1引数:HINTERNET hConnect

 InternetConnect関数で得られるHINTERNET型ハンドルです。


第2引数:LPCWSTR lpszSearchFile

 検索するファイル名の文字列を保持した配列変数の先頭を差す文字型のポインタです。


第3引数:LPWIN32_FIND_DATAW lpFindFileData

 検索結果を返すLPWIN32_FIND_DATAW型構造体の変数。参照型として渡します。


第4引数:DWORD dwFlags

 INTERNET_FLAG_HYPERLINK, INTERNET_FLAG_NEED_FILE, INTERNET_FLAG_NO_CACHE_WRITE, INTERNET_FLAG_RELOAD, INTERNET_FLAG_RESYNCHRONIZEといった検索処理についてのフラグを設定します。


  • INTERNET_FLAG_HYPERLINK
 ネットワークからアイテムを再読み込みするかどうかを決定するときに、有効期限時間がなく、サーバーから LastModified 時刻が返されない場合は、強制的に再読み込みを行う。


  • INTERNET_FLAG_NEED_FILE
 ファイルをキャッシュできない場合は、一時ファイルを作成します。


  • INTERNET_FLAG_NO_CACHE_WRITE
 返されたエンティティをキャッシュに追加しません。


  • INTERNET_FLAG_RELOAD
 要求されたファイル、オブジェクト、またはディレクトリ リストをキャッシュからではなく元のサーバーからダウンロードします。


  • INTERNET_FLAG_RESYNCHRONIZE
 最後にダウンロードされてからリソースが変更された場合は、HTTP リソースを再読み込みします。 すべての FTP リソースが再読み込みされます。


第5引数:DWORD_PTR dwContext

 返されるハンドルと共にコールバック関数に渡されるアプリケーション定義値を指定する変数へのポインター。


FtpDeleteFile

■FtpDeleteFile(HINTERNET, LPCWSTR)


第1引数:HINTERNET hConnect

 InternetConnect関数で得られるHINTERNET型ハンドルです。


第2引数:LPCWSTR lpszFileName

 削除するファイルの名前の文字列を保持した配列変数の先頭を差す文字型のポインタです。


FtpOpenFile

■FtpOpenFile(HINTERNET, LPCWSTR, DWORD, DWORD, DWORD_PTR)


 オープンした状態でFtpGetFileSizeとInternetReadFileとInternetWriteFile関数が使えます。戻り値にはHINTERNET型のインスタンスハンドルを取得できます。


第1引数:HINTERNET hConnect

 InternetConnect関数で得られるHINTERNET型ハンドルです。


第2引数:LPCWSTR lpszFileName

 ファイルオープンするファイルのファイル名を保持した配列変数の先頭を差す文字型のポインタです。


第3引数:DWORD dwAccess

 ファイルアクセスについての指定。このパラメーターはGENERIC_READまたはGENERIC_WRITEを指定できます。両方を指定することはできません。


第4引数:DWORD dwFlags

 ファイルの通信方式を定義するフラグです。FTP_TRANSFER_TYPE_BINARY(2)とFTP_TRANSFER_TYPE_ASCII(1)を主に指定します。


第5引数:DWORD_PTR dwContext

 返されるハンドルと共にコールバック関数に渡されるアプリケーション定義値を指定する変数へのポインター。


FtpGetFileSize

■FtpGetFileSize(HINTERNET, LPCWSTR)


第1引数:HINTERNET hConnect

 FtpOpenFile関数で得られるHINTERNET型ハンドルです。


第2引数:LPDWORD lpdwFileSizeHigh

 要求されたFTPリソースのファイルサイズの上位符号なしlong整数へのポインター。参照で渡します。ファイルサイズが4GByteを超えるような場合だけ第2引数で0以外の値を得ることができます。


InternetWriteFile

■InternetWriteFile(HINTERNET, LPCVOID, DWORD, LPDWORD)


第1引数:HINTERNET hConnect

 FtpOpenFile関数で得られるHINTERNET型ハンドルです。


第2引数:LPCVOID lpBuffer

 ファイルに書き込まれる、文字列の先頭アドレスです。


第3引数:DWORD dwNumberOfBytesToWrite

 ファイルに書き込まれるバイト数。


第4引数:LPDWORD lpdwNumberOfBytesWritten

 ファイルに書き込まれたバイト数を受け取る変数です。参照で渡します。


 FTPコマンドは沢山ありますので、それも実行できないと都合が悪いわけです。そのための関数として以下のようなものもあります。上記のプログラムの続きとしてコードを記述しています。


    DWORD DWORDcontext4 = 4;
    HINTERNET HINTERNETftp;
    char buffer[1025];
    DWORD bytesRead = 0;
    FtpCommand(HINTERNEThost, TRUE, FTP_TRANSFER_TYPE_ASCII, L"LIST", DWORDcontext4, &HINTERNETftp);
    InternetReadFile(HINTERNETftp, buffer, sizeof(buffer), &bytesRead);


 例えば"LIST"というコマンドを実効するときは上記のようにします。HINTERNET型の変数の参照を第6変数にとるFtpCommand関数で 実行した結果を得るには第6引数のHINTERNET型変数に受け取っているので、これをInternetReadFile関数の第1引数にすることで文字列として応答結果を得ることができます。


 ただし、管理人が使っている環境ではxreaのftpサーバからはwininetライブラリではコマンド完了の応答が得られず、FtpCommand関数を使うたびに一度、接続を切って、またカレントディレクトリの調整をして再開しなければならないような状態になりました。ftpサーバの仕様によっては、FtpCommand関数が使えないということなので、注意が必要かもしれません。結局は自分でWinsockレベルで通信をするプログラムを作らないとダメなのかもしれません。wininetライブラリは簡易的なプログラムの範囲で、関係者内でしか使うことはできないかもしれません。広く配布しておいて使えない人がいるという状態になるのは避けたいものです。


FtpCommand

■FtpCommand(HINTERNET, BOOL, LPCWSTR, DWORD_PTR, HINTERNET)


第1引数:HINTERNET hConnect

 FtpOpenFile関数で得られるHINTERNET型ハンドルです。


第2引数:BOOL fExpectResponse

 データ接続が必要なコマンドである場合はTRUE。


第3引数:DWORD dwFlags

 ファイルの通信方式を定義するフラグです。FTP_TRANSFER_TYPE_BINARY(2)とFTP_TRANSFER_TYPE_ASCII(1)を主に指定します。


第4引数:LPCWSTR lpszCommand

 コマンドの文字列を指定します。大体ASCIIコードの範囲の文字列であるはずです。それでもワイド文字列版の関数はあります。


第5引数:DWORD_PTR dwContext

 返されるハンドルと共にコールバック関数に渡されるアプリケーション定義値を指定する変数へのポインター。


第6引数:HINTERNET *phFtpCommand

 コマンドで得られる結果を取得するHINTERNET型ハンドルです。このハンドルを使って、次のステップでInternetReadFile関数を使って応答の文字列を得ることが出来ます。


 

前の記事:Win32/64ネットワーク開発 002 wininetを使ったhttp通信

次の記事:Win32/64ネットワーク開発 004 winsockを使ったhttp通信

Win32/64 ネットワーク処理開発へ戻る。