第218章 FTPサーバのカレントディレクトリ表示


前章のプログラムを動かして 何が一番使いにくいかというと、FTPの指定のディレクトリに どんなファイルがあるのかわからない、という点ではないでしょうか。 FTPの現在のディレクトリ内のファイル表示がされればかなり 便利になると思われます。



今回はとりあえず、現在のFTPのディレクトリを左のような感じで 表示します。<dir>と表示のあるのはサブディレクトリです。 この中に入っていきたいときは、これをダブルクリックします。

さて、このプログラムでいろいろなサーバーに接続してみると、 「..」「.」のディレクトリを表示するものと、そうでないものがあることが わかります。「..」のディレクトリを表示している場合は これをダブルクリックするとひとつ上のディレクトリに移動できます。

さて、「..」を表示しないFTPサーバーのことも考えて「ひとつ上に」行く ボタンなどを考える必要がありそうです。



今回のプログラムで必要な関数はFtpGetCurrentDirectory、 FtpFindFirstFile、InternetFindNextFileです。

BOOL FtpGetCurrentDirectory( IN HINTERNET hFtpSession, OUT LPSTR lpszCurrentDirectory, IN OUT LPDWORD lpdwCurrentDirectory );

FTPセッションからカレント・ディレクトリを取得します。

hFtpSessionには、FTPセッションの有効なハンドルを指定します。

lpszCurrentDirectoryには、カレントディレクトリ名を取得するバッファの アドレスを指定します。

lpdwCurrentDirectoryには、バッファのサイズを保存した変数のアドレスを 指定します。関数が成功したら変数はバッファにコピーした文字数を受け取ります。

この関数は成功するとTRUEを返します。その他の場合はFALSEを返します。

間違えやすいのがlpdwCurrentDirectoryの指定です。用意したバッファサイズを 変数に代入して、その変数のアドレスを指定します。筆者は単にコピーされた バイト数を格納する変数のアドレスを指定すればよいのかと思って失敗しました。 (なんとなくINとOUTの意味がわかってきたでしょうか)

HINTERNET FtpFindFirstFile( IN HINTERNET hFtpSession, IN LPCSTR lpszSearchFile, OUT LPWIN32_FIND_DATA lpFindFileData, IN DWORD dwFlags, IN DWORD dwContext );

FTPセッションの指定されたディレクトリの中を検索します。検索結果は WIN32_FINFD_DATA構造体に格納されます。

hFtpSessionには、FTPセッションのハンドルを指定します。

lpszSearchFileにはディレクトリ名またはファイル名を指定します。 ワイルドカードを指定することもできますが、空白のスペースは許されていません。 これがNULLまたは空の文字列であれば、現在のディレクトリの最初のファイルを 見つけます。

lpFindFileDataには、発見したディレクトリやファイルの情報を取得する WIN32_FIND_DATA構造体のアドレスを指定します。

dwFlagsには、次の値の中から1つを選ぶことができます。
INTERNET_FLAG_DONT_CACHE戻ってきたものをキャッシュに加えません
INTERNET_FLAG_HYPERLINKネットワークからのアイテムをリロードするか どうかを決定するときサーバーから「期限切れ」とか最終更新時間がなければ 強制的にリロードします。
INTERNET_FLAG_MAKE_PERSISTENTサポートされていません
INTERNET_FLAG_MUST_CACHE_REQUESTファイルがキャッシュできない時は 一時ファイルを作ります
INTERNET_FLAG_NEED_FILE同上
INTERNET_FLAG_NO_CACHE_WRITE返ってきたものをキャッシュに加えません
INTERNET_FLAG_RELOAD要求されたファイル、オブジェクト、ディレクトリの ダウンロードをキャッシュからではなく、サーバーからダウンロードすることを強制します
INTERNET_FLAG_RESYNCHRONIZEFTPリソースはサーバーからリロードされます

dwContextには、アプリケーション定義の値を指定します。このパラメーターは アプリケーションがすでにInternetSetStatusCallbackを呼んでいる時のみ使われます。

