第217章 簡易FTPを作る


さて、今回からいよいよ簡易FTPの制作に取り掛かります。 と、言ってもたいしたものではありません。



メインウィンドウの外観です。 メニューの「ファイル」の下には「終了」(IDM_END)、 「ftp」の下には「接続」(IDM_CONNECT)「ファイルのダウンロード」 (IDM_DOWNLOAD)「切断」(IDM_DISCONNECT)、 「設定」の下には「アカウント設定」(IDM_ACCOUNT)「ホスト設定」(IDM_GETHOST) のサブ項目があります。



「アカウントの設定」を選択すると左のダイアログボックス("MYACCOUNT") が出現します。

IDを入力するエディットボックスのシンボルはIDC_ID、 パスワードを入力するエディットボックスのシンボルはIDC_PASSWORD にします。また、パスワードのエディットボックスのスタイルには 「パスワード」を指定すると入力が伏字(*)になります。



「ホストの設定」を選択すると左のダイアログボックス("MYFTPADDRESS") が出現します。

FTPアドレスを入力するエディットボックスのシンボルはIDC_FTPADDRESS、 基準となるディレクトリを入力するエディットボックスのシンボルは IDC_BASEDIRとします。



ftpサーバーに接続後、「ファイルのダウンロード」を選択すると 左のダイアログボックス("MYGETFNAME")が出現します。

ダウンロード元のファイル名を入力するエディットコントロールの シンボルはIDC_FNAME、ダウンロード先のファイル名を入力する エディットボックスのシンボルはIDC_LOCALFILEとします。 左の図はFTPの基準ディレクトリにあるindex.htmlファイルを 自分のコンピュータのデスクトップにindex.htmlとして ダウンロードする時の入力例です。



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

// myftp01.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "ftp(&T)" BEGIN MENUITEM "接続(&C)", IDM_CONNECT MENUITEM "ファイルのダウンロード(&D)", IDM_DOWNLOAD MENUITEM "切断(&X)", IDM_DISCONNECT END POPUP "設定(&S)" BEGIN MENUITEM "アカウント設定(&A)", IDM_ACCOUNT MENUITEM "ホスト設定(&H)", IDM_GETHOST END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYACCOUNT DIALOG DISCARDABLE 0, 0, 143, 67 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "アカウント" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,7,46,50,14 PUSHBUTTON "キャンセル",IDCANCEL,86,46,50,14 LTEXT "ID",IDC_STATIC,7,7,8,8 LTEXT "パスワード",IDC_STATIC,7,25,32,8 EDITTEXT IDC_ID,43,7,94,13,ES_AUTOHSCROLL EDITTEXT IDC_PASSWORD,43,25,94,13,ES_PASSWORD | ES_AUTOHSCROLL END MYFTPADDRESS DIALOG DISCARDABLE 0, 0, 159, 63 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "ホスト" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,7,42,50,14 PUSHBUTTON "キャンセル",IDCANCEL,102,42,50,14 LTEXT "FTPアドレス",IDC_STATIC,7,7,36,8 LTEXT "基準となるディレクトリ",IDC_STATIC,7,25,64,8 EDITTEXT IDC_FTPADDRESS,75,7,78,13,ES_AUTOHSCROLL EDITTEXT IDC_BASEDIR,75,25,78,13,ES_AUTOHSCROLL END MYGETFNAME DIALOG DISCARDABLE 0, 0, 187, 67 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "ファイル名入力" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,7,46,50,14 PUSHBUTTON "キャンセル",IDCANCEL,130,46,50,14 LTEXT "ダウンロードするファイル",IDC_STATIC,7,7,72,8 EDITTEXT IDC_FNAME,88,7,92,12,ES_AUTOHSCROLL LTEXT "ダウンロード先ファイル名",IDC_STATIC,7,31,74,8 EDITTEXT IDC_LOCALFILE,88,27,92,12,ES_AUTOHSCROLL END

ごく普通のメニューとダイアログボックスのリソース・スクリプトです。

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

