左の図のようにInternetConnect関数が実行中に現在内部でどんな作業を
しているかわかると便利です。
ファイルのダウンロード中も進行経過がわかるとありがたいですね。
では、このようなことはどうすれば実現できるのでしょうか。
簡単に書くとこのようになります。これではちょっとわかりにくいので もう少し詳しく書きます。1.InternetOpen関数実行後にInternetSetStatusCallback関数を実行する 2.InternetSetStatusCallback関数で指定したコールバック関数を書く 3.InternetConnect, FtpGetFile関数などの最後の引数にアプリケーション定義の dwContextを指定する
コールバック関数をセットします。INTERNET_STATUS_CALLBACK InternetSetStatusCallback( IN HINTERNET hInternet, IN INTERNET_STATUS_CALLBACK lpfnInternetCallback );
hInternetにはInternetOpen関数の戻り値であるインターネットハンドルを指定します。
lpfnInternetCallbackにはコールバック関数の名前を指定します。
コールバック関数は次の形式となります。
hInternetには、インターネットハンドルを指定します。VOID (CALLBACK * INTERNET_STATUS_CALLBACK)( IN HINTERNET hInternet, IN DWORD dwContext, IN DWORD dwInternetStatus, IN LPVOID lpvStatusInformation, IN DWORD dwStatusInformationLength );
dwContextには、アプリケーション定義のコンテキスト値を指定します。 あとでもう少し詳しく解説します。
dwInternetStatusには、コールバック関数が呼ばれた理由を示します。 次の値のひとつです。
INTERNET_STATUS_CLOSING_CONNECTION | サーバーへの接続が閉じられようとしています。 lpvStatusInformationはNULLです。 |
INTERNET_STATUS_CONNECTED_TO_SERVER | サーバーへの接続が成功しました。 lpvStatusInformationにはソケットアドレスのポインタが入ります。 |
INTERNET_STATUS_CONNECTING_TO_SERVER | ソケットアドレスlpvStatusInformationに接続しようとしています。 |
INTERNET_STATUS_CONNECTION_CLOSED | サーバーへの接続を閉じるのに成功しました。 lpvStatusInformationはNULLです。 |
INTERNET_STATUS_HANDLE_CREATED | InternetConnect によって使用され、新規ハンドルを作成したことを示します。 |
INTERNET_STATUS_INTERMEDIATE_RESPONSE | サーバーからintermediate status code を受け取りました。 |
INTERNET_STATUS_NAME_RESOLVED | lpvStatusInformationに含まれる名前のIPアドレスを見つけました。 |
INTERNET_STATUS_RECEIVING_RESPONSE | 要求に対するサーバーの応答を待っています。 lpvStatusInformationはNULLです。 |
INTERNET_STATUS_REDIRECT | HTTP要求が自動的にリダイレクトされようとしています。 lpvStatusInformationは新しいURLへのポインタです。 |
INTERNET_STATUS_REQUEST_COMPLETE | 非同期操作が完了しました。 |
INTERNET_STATUS_REQUEST_SENT | サーバーに要求を送ることに成功しました。 lpvStatusInformationには送られたバイト数へのポインタを表します。 |
INTERNET_STATUS_RESOLVING_NAME | lpvStatusInformationに含まれる名前のIPアドレスを探しています。 |
INTERNET_STATUS_RESPONSE_RECEIVED | サーバーから反応を受け取りました。 lpvStatusInformationには、受け取ったバイト数へのポインタを表します。 |
INTERNET_STATUS_SENDING_REQUEST | サーバーに要求を送っています。 lpvStatusInformationはNULLです。 |
INTERNET_STATUS_STATE_CHANGE | HTTPSとHTTP間での移動がありました。 |
lpvStatusInformationには、情報を格納するバッファのアドレスを指定します。
dwStatusInformationLengthには、lpvStatusInformationバッファのサイズです。
さて、ここで具体的に使い方をみてみましょう。
と、こんな感じになります。INTERNET_STATUS_***が来たときに 必要な処理(状況の表示など)を行います。 上の例で、InternetConnect関数の最後の引数を構造体のアドレスにすれば コールバック関数にいろいろな情報を送ることもできます。hInternet = InternetOpen(); InternetSetStatusCallback(hInternet, MyCallback); ... InternetConnect(hInternet,...dwContext); ... void CALLBACK MyCallback(HINTERNET X, DWORD dwCon, DWORD dwStatus,...) { switch (dwCon) { case dwContext: switch (dwStatus) { case INTERNET_STATUS_SENDING_REQUEST: ..... }
それと、INTERNET_STATUS_REQUEST_COMPLETEは非同期操作のみで 有効であることに注意してください。
では、プログラムを見てみることにします。
リソース・スクリプト(myftp07.rc)は変更がありません。
CONTEXT_GETFILE, CONTEXT_CONNECTを適当な値でdefineしています。// myftp07.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <wininet.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #define ID_LIST 100 #define ID_STATIC 101 #define ID_LISTL 102 #define ID_STATICL 103 #define UP 1 //ソート;昇順 #define DOWN 2 //降順 #define NO_OF_SUBITEM 3 //サブアイテムの数 #define CONTEXT_GETFILE 100 #define CONTEXT_CONNECT 200 typedef struct _tagAccount{ char szUserName[64]; char szPassWord[64]; } ACCOUNT; typedef struct _tagFTPAddress{ char szHost[64]; char szBaseDir[64]; } FTPADDRESS; typedef struct _tagINETHANDLE{ HINTERNET hInternet; HINTERNET hHost; } INETHANDLE; typedef struct _tagFNAME{ char szFName[MAX_PATH]; char szLocalFileName[MAX_PATH]; } FNAME; typedef struct _tagHWNDSET{ HWND hwnd1; HWND hwnd2; } HWNDSET; typedef struct _tagSORTDATA{ HWND hwndList; //リストビューのhwnd int isortSubItem; //ソートするサブアイテムインデックス int iUPDOWN; //昇順か降順か } SORTDATA; typedef struct _tagSTATUSCALLBACK { HWND hwndStatus; DWORD dwFrom; } STATUSCALLBACK; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyAccountProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyFtpAddressProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyGetFNameProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDriveProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyCreateDirProc(HWND, UINT, WPARAM, LPARAM); int CALLBACK MyCompProc(LPARAM, LPARAM, LPARAM); void CALLBACK MyInetCallback(HINTERNET, DWORD, DWORD, LPVOID, DWORD); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL MyConnect(HWND, INETHANDLE *, ACCOUNT, FTPADDRESS, HWND, HWND); void MyDown(HWND, INETHANDLE, FTPADDRESS, FNAME *, HWND, HWND, HWND); void SetAccount(HWND, ACCOUNT *); void SetHost(HWND, FTPADDRESS *); void GetAllFiles(HWND, HWND, HWND, INETHANDLE *); void SetMyLocalDir(HWND, HWND, HWND); void ChangeLocalDrive(HWND, HWND, HWND); void MyUp(HWND, INETHANDLE, HWND, HWND, HWND, HWND); void MyFtpDelFile(HWND, INETHANDLE, HWND, HWND); void MyFtpCreatDir(HWND, INETHANDLE, HWND, HWND); HWND MakeMyList(HWND); void InsertMyColumn(HWND); const char szWindowTitle[] = "猫でもわかるFTP"; char szClassName[] = "myftp07"; //ウィンドウクラス BOOL bConnect = FALSE;//インターネットに接続しているかどうか
WinMain, InitApp, InitInstance, WndProcの各関数に変更はありません。
STATUSCALLBACK構造体は筆者が勝手に定義した構造体です。BOOL MyConnect(HWND hWnd, INETHANDLE *lpinet, ACCOUNT myact, FTPADDRESS ftpadr, HWND hList, HWND hStatic) { char szStr[128], szStr2[128]; STATUSCALLBACK sc; if (bConnect) { MessageBox(hWnd, "すでに接続済みです", "接続済", MB_OK); return TRUE; } if (strcmp(myact.szPassWord, "") == 0 || strcmp(myact.szPassWord, "") ==0) { MessageBox(hWnd, "アカウントが設定されていません", "失敗", MB_OK); return FALSE; } if (strcmp(ftpadr.szHost, "") == 0) { MessageBox(hWnd, "FTPのホスト名が設定されていません", "失敗", MB_OK); return FALSE; } if (strcmp(ftpadr.szBaseDir, "") == 0) { MessageBox(hWnd, "基準となるディレクトリが設定されていません", "失敗", MB_OK); return FALSE; } lpinet->hInternet = InternetOpen("myftp01", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); if (lpinet->hInternet == NULL) { MessageBox(hWnd, "インターネットを開けません", "失敗", MB_OK); InternetCloseHandle(lpinet->hInternet); return FALSE; } if (InternetSetStatusCallback(lpinet->hInternet, MyInetCallback) == INTERNET_INVALID_STATUS_CALLBACK) { MessageBox(hWnd, "コールバックを設定できません", "Error", MB_OK); return FALSE; } sc.dwFrom = CONTEXT_CONNECT; sc.hwndStatus = hStatic; lpinet->hHost = InternetConnect(lpinet->hInternet, ftpadr.szHost, INTERNET_DEFAULT_FTP_PORT, myact.szUserName, myact.szPassWord, INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, (DWORD)&sc); if (lpinet->hHost == NULL) { MessageBox(hWnd, "接続中にエラー発生", "OK", MB_OK); InternetCloseHandle(lpinet->hInternet); return FALSE; } if (!FtpSetCurrentDirectory(lpinet->hHost, ftpadr.szBaseDir)) { MessageBox(hWnd, "ディレクトリの設定ができません", "失敗", MB_OK); return FALSE; } MessageBox(hWnd, "FTPに接続しました", "接続完了", MB_OK); strcpy(szStr, szWindowTitle); wsprintf(szStr2, " <<%sに接続済み>>", ftpadr.szHost); strcat(szStr, szStr2); SetWindowText(hWnd, szStr); GetAllFiles(hWnd, hList, hStatic, lpinet); return TRUE; }
InternetOpen関数を実行後、InternetSetStatusCallback関数で コールバック関数をセットしています。
InternetConnect関数を呼ぶ前にSTATUSCALLBACK構造体の メンバをセットして、InternetConnect関数の最後の引数に そのポインタを渡しています。これで、この関数の途中の状況を コールバック関数で把握することができます。そしてSTATUSCALLBACK 構造体に含まれているウィンドウハンドルを持つウィンドウに 状況表示することが可能になります。
STATUSCALLBACK構造体のメンバを設定してFtpGetFile関数の最後の引数に この構造体のアドレスを渡します。これで、FtpGetFile関数の途中経過を 知ることができます。void MyDown(HWND hWnd, INETHANDLE inet, FTPADDRESS ftpadr, FNAME *lpfname, HWND hList, HWND hListL, HWND hStaticL) { HINSTANCE hInst; int iIndex; static STATUSCALLBACK sc; if (!bConnect) { MessageBox(hWnd, "インターネットに接続されていません", "失敗", MB_OK); return; } sc.hwndStatus = hStaticL; sc.dwFrom = CONTEXT_GETFILE; iIndex = ListView_GetNextItem(hList, -1, LVNI_SELECTED); ListView_GetItemText(hList, iIndex, 0, lpfname->szFName, MAX_PATH); GetWindowText(hStaticL, lpfname->szLocalFileName, sizeof(lpfname->szLocalFileName)); strcat(lpfname->szLocalFileName, "\\"); strcat(lpfname->szLocalFileName, lpfname->szFName); hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); if (DialogBoxParam(hInst, "MYGETFNAME", hWnd, (DLGPROC)MyGetFNameProc, (LPARAM)lpfname) == IDCANCEL) return; if (!FtpGetFile(inet.hHost, //FTPのインターネットハンドル lpfname->szFName, //ダウンロードするファイル lpfname->szLocalFileName, //ダウンロード先のパス付ファイル名 TRUE, //ダウンロード先に同名のファイルがあるときエラーにする(上書き防止) FILE_ATTRIBUTE_NORMAL, //ダウンロード先に作られるファイルのアトリビュート FTP_TRANSFER_TYPE_BINARY, //バイナリファイルとしてダウンロード (DWORD)&sc)) { MessageBox(hWnd, "ダウンロードに失敗しました。", "失敗", MB_OK); return; } SetMyLocalDir(hWnd, hListL, hStaticL); MessageBox(hWnd, "無事ダウンロードできました", "OK", MB_OK); return; }
MyGetFNameProc, SetAccount, MyAccountProc, SetHost, MyFtpAddressProc, GetAllFiles, SetMyLocalDir, ChangeLocalDrive, MyDriveProc, MyUp, MyFtpDelFile, MyFtpCreatDir, MyCreateDirProc, MakeMyList, InsertMyColumn, MyCompProcの各関数に変更はありません。
関数の途中の状況を調べるコールバック関数です。 今回はInternetConnectとFtpGetFile関数のみ調べています。 FtpPutFile関数についても同様の処理を行ってみてください。 また、INTERNET_STATUS_***が来るたびにメッセージボックスを出していますが これは、確認用のプログラムで実際にはメッセージボックス部分は不要です。void CALLBACK MyInetCallback(HINTERNET hInet, DWORD dwContx, DWORD dwStatus, LPVOID lpvStatus, DWORD dwLength) { char str[1024]; LPDWORD lpdwByte; static DWORD dwTemp; STATUSCALLBACK *lpsc; lpsc = (STATUSCALLBACK *)dwContx; switch (lpsc->dwFrom) { case CONTEXT_GETFILE: switch (dwStatus) { case INTERNET_STATUS_SENDING_REQUEST: SetWindowText(lpsc->hwndStatus, "サーバーに要求を送信しています"); MessageBox(lpsc->hwndStatus, "サーバーに要求を送信しています", "CALLBACK", MB_OK); break; case INTERNET_STATUS_RESPONSE_RECEIVED: lpdwByte = (LPDWORD)lpvStatus; if (dwTemp < *lpdwByte) { wsprintf(str, "%dバイトダウンロードしました", *lpdwByte); SetWindowText(lpsc->hwndStatus, str); MessageBox(NULL, str, "OK", MB_OK); dwTemp = *lpdwByte; } break; case INTERNET_STATUS_HANDLE_CREATED: dwTemp = 0; SetWindowText(lpsc->hwndStatus, "ftp handle created"); MessageBox(NULL, "FTPセッションハンドルが作られました", "OK", MB_OK); break; } break; case CONTEXT_CONNECT: switch (dwStatus) { case INTERNET_STATUS_CONNECTING_TO_SERVER: wsprintf(str, "ソケットアドレス%sに接続中", lpvStatus); SetWindowText(lpsc->hwndStatus, str); MessageBox(NULL, str, "CALLBACK", MB_OK); break; case INTERNET_STATUS_CONNECTED_TO_SERVER: wsprintf(str, "ソケットアドレス%sに接続しました", lpvStatus); SetWindowText(lpsc->hwndStatus, str); MessageBox(NULL, str, "CALLBACK", MB_OK); break; case INTERNET_STATUS_CONNECTION_CLOSED: strcpy(str, "サーバーからの接続をクローズしました"); SetWindowText(lpsc->hwndStatus, str); MessageBox(NULL, str, "CALLBACK", MB_OK); break; case INTERNET_STATUS_RECEIVING_RESPONSE: MessageBox(NULL, "サーバーからの応答を待っています", "CALLBACK", MB_OK); SetWindowText(lpsc->hwndStatus, "サーバーからの応答を待っています"); break; } break; } return; }
また、ファイルのダウンロード時にlpdwByteはだんだん大きな値を 示し最終的にダウンロードしたファイルのサイズを示しますが時々 前値より小さな値が来ることがあります。そのためその値を無視するために 少々手間をかけています。
今回のプログラムは同期操作であることに注意してください。 (InternetOpen関数でINTERNET_FLAG_ASYNCを指定していない。)
非同期操作にするとどうなるかいろいろ試してみてください。
Update 23/Aug/1999 By Y.Kumei