さて、ヘルプにはFtpFindFirstFile関数はFTPセッションで一度だけ使うことができると書かれています。 FtpFindFirstFile関数を呼んだ後でもし、InternetCloseHandleを呼ばなければアプリケーションは 再度FtpFindFirstFile関数を呼ぶことができない、と書かれています。もしそうすると 関数はERROR_FTP_TRANSFER_IN_PROGRESSを伴って失敗する、とも書かれています。 どうも試行錯誤でプログラムを書いてみると、この意味はFtpFindFirstFileの返す HINTERNETをクローズしないと次にFtpFindFirstFile関数を呼べない、という意味のようです。

WIN32_FIND_DATA構造体は次のように定義されています。

typedef struct _WIN32_FIND_DATA { // wfd DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; TCHAR cFileName[ MAX_PATH ]; TCHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATA;

dwFileAttributesは発見されたファイルのアトリビュートを特定します。 このメンバは次の値の1つまたは組み合わせです。
FILE_ATTRIBUTE_ARCHIVEファイルまたはディレクトリはアーカイブされています
アプリケーションはバックアップとか削除のためにこのアトリビュートを使うことができます
FILE_ATTRIBUTE_COMPRESSEDファイルまたはディレクトリは圧縮されています
ディレクトリでは新しく作られるファイルとかサブディレクトリはデフォルトで圧縮されることを 意味します
FILE_ATTRIBUTE_DIRECTORYディレクトリです
FILE_ATTRIBUTE_ENCRYPTED隠しファイル・ディレクトリ
FILE_ATTRIBUTE_HIDDEN隠しファイル・ディレクトリ
FILE_ATTRIBUTE_NORMALファイルやディレクトリは他のアトリビュートを持ちません
このアトリビュートは単独で使われたときのみ有効です
FILE_ATTRIBUTE_OFFLINEファイルデータはすぐには利用できません
ファイルデータは物理的にオフラインstorageに移動されています
FILE_ATTRIBUTE_READONLY読み出し専用です
FILE_ATTRIBUTE_REPARSE_POINT
FILE_ATTRIBUTE_SPARSE_FILE
FILE_ATTRIBUTE_SYSTEMファイルやディレクトリはOSの一部です
または、OSによって独占的に使われています
FILE_ATTRIBUTE_TEMPORARY一時ファイルです

ftCreationTimeは、ファイルが作られたときの時間を含むFILETIME構造体です。

ftLastAccessTimeは、ファイルが最後にアクセスされた時間を含むFILETIME構造体です。

ftLastWriteTimeは、ファイルが最後に書かれた時刻を含むFILETIME構造体です。

nFileSizeHighはファイルサイズの上位DWORD値です。ファイルサイズは (nFileSizeHigh * MAXDWORD) + nFileSizeLowで求めることができます。

nFileSizeLowファイルサイズの下位DWORD値です。

dwReserved0, dwReserved1は将来のために予約されています。

cFileNameはファイル名です。

cAlternateFileNameは「8.3」式ファイル名です。

BOOL InternetFindNextFile( IN HINTERNET hFind, OUT LPVOID lpvFindData );

FtpFindFirstFile関数またはGopherFindFirstFile関数呼び出しのあと引き続きファイルを 探します。成功するとTRUEを返します。一致するファイルを見つけられないときは GetLastError関数がERROR_NO_MORE_FILESを返します。

hFindはFtpFindFirstFile、GopherFindFirstFileまたは、InternetOpenUrl関数の戻り値を 指定します。

lpvFindDataは発見されたファイルやディレクトリの情報を取得するバッファのアドレスを 指定します。このフォーマットは、使われているプロトコールに依存します。 FTPプロトコールではWIN32_FIND_DATA構造体を使います。

DWORD GetLastError(VOID)

呼び出し側スレッドの最新エラーコードを返します。

さて、上の図を見てわかるように、このプログラムには 親ウィンドウに二つの子ウィンドウが乗っています。 親のサイズが変えられたとき、子供のサイズも変える必要があります。 普通はWM_SIZEメッセージが来たときにMoveWindow関数などで処理すれば よいのですが今回はDeferWindowPos関数を使っています。 複数のウィンドウサイズを変えるときは、この方が効率的です。 第118章に名前だけ出てきましたが、実際には解説していませんでした。

HDWP BeginDeferWindowPos( int nNumWindows );

multiple-window-position構造体にメモリを割り当て、ハンドルを返します。
nNumWindowsにはウィンドウの数を指定します。
失敗したときはNULLを返します。

HDWP DeferWindowPos( HDWP hWinPosInfo, HWND hWnd, HWND hWndInsertAfter, int x, int y, int cx, int cy, UINT uFlags );

hWinPosInfoにはBeginDeferWindowPos関数または、直前の DeferWindowPos関数の戻り値を指定します。

hWndは、移動するウィンドウのハンドルです。

hWndInsertAfterは、hWndで指定したウィンドウに先行する ウィンドウハンドルまたは、次の値のいずれかを指定します。

HWND_BOTTOMウィンドウを一番下に置きます
HWND_NOTOPMOSTウィンドウをWS_EX_TOPMOST以外の すべてのウィンドウの上位に置きます。
HWND_TOPウィンドウをZ方向の手前に置きますが 非最前面ウィンドウが最前面ウィンドウになることはありません。
HWND_TOPMOSTウィンドウを最前面にします。

x, yにはウインドウの左上隅の座標を指定します。

cx, cyにはウィンドウの幅、高さを指定します。

uFlagsには、SWP_HIDEWINDOW(ウィンドウ非表示)、 SWP_SHOWWINDOW(ウィンドウを表示)などの 値の組み合わせを指定します。他にも多数あるのでヘルプで 調べてみてください。

BOOL EndDeferWindowPos( HDWP hWinPosInfo );

ウィンドウの位置・サイズを一斉に変更します。
hWinPosInfoには、BiginDeferWindowPos関数または直前の DeferWindowPos関数の戻り値を指定します。

では、プログラムを見てみましょう。

リソーススクリプトに変更はないので省略します。

// myftp02.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <wininet.h> #include <windowsx.h> #include "resource.h" #define ID_LIST 100 #define ID_STATIC 101 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; 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); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL MyConnect(HWND, INETHANDLE *, ACCOUNT, FTPADDRESS, HWND, HWND); void MyDown(HWND, INETHANDLE, FTPADDRESS); void SetAccount(HWND, ACCOUNT *); void SetHost(HWND, FTPADDRESS *); void GetAllFiles(HWND, HWND, HWND, INETHANDLE *); const char szWindowTitle[] = "猫でもわかるFTP"; char szClassName[] = "myftp02"; //ウィンドウクラス BOOL bConnect = FALSE;//インターネットに接続しているかどうか

表示用のリストボックス、スタティックウィンドウの識別子(ID_LIST, ID_STATIC)を指定しています。
あと、MyConnect関数の引数が増えました。
また、GetAllFilesという指定されたFTPのディレクトリ中のファイルを すべてリストボックスに表示する関数を新たに作りました。
タイトルバーに表示するタイトルの表示方法を少し変えたので、 szWindowTitle変数はconstにしました。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, szWindowTitle, //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

これは、前章とまったく同じです。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static ACCOUNT myaccount; static FTPADDRESS myftpaddress; static INETHANDLE inet; static HWND hList, hStatic; static HINSTANCE hInst; RECT rc; HDWP hDwp; static char szDir[MAX_PATH], szBuf[MAX_PATH]; int i, iIndex; switch (msg) { case WM_CREATE: hInst = ((LPCREATESTRUCT)lp)->hInstance; GetClientRect(hWnd, &rc); hList = CreateWindowEx(WS_EX_WINDOWEDGE, "LISTBOX", "", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_SORT | LBS_NOTIFY, 10, 40, rc.right - rc.left - 20, rc.bottom - rc.top - 50, hWnd, (HMENU)ID_LIST, hInst, NULL); hStatic = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 5, rc.right - rc.left -20, 30, hWnd, (HMENU)ID_STATIC, hInst, NULL); break; case WM_SIZE: hDwp = BeginDeferWindowPos(2); hDwp = DeferWindowPos(hDwp, hStatic, HWND_TOP, 10, 5, LOWORD(lp) - 20, 30, SWP_SHOWWINDOW); hDwp = DeferWindowPos(hDwp, hList, HWND_TOP, 10, 40, LOWORD(lp) - 20, HIWORD(lp) - 50, SWP_SHOWWINDOW); EndDeferWindowPos(hDwp); break; case WM_COMMAND: switch (LOWORD(wp)) { case ID_LIST: if (HIWORD(wp) == LBN_DBLCLK) { iIndex = ListBox_GetCurSel(hList); ListBox_GetText(hList, iIndex, szDir); for (i = 0; i <= lstrlen(szDir) -5 ; i++) { szBuf[i] = szDir[i + 6]; } FtpSetCurrentDirectory(inet.hHost, szBuf); GetAllFiles(hWnd, hList, hStatic, &inet); } break; case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_CONNECT: bConnect = MyConnect(hWnd, &inet, myaccount, myftpaddress, hList, hStatic); break; case IDM_DOWNLOAD: MyDown(hWnd, inet, myftpaddress); break; case IDM_ACCOUNT: SetAccount(hWnd, &myaccount); break; case IDM_GETHOST: SetHost(hWnd, &myftpaddress); break; case IDM_DISCONNECT: if (!bConnect) { MessageBox(hWnd, "インターネットに接続されていません", "失敗", MB_OK); break; } if (InternetCloseHandle(inet.hHost)) MessageBox(hWnd, "FTPをクローズしました", "OK", MB_OK); if (InternetCloseHandle(inet.hInternet)) MessageBox(hWnd, "インターネットから切り離しました", "OK", MB_OK); bConnect = FALSE; SetWindowText(hWnd, szWindowTitle); break; } break; case WM_CLOSE: if (bConnect) { MessageBox(hWnd, "インターネットの接続を切ってから終了してください", "注意", MB_OK); break; } id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hList); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