いろいろな構造体を適当にtypedefで定義してみました。 必ずしもこのように定義する必要はありません。

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; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_CONNECT: bConnect = MyConnect(hWnd, &inet, myaccount, myftpaddress); 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, "猫でもわかるFTP"); break; } break; case WM_CLOSE: if (bConnect) { MessageBox(hWnd, "インターネットの接続を切ってから終了してください", "注意", MB_OK); break; } id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

ACCOUNT, FTPADDRESS, INETHANDLEは自分でtypedefした構造体です。

メニューからIDM_CONNECTが選択されたら自作関数MyConnectを呼びます。 この関数は接続が成功したらTRUE、失敗したらFALSEを返すようにしてあるので 接続の有無がグローバル変数のbConnectに格納されます。

IDM_DOWNLOADが選択されたら自作関数MyDownを呼びます。

IDM_ACCOUNTが選択されたら自作関数SetAccountを呼びます。

IDM_GETHOSTが選択されたら自作関数SetHostを呼びます。

IDM_DISCONNECTが選択されたらグローバル変数のbConnectを 調べて接続されていなかったらその旨メッセージボックスで知らせます。 接続されていたらInternetCloseHandle関数でftpとインターネットの インターネットハンドルをクローズします。

BOOL MyConnect(HWND hWnd, INETHANDLE *lpinet, ACCOUNT myact, FTPADDRESS ftpadr) { char szStr[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_INVALID_PORT_NUMBER, 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); GetWindowText(hWnd, szWindowTitle, sizeof(szWindowTitle)); wsprintf(szStr, " <<%sに接続済み>>", ftpadr.szHost); strcat(szWindowTitle, szStr); SetWindowText(hWnd, szWindowTitle); return TRUE; }

ftpに接続する関数です。

まずいろいろな設定がされていないときは、その旨の注意をメッセージボックスで 表示して終了します。

必要な設定がされているときは、InternetOpen関数でインターネット関係の 関数を初期化します。次にInternetConnect関数でftpセッションを開始します。 これらがうまくいったらFtpSetCurrentDirectory関数でftpサーバーののディレクトリを 指定のディレクトリに変えます。

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

ファイルダウンロードの関数です。 接続されていないときは注意を促して戻ります。

接続されているときはダウンロードするファイル名と ダウンロード先を指定するダイアログボックスを出します。

さて、ダイアログボックスを出す場合はDialogBoxでよいのですが ダイアログボックスでユーザーが入力した値を得るには これをグローバル変数にしなくてはいけませんでした。 それで、Windowsのプログラムではやたらグローバル変数が増える 傾向があります。そこで今回はDialogBoxParam関数を使います。

int DialogBoxParam( HINSTANCE hInstance, LPCTSTR lpTemplateName, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam );

最後の引数がDialogBoxマクロにはありませんでした。他は同じです。 このdwInitParamに値を設定するとこれがプロシージャでWM_INITDIALOG メッセージのlParamに渡されます。いろいろなデータを送るには 構造体を設定してそのアドレスを渡せばよいことになります。

ダウンロードするファイルとダウンロード先が決まったら FtpGetFile関数でダウンロードします。

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

ダウンロードするファイルとダウンロード先を入力するダイアログボックスの プロシージャです。

WM_INITDIALOGメッセージが来たらlpの値をlpfnameにコピーしています。 lpfnameは自分でtypedefしたFNAME構造体へのポインタです。 この構造体のメンバにダウンロードするファイルと、ダウンロード先ファイル を設定すると、呼び出し元でもこれらを知ることができます。

後は、特に説明がなくてもわかりますね。

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

アカウントを設定するダイアログを呼び出す関数と、そのプロシージャです。 ここでもダイアログ呼び出しにDialogBoxParam関数を使っています。

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

ftpアドレスと基準となるディレクトリを入力するダイアログボックスを 呼び出す関数とそのプロシージャです。ここでもDialogBoxParam関数を 使ってダイアログボックスを出しています。

今回は少し長いでしたが、中身はそれほど面倒ではありませんでした。 実際に使ってみるといろいろ不備で使い勝手が悪いことがわかります。 改良してみてください。それとDialogBoxParam関数はダイアログボックスを 多用するプログラムでは大変重宝するので使い方を覚えておいてください。


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

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