第223章 InternetSetStatusCallback関数を使う


前回まで作ったプログラムでは、途中経過がわかりませんでした。 たとえばファイルをダウンロードしているとき、現在どの程度作業が 進んでいるのかわかりませんでした。大きなファイルをダウンロードしている 場合、これでは不安ですね。



左の図のように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にはコールバック関数の名前を指定します。

コールバック関数は次の形式となります。

VOID (CALLBACK * INTERNET_STATUS_CALLBACK)( IN HINTERNET hInternet, IN DWORD dwContext, IN DWORD dwInternetStatus, IN LPVOID lpvStatusInformation, IN DWORD dwStatusInformationLength );

hInternetには、インターネットハンドルを指定します。

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バッファのサイズです。

さて、ここで具体的に使い方をみてみましょう。

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_***が来たときに 必要な処理(状況の表示など)を行います。 上の例で、InternetConnect関数の最後の引数を構造体のアドレスにすれば コールバック関数にいろいろな情報を送ることもできます。

それと、INTERNET_STATUS_REQUEST_COMPLETEは非同期操作のみで 有効であることに注意してください。

では、プログラムを見てみることにします。

リソース・スクリプト(myftp07.rc)は変更がありません。

// 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;//インターネットに接続しているかどうか

CONTEXT_GETFILE, CONTEXT_CONNECTを適当な値でdefineしています。
また、STATUSCALLBACK構造体をtypedefしています。 この構造体に状況を表示させたいウィンドウのハンドルと、 どの操作(関数)の状況かをメンバとして持たせます。

WinMain, InitApp, InitInstance, WndProcの各関数に変更はありません。

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; }

STATUSCALLBACK構造体は筆者が勝手に定義した構造体です。

InternetOpen関数を実行後、InternetSetStatusCallback関数で コールバック関数をセットしています。

InternetConnect関数を呼ぶ前にSTATUSCALLBACK構造体の メンバをセットして、InternetConnect関数の最後の引数に そのポインタを渡しています。これで、この関数の途中の状況を コールバック関数で把握することができます。そしてSTATUSCALLBACK 構造体に含まれているウィンドウハンドルを持つウィンドウに 状況表示することが可能になります。

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; }

STATUSCALLBACK構造体のメンバを設定してFtpGetFile関数の最後の引数に この構造体のアドレスを渡します。これで、FtpGetFile関数の途中経過を 知ることができます。

MyGetFNameProc, SetAccount, MyAccountProc, SetHost, MyFtpAddressProc, GetAllFiles, SetMyLocalDir, ChangeLocalDrive, MyDriveProc, MyUp, MyFtpDelFile, MyFtpCreatDir, MyCreateDirProc, MakeMyList, InsertMyColumn, MyCompProcの各関数に変更はありません。

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; }

関数の途中の状況を調べるコールバック関数です。 今回はInternetConnectとFtpGetFile関数のみ調べています。 FtpPutFile関数についても同様の処理を行ってみてください。 また、INTERNET_STATUS_***が来るたびにメッセージボックスを出していますが これは、確認用のプログラムで実際にはメッセージボックス部分は不要です。

また、ファイルのダウンロード時にlpdwByteはだんだん大きな値を 示し最終的にダウンロードしたファイルのサイズを示しますが時々 前値より小さな値が来ることがあります。そのためその値を無視するために 少々手間をかけています。

今回のプログラムは同期操作であることに注意してください。 (InternetOpen関数でINTERNET_FLAG_ASYNCを指定していない。)

非同期操作にするとどうなるかいろいろ試してみてください。


[SDK第3部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 23/Aug/1999 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。