親ウィンドウのプロシージャです。

WM_CREATEメッセージが来たらリストボックスと、スタティックウィンドウを 子供ウィンドウとして作っています。
リストボックスを作るときウィンドウスタイルにLBS_NOTIFYを加えないと通知メッセージが 親に届かない、とヘルプにかかれていますが実際はなくても届きます。

WM_SIZEメッセージが来たら2つの子供ウィンドウの位置、大きさを調整しています。 DeferWindowPos関数一派の使い方を見ておいてください。

さて、リストボックスのディレクトリ名の項目がダブルクリックされた時、 FTPのカレントディレクトリを変えたいので、まずはリストボックスの通知メッセージ LBN_DBLCLKを捕まえなくてはなりません。これは、WM_COMMANDの形で 送られてきます。wParam値の下位WORD値ががリストボックスのID、上位WORD値が 通知コードとなります。LBN_DBLCLKを捕まえたら、現在選択されている 項目のテキストを取得します。しかしこれには「<dir>」(後述)がついてるので これを取り除く必要があります。そしてFtpSetCurrentDirectory関数でカレントディレクトリを 変更します。そのあと、自作GetAllFiles関数でこのディレクトリにあるすべてのファイルを リストボックスに表示します。

メニューからIDM_CONNECTが選択されたときMyConnect関数を呼びますが、 引数が変更になっているので注意してください。

メニューからIDM_DISCONNECTが選択されたとき、ウィンドウタイトルを 元に戻しますがここも少し前章とは変えていますので注意してください。

プログラム終了時一応子供ウィンドウを破棄してから親を破棄しています。

BOOL MyConnect(HWND hWnd, INETHANDLE *lpinet, ACCOUNT myact, FTPADDRESS ftpadr, HWND hList, HWND hStatic) { char szStr[128], szStr2[128]; 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; } lpinet->hHost = InternetConnect(lpinet->hInternet, ftpadr.szHost, INTERNET_DEFAULT_FTP_PORT, myact.szUserName, myact.szPassWord, INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, 0); 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; }

ウィンドウタイトルの表示の仕方を前章とは少し変えています。 また、FTPに接続後カレントディレクトリを指定のものに変更後自作関数のGetAllFiles関数を 呼んでリストボックスにファイルを表示させています。

void MyDown(HWND hWnd, INETHANDLE inet, FTPADDRESS ftpadr) { static FNAME myfname; HINSTANCE hInst; if (!bConnect) { MessageBox(hWnd, "インターネットに接続されていません", "失敗", MB_OK); return; } hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); if (DialogBoxParam(hInst, "MYGETFNAME", hWnd, (DLGPROC)MyGetFNameProc, (LPARAM)&myfname) == IDCANCEL) return; if (!FtpGetFile(inet.hHost, //FTPのインターネットハンドル myfname.szFName, //ダウンロードするファイル myfname.szLocalFileName, //ダウンロード先のパス付ファイル名 TRUE, //ダウンロード先に同名のファイルがあるときエラーにする(上書き防止) FILE_ATTRIBUTE_NORMAL, //ダウンロード先に作られるファイルのアトリビュート FTP_TRANSFER_TYPE_BINARY, //バイナリファイルとしてダウンロード 0 )) { MessageBox(hWnd, "ダウンロードに失敗しました。", "失敗", MB_OK); return; } MessageBox(hWnd, "無事ダウンロードできました", "OK", MB_OK); return; } LRESULT CALLBACK MyGetFNameProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static FNAME *lpfname; static HWND hFName, hLocalFName; switch (msg) { case WM_INITDIALOG: lpfname = (FNAME *)lp; hFName = GetDlgItem(hDlg, IDC_FNAME); hLocalFName = GetDlgItem(hDlg, IDC_LOCALFILE); Edit_SetText(hFName, lpfname->szFName); Edit_SetText(hLocalFName, lpfname->szLocalFileName); SetFocus(hFName); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hFName, lpfname->szFName, sizeof(lpfname->szFName)); Edit_GetText(hLocalFName, lpfname->szLocalFileName, sizeof(lpfname->szLocalFileName)); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; } void SetAccount(HWND hWnd, ACCOUNT *lpmyaccount) { HINSTANCE hInst; if (bConnect) { MessageBox(hWnd, "接続を切ってから再設定してください", "注意", MB_OK); return; } hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); DialogBoxParam(hInst, "MYACCOUNT", hWnd, (DLGPROC)MyAccountProc, (LPARAM)lpmyaccount); return; } LRESULT CALLBACK MyAccountProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static ACCOUNT *lpmyact; static HWND hID, hPass; switch (msg) { case WM_INITDIALOG: lpmyact = (ACCOUNT *)lp; hID = GetDlgItem(hDlg, IDC_ID); hPass = GetDlgItem(hDlg, IDC_PASSWORD); Edit_SetText(hID, lpmyact->szUserName); Edit_SetText(hPass, lpmyact->szUserName); SetFocus(hID); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hID, lpmyact->szUserName, sizeof(lpmyact->szUserName)); Edit_GetText(hPass, lpmyact->szPassWord, sizeof(lpmyact->szPassWord)); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; } void SetHost(HWND hWnd, FTPADDRESS *lpftpadr) { HINSTANCE hInst; if (bConnect) { MessageBox(hWnd, "接続を切ってから再設定してください。", "注意", MB_OK); return; } hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); DialogBoxParam(hInst, "MYFTPADDRESS", hWnd, (DLGPROC)MyFtpAddressProc, (LPARAM)lpftpadr); return; } LRESULT CALLBACK MyFtpAddressProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static FTPADDRESS *lpftpadr; static HWND hFTPAdr, hDir; switch (msg) { case WM_INITDIALOG: lpftpadr = (FTPADDRESS *)lp; hFTPAdr = GetDlgItem(hDlg, IDC_FTPADDRESS); hDir = GetDlgItem(hDlg, IDC_BASEDIR); Edit_SetText(hFTPAdr, lpftpadr->szHost); Edit_SetText(hDir, lpftpadr->szBaseDir); SetFocus(hFTPAdr); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hFTPAdr, lpftpadr->szHost, sizeof(lpftpadr->szHost)); Edit_GetText(hDir, lpftpadr->szBaseDir, sizeof(lpftpadr->szBaseDir)); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }

これらには特に変更はありません。

void GetAllFiles(HWND hWnd, HWND hList, HWND hStatic, INETHANDLE *lpinet) { WIN32_FIND_DATA FindFileData; HINTERNET hFindHandle; char fname[MAX_PATH + 5]; char szStr[MAX_PATH]; DWORD dwCD = MAX_PATH; if (FtpGetCurrentDirectory(lpinet->hHost, szStr, &dwCD) == TRUE) SetWindowText(hStatic, szStr); else { MessageBox(hWnd, "現在のディレクトリを取得できません", "失敗", MB_OK); return; } ListBox_ResetContent(hList); hFindHandle = FtpFindFirstFile(lpinet->hHost, "*.*", &FindFileData, INTERNET_FLAG_RELOAD, 0); if (hFindHandle == NULL) { if (GetLastError() == ERROR_NO_MORE_FILES) { MessageBox(hWnd, "ファイルはありません", "Error", MB_OK); return; } else { MessageBox(hWnd, "深刻なエラーが発生しました", "Error", MB_OK); InternetCloseHandle(lpinet->hHost); InternetCloseHandle(lpinet->hInternet); bConnect = FALSE; SetWindowText(hWnd, szWindowTitle); return; } } else { if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { strcpy(fname, "<dir> "); strcat(fname, FindFileData.cFileName); } else { strcpy(fname, FindFileData.cFileName); } ListBox_AddString(hList, fname); } while (InternetFindNextFile(hFindHandle, &FindFileData)) { if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { strcpy(fname, "<dir> "); strcat(fname, FindFileData.cFileName); } else { strcpy(fname, FindFileData.cFileName); } ListBox_AddString(hList, fname); } InternetCloseHandle(hFindHandle); return; }

さて、これがこの章のメインのディレクトリにあるファイルをリストボックスに 表示する関数です。

まずは、FtpGetCurrentDirectory関数で現在のディレクトリを取得して、これを スタティックウィンドウに表示します。

次に、ListBox_ResetContentマクロでリストボックスをクリアします。

FtpFindFirstFile関数を探すファイルを"*.*"にして呼び出します。
この戻り値がNULLでなおかつGetLastError関数がERROR_NO_MORE_FILESを 返したときは指定のディレクトリにファイルはないのでその旨を メッセージボックスで表示して関数を終了します。

もし、FtpFindFirstFile関数の戻り値が上記以外の原因でNULLならば、きっと 何かまずいことが起こっているのでエラー表示をして関数を終了させます。

成功したらWIN32_FIND_DATA構造体のdwFileAttributesを調べて、これが ディレクトリならばファイル名の前に「<dir> 」をつけてリストボックスに表示します。 ディレクトリでなければそのままリストボックスに表示します。

そのあとInternetFindNextFile関数を失敗するまで繰り返し呼び出します。 見つかったファイルがディレクトリなら先ほどと同様の処理をします。 ファイルをすべてリストボックスに表示し終えたら FtpFindFirstFile関数の戻り値hFindHandleをクローズして終了します。

こう書くと簡単そうに見えますが、実際にプログラムを作ってみると細かなことで いろいろ面倒なことが起こります。

今回のプログラムではリストボックスに表示されているディレクトリ名を ダブルクリックすると、カレントディレクトリを変更することができますが さらに、ファイル名をクリックするとそれをダウンロードできるように 改良してみてください。


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

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