※ 修正版をこちらのページにアップしています。将来的にはこのページは削除される可能性があります。
前回は、関数を分割するだけで終了してしまいましたが、さらにファイルも分けてみることにします。
ファイルを分ける事によって、変更箇所がないファイルについては、コンパイルする手間が省けます。
どうしてかというと、普通のコンパイラは、コンパイルしたものを OBJ ファイルとしてファイルごとに保存しており、変更がないファイルに関しては、コンパイル作業を飛びぬかして、その後のリンクから行うことで時間の短縮が行えるからです。
※ 大規模なプロジェクトじゃない限り、コンパイルがそんなに遅くなることはありません。
よく、ファイル分割を行う理由のひとつにコンパイル時間が短くなることがあげられますが、今のコンパイラーはそんなに遅くはないと思うので、昔はそうだったと読み替えてください。
ファイルのきり方は、なんでもいいのですが、今回は、以下のように大別してみました。
@ウインドウ・コントロールの作成、ウインドウプロシージャの処理を行うファイル。
A メッセージ処理を行うファイル
B ディレクトリ、ファイル関連の処理を行うファイル
それぞれ、WinMain.cpp、Message.cpp、Filelist.cpp として、前回ほど分割した関数を配置しました。
以下がその概略です。
|
WinMain.cpp |
Message.cpp |
Filelist.cpp |
/* ウインドウプロシージャ */ WndProc { WM_COMMAND IDM_EXIT SendMessage(WM_CLOSE) WM_CREATE OnCreate(hWnd) WM_DESTROY PostQuitMessage(0) } /* エントリポイント */ WinMain { // ウィンドウクラス登録 RegisterClassEx(&WndClass) // ウィンドウ作成 CreateWindow // ウィンドウの表示 ShowWindow // メッセージループ while (GetMessage(&Msg)) } /* コントロール作成 */ WinCtlInit { // コンボボックス作成 WC_COMBOBOX CreateWindowEx // リストビュウ作成 WC_LISTVIEW CreateWindowEx } |
/* WM_CREATE時処理 */ OnCreate { // コントロール作成 WinCtlInit(ID_CBOCLIENT) WinCtlInit(ID_LVWCLIENT) // コンボボックス初期設定 GetComboBoxItems(hCtlCBOClient) // リストビュウ初期設定 GetListViewItems(hCtlLVWClient) } |
/* コンボボックス設定 */ GetComboBoxItems { // カレントディレクトリ取得 GetCurrentDirectory // ドライブ名取得 GetLogicalDriveStrings } /* リストビュウ設定 */ GetListViewItems { FindFirstFile FindNextFile FindClose // ソート ListView_SortItemsEx(LVWCmpProc) } /* ソート関数 */ LVWCmpProc { lstrcmpi } |
そして、コンパイルすると以下のエラーが出ます。

これはどういうことでしょうか?
前回までに、コンパイラがコンパイルしようとしたときに関数や変数が既知の状態になっていないといけないことをお伝えしました。
これは、同一ファイルだけでなく、複数ファイルでも一緒です。
Message ファイルの中では、WinMain ファイルの WinCtlInit() 関数を呼び出していますが、コンパイラが Message ファイルの WinCtlInit() というコードに達したときに、そんな関数は知らんといっているのです。
Filelist ファイルの GetComboBoxItems() 関数と、GetListViewItems() 関数の呼び出しについても同様です。
WinMain ファイルでも、Message ファイルの OnCreate() 関数を呼び出していますが、同じ理由でコンパイルできません。
同一ファイルであれば、ファイルの上のほうに書けば、よかったですが、異なるファイルの場合、どうしたらよいのでしょうか?
ファイル分割は、ここで頓挫してしまうのでしょうか?
※ プログラミングをやっていると、こうしてみようかなという思いや、ああやってみようという計画が頓挫することはよくあります。
めげないでくださいとしかいいようがありませんが、こんなところもいやになってしまう原因のひとつかも知れません。
実は、ファイルの上の方に書くのと同じ効果を発揮する魔法の技があります。(大袈裟か?)
これを、ヘッダファイルと呼びます。
※ リソースの時にちょっと出てきましたね。
早速、ヘッダファイルなるものを用意しましょう。
ファイル名は 「….h」とすれば、何でもいいのですが、「プロジェクト名.h」や「common.h」という場合が多いようです。
今回は、「common.h」にしました。
/* ========================================================================== */ /* FILE Common.h */ /* CREATE 2007/03/11 */ /* MODIFY ----/--/-- */ /* REMARK */ /* COPYRIGHT 2000-2007, Latin. All Rights Reserved */ /* ========================================================================== */ /* -------------------------------------------------------------------------- */ /* グローバル関数宣言部 */ /* -------------------------------------------------------------------------- */ /* ==================== WinMain.cpp ========================================= */ HWND WinCtlInit(LPCTSTR, HWND, LPVOID, UINT); /* ==================== Message.cpp ========================================= */ void OnCreate(HWND); /* ==================== FileList.cpp ======================================== */ void GetComboBoxItems(HWND); void GetListViewItems(HWND);
ややっ、これは何でしょうか。関数の頭の引数の部分だけしか書いてありません。
これをプロトタイプ宣言といいます。
実は、コンパイラは、コンパイル時点で出てきた関数や変数が既知の状態になっていないといけないのですが、その関数の中身までは既知になっている必要はないのです。
コンパイラは、プロトタイプ宣言があると、その関数の本体はどこかにあるはずということでコンパイルをしてくれるのです。
ですが、引数に関しては、チェックを行っているので、プロトタイプ宣言を行うことで、コンパイルができるようになるのです。
それでも、コンパイルできないでっすって?
ヘッダファイルをインクルードしてください。
ところで、このヘッダファイルには、すべての関数のプロトタイプが書いてあるわけではありません。
そうです。LVWCmpProc() 関数については、Filelist.cpp ファイルの中でしか使いませんね。
このようにファイル内に限定される関数は、そのファイルだけで使えるようにすればいいのです。
※ すべて、ヘッダファイルに入れるという方法もありだとは思いますが、はじめから使わないとわかっている場合は、間違いを起こさないためにも使用するファイル内に限定したほうがいいでしょう。
この場合は、ファイルの上のほうで、プロトタイプ宣言します。
※ プロトタイプ宣言は、引数の型をコンパイルに教える働きがありますので、引数の型は書かないといけませんが、引数名は書いても書かなくても問題ありません。
ふー、何とかWinMain.cpp、Message.cpp、Filelist.cpp の分割までこぎつけました。
/* ========================================================================== */ /* FILE WinMain.cpp */ /* CREATE 2006/10/01 */ /* MODIFY 2007/03/11 */ /* REMARK */ /* COPYRIGHT 2000-2007, Latin. All Rights Reserved */ /* ========================================================================== */ #include <Windows.h> #include <WindowsX.h> #include <CommCtrl.h> #include "Common.h" #include "Resource.h" /* -------------------------------------------------------------------------- */ /* グローバル(EXTERN)関数宣言部 */ /* -------------------------------------------------------------------------- */ extern HWND WinCtlInit(LPCTSTR, HWND, LPVOID, UINT); /* -------------------------------------------------------------------------- */ /* ローカル(STATIC)関数宣言部 */ /* -------------------------------------------------------------------------- */ static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); /* -------------------------------------------------------------------------- */ /* CALLBACK WndProc */ /* PARAM HWND hWnd : ウィンドウハンドル */ /* UINT uMsg : メッセージ */ /* WPARAM wParam : メッセージの追加情報 */ /* LPARAM lParam : メッセージの追加情報 */ /* RETURN LRESULT */ /* メッセージ処理の結果 */ /* REMARK */ /* -------------------------------------------------------------------------- */ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { /* 関数本体部 */ switch (uMsg) { case WM_COMMAND: switch (LOWORD(wParam)) { case IDM_EXIT: SendMessage(hWnd, WM_CLOSE, 0, 0L); break; default: return (DefWindowProc(hWnd, uMsg, wParam, lParam)); } break; case WM_CREATE: OnCreate(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, uMsg, wParam, lParam)); } return 0L; } /* -------------------------------------------------------------------------- */ /* WINAPI WinMain */ /* PARAM HINSTANCE hInstance : 現在のインスタンス */ /* HINSTANCE hPrevInst : 直前のインスタンス */ /* LPSTR lpCmdLine : コマンドライン引数 */ /* int nCmdShow : ウインドウの表示形式 */ /* RETURN int : */ /* メッセージの wParam パラメータ */ /* REMARK Win32 アプリケーションの初期エントリポイント */ /* Windows システムが呼び出す。 */ /* -------------------------------------------------------------------------- */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { /* 変数宣言部 */ HWND hWnd; WNDCLASSEX WndClass; MSG Msg; /* 関数本体部 */ // ウインドウクラス作成 WndClass.cbSize = sizeof(WndClass); WndClass.style = CS_HREDRAW | CS_VREDRAW; WndClass.lpfnWndProc = WndProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.hInstance = hInstance; WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); WndClass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); WndClass.hCursor = LoadCursor(NULL, IDC_ARROW); WndClass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); WndClass.lpszMenuName = MAKEINTRESOURCE(IDM_MENUMAIN); WndClass.lpszClassName = "FTPManager"; // ウィンドウクラス登録 if (! RegisterClassEx(&WndClass)) { return false; } // ウィンドウ作成 hWnd = CreateWindow(WndClass.lpszClassName, // 登録されているクラス名 "FTPManager", // ウィンドウ名 WS_OVERLAPPEDWINDOW, // ウィンドウスタイル CW_USEDEFAULT, // ウィンドウの横方向の位置 CW_USEDEFAULT, // ウィンドウの縦方向の位置 CW_USEDEFAULT, // ウィンドウの幅 CW_USEDEFAULT, // ウィンドウの高さ NULL, // 親ウィンドウまたはオーナーウィンドウのハンドル NULL, // メニューハンドルまたは子ウィンドウ ID hInstance, // アプリケーションインスタンスのハンドル NULL); // ウィンドウ作成データ if (! hWnd) { return false; } // ウィンドウの表示 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // メッセージループ while (GetMessage(&Msg, NULL, 0, 0)) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return (int) Msg.wParam; } /* -------------------------------------------------------------------------- */ /* FUNCTION WinCtlInit */ /* PARAM LPCSTR szClassName : ウィンドウクラス名 */ /* HWND hParentWnd : 親ウィンドウのハンドル */ /* LPVOID lpParam : アイテム情報 */ /* UINT uSize : アイテムのサイズ */ /* RETURN HWND : 作成されたウィンドウのハンドル */ /* REMARK コントロールウインドウの作成 */ /* -------------------------------------------------------------------------- */ HWND WinCtlInit(LPCTSTR szClassName, HWND hParentWnd, LPVOID lpParam, UINT uSize) { /* 変数宣言部 */ HWND hControl; HINSTANCE hInstance; LPLVCOLUMN lpLVColumn; DWORD dwStyle; /* 関数本体部 */ // インスタンス取得 hInstance = (HINSTANCE) GetWindowLong(hParentWnd, GWL_HINSTANCE); // コンボボックス作成 if (lstrcmp(szClassName, "COMBOBOX") == 0) { dwStyle = WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL; hControl = CreateWindowEx(WS_EX_CLIENTEDGE, szClassName, NULL, dwStyle, 10, 5, 380, 100, hParentWnd, 0, hInstance, NULL); if (! hControl) { return NULL; } SendMessage(hControl, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(true, 0)); ComboBox_LimitText(hControl, _MAX_PATH); } // リストビュー作成 else if (lstrcmp(szClassName, WC_LISTVIEW) == 0) { dwStyle = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL; hControl = CreateWindowEx(WS_EX_CLIENTEDGE, szClassName, NULL, dwStyle, 10, 30, 380, 320, hParentWnd, 0, hInstance, NULL); if (! hControl) { return NULL; } if (lpParam) { lpLVColumn = (LPLVCOLUMN) lpParam; for (UINT i = 0; i < (uSize / sizeof(LV_COLUMN)); i++) { ListView_InsertColumn(hControl, i, &lpLVColumn[i]); } } } return hControl; }
/* ========================================================================== */ /* FILE Message.cpp */ /* CREATE 2007/03/11 */ /* MODIFY ----/--/-- */ /* REMARK */ /* COPYRIGHT 2000-2007, Latin. All Rights Reserved */ /* ========================================================================== */ #include <Windows.h> #include <WindowsX.h> #include <CommCtrl.h> #include "Common.h" #include "Resource.h" /* -------------------------------------------------------------------------- */ /* グローバル(EXTERN)関数宣言部 */ /* -------------------------------------------------------------------------- */ extern void OnCreate(HWND); /* -------------------------------------------------------------------------- */ /* コントロール(STATIC)宣言部 */ /* -------------------------------------------------------------------------- */ static HWND hCtlCBOClient; static HWND hCtlLVWClient; /* -------------------------------------------------------------------------- */ /* PROCEDURE OnCreate */ /* PARAM HWND hWnd : ウインドウハンドル */ /* REMARK */ /* -------------------------------------------------------------------------- */ void OnCreate(HWND hWnd) { /* 変数宣言部 */ INITCOMMONCONTROLSEX ICC; LV_COLUMN LVColumn[] = { {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT, 150, "名前", 0, 0}, {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_RIGHT, 75, "サイズ", 0, 1}, {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT, 110, "更新日時", 0, 2}, }; /* 関数本体部 */ ICC.dwSize = sizeof(INITCOMMONCONTROLSEX); ICC.dwICC = ICC_LISTVIEW_CLASSES | ICC_BAR_CLASSES; InitCommonControlsEx(&ICC); /* ------------------------------------------------------------------------ */ /* 配列全体のバイト数は、配列を宣言した関数に限ってsizeof演算子で求められる */ /* ------------------------------------------------------------------------ */ hCtlCBOClient = WinCtlInit("COMBOBOX", hWnd, NULL, 0); hCtlLVWClient = WinCtlInit(WC_LISTVIEW, hWnd, LVColumn, sizeof(LVColumn)); GetComboBoxItems(hCtlCBOClient); GetListViewItems(hCtlLVWClient); return; }
/* ========================================================================== */ /* FILE Filelist.cpp */ /* CREATE 2007/03/11 */ /* MODIFY ----/--/-- */ /* REMARK */ /* COPYRIGHT 2000-2007, Latin. All Rights Reserved */ /* ========================================================================== */ #include <Windows.h> #include <WindowsX.h> #include <CommCtrl.h> #include "Common.h" /* -------------------------------------------------------------------------- */ /* グローバル(EXTERN)関数宣言部 */ /* -------------------------------------------------------------------------- */ extern void GetComboBoxItems(HWND); extern void GetListViewItems(HWND); /* -------------------------------------------------------------------------- */ /* ローカル(STATIC)関数宣言部 */ /* -------------------------------------------------------------------------- */ static int CALLBACK LVWCmpProc(LPARAM, LPARAM, LPARAM); /* -------------------------------------------------------------------------- */ /* PROCEDURE GetComboBoxItems */ /* PARAM HWND hCtlCombo : コンボボックスハンドル */ /* RETURN void */ /* REMARK コンボボックスフォルダ情報取得 */ /* -------------------------------------------------------------------------- */ void GetComboBoxItems(HWND hCtlCombo) { /* 変数宣言部 */ char szCurDir[MAX_PATH + 1]; char szDrives[128]; /* 関数本体部 */ // カレントディレクトリ取得 if (! GetCurrentDirectory(_MAX_DIR + 1, szCurDir)) { MessageBox(NULL, "現在のディレクトリを取得できません", "ERR", MB_OK); return; } ComboBox_SetText(hCtlCombo, szCurDir); ComboBox_AddString(hCtlCombo, szCurDir); // ドライブ名取得 GetLogicalDriveStrings(sizeof(szDrives), szDrives); for (int i = 0; szDrives[i] != '\0'; i++) { ComboBox_AddString(hCtlCombo, &szDrives[i]); // 次のドライブ名へ while (szDrives[i] != '\0') { i++; } } return; } /* -------------------------------------------------------------------------- */ /* PROCEDURE GetListViewItems */ /* PARAM HWND hCtlLView : リストビュウハンドル */ /* RETURN */ /* REMARK リストビュウファイル情報取得 */ /* -------------------------------------------------------------------------- */ void GetListViewItems(HWND hCtlLView) { /* 変数宣言部 */ WIN32_FIND_DATA Win32FindData; HANDLE hFind; LVITEM LVItem; SYSTEMTIME SystemTime; FILETIME FileTime; /* 関数本体部 */ if (INVALID_HANDLE_VALUE == (hFind = FindFirstFile("*.*", &Win32FindData))) { if (GetLastError() != ERROR_NO_MORE_FILES) { MessageBox(NULL, "ファイル一覧の取得に失敗しました", "ERR", MB_OK); return; } } do { // ルートディレクトリ・カレントディレクトリは無視 if (lstrcmp(Win32FindData.cFileName, "." ) == 0 || lstrcmp(Win32FindData.cFileName, "..") == 0) { continue; } // 1項目(名前) LVItem.mask = LVIF_TEXT; LVItem.iItem = 0; LVItem.iSubItem = 0; LVItem.pszText = Win32FindData.cFileName; ListView_InsertItem(hCtlLView, &LVItem); // 2項目(サイズ) LVItem.iSubItem = 1; if (Win32FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { wsprintf(LVItem.pszText, "%s", "<DIR>"); } else { wsprintf(LVItem.pszText, "%d", Win32FindData.nFileSizeHigh * MAXDWORD + Win32FindData.nFileSizeLow); } ListView_SetItem(hCtlLView, &LVItem); // 3項目(更新日時) LVItem.iSubItem = 2; // 協定世界時(UTC)を地域標準時に変更 FileTimeToLocalFileTime(&Win32FindData.ftLastWriteTime, &FileTime); FileTimeToSystemTime(&FileTime, &SystemTime); wsprintf(LVItem.pszText, "%d/%02d/%02d %d:%02d", SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour, SystemTime.wMinute); ListView_SetItem(hCtlLView, &LVItem); } while (FindNextFile(hFind, &Win32FindData)); FindClose(hFind); // ソート ListView_SortItemsEx(hCtlLView, LVWCmpProc, hCtlLView); return; } /* -------------------------------------------------------------------------- */ /* CALLBACK LVWCmpProc */ /* PARAM LPARAM lParam1 : 比較される 2 つのアイテム1 */ /* LPARAM lParam2 : 比較される 2 つのアイテム2 */ /* LPARAM lParamSort : アプリケーション定義(任意)*/ /* RETURN -1 : lParam1 のアイテムが lParam2 のアイテムより前 */ /* 0 : lParam1 のアイテムが lParam2 のアイテムと等しい */ /* 1 : lParam1 のアイテムが lParam2 のアイテムより後 */ /* lParamSortにはポインタ指定で出力先の指定が可能 */ /* -------------------------------------------------------------------------- */ int CALLBACK LVWCmpProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { /* 変数宣言部 */ HWND hCtlLView; char szItemText1[_MAX_PATH]; char szItemText2[_MAX_PATH]; char szAttribute1[256]; char szAttribute2[256]; /* 関数本体部 */ hCtlLView = (HWND) lParamSort; // パラム1情報取得 ListView_GetItemText(hCtlLView, lParam1, 0, szItemText1, sizeof(szItemText1)); ListView_GetItemText(hCtlLView, lParam1, 1, szAttribute1, sizeof(szAttribute1)); // パラム2情報取得 ListView_GetItemText(hCtlLView, lParam2, 0, szItemText2, sizeof(szItemText2)); ListView_GetItemText(hCtlLView, lParam2, 1, szAttribute2, sizeof(szAttribute2)); if (lstrcmp(szAttribute1, "<DIR>") == 0 && lstrcmp(szAttribute2, "<DIR>") == 0) { return lstrcmpi(szItemText1, szItemText2); } else if (lstrcmpi(szAttribute1, "<DIR>") == 0) { return -1; } else if (lstrcmpi(szAttribute2, "<DIR>") == 0) { return 1; } else { return lstrcmpi(szItemText1, szItemText2); } }
よくよく見ると、今までとは変数の位置が変わっているところがあります。
実は、こっそりとですが、Message.cpp の コントロールのハンドルを格納する変数が、OnCreate() 関数の中から、外に出ていています。
さらには、なにやら static という指定子がついています。
通常、関数の外で変数を宣言すると、その変数は、グローバル変数になります。
これは、先ほどやったヘッダファイルで変数宣言したことと、まったく同じ意味になります。
しかし、static 指定子をつけると、スコープが宣言したファイル内に限られるようになるのです。
※ static は、英語的には「静的」という意味ですが、なんのこっちゃわかりません。
ここでの意味は、一度確保すると「同一メモリ上にあり続ける(動かない = 静的)」という様に理解するとよいでしょう。
ちなみに普通の変数は、自動変数といったりします。
そもそも、今回は、OnCreate() 関数の中でも、外でも動作にまったく影響ありませんが、次回以降に問題があるのでここで先んじてやりました。
その場面が出てきたときにまた触れることにしましょう。
static 指定子が関数のプロトタイプ宣言についているものがあります。
Filelist.cpp() ファイルにある LVWCmpProc() 関数です。
実をいうと関数は、static 指定子をつけてもつけなくても、デフォルトで宣言したファイル内にスコープが限られます。
つまり、つけてもつけなくてもいいことになりますが、ファイル内でしか参照されないということを明示するためにつけました。
extern 指定子が関数のプロトタイプ宣言についているものがあります。
ヘッダファイルにプロトタイプ宣言した関数を、定義があるファイルでもう一度、プロトタイプ宣言しています。
extern 指定子は、「本物はどこかで定義している」という意味しかありません。
ここでは、グローバルの関数のファイルでの再宣言に extern を指定しています。
※ もちろん、再宣言しなくてもかまいませんし、extern を指定しなくてもかまいません。
今回も、機能的には新しいことはありませんが、今後のプログラム開発には必要なことをやりました。
本日のソース: FTPManager109(VC8).zip FTPManager109(BCC).zip
ファイルや関数を分割していくと、視認性は上がりますが、一つの処理の中で、いろんなところに処理が飛んで行って
プログラム全体の流れが、つかみにくくなることがあります。
このため、何かしら、関数の連携関係を示したものを作っておく必要があります。
一応、エクセルにまとめたものを、本日のソースの中に入れておきました。各関数には、リンクが張ってあります。
全体の流れが読みづらくなったときには、一度自分で書いてみてください。
世の中にはもっと良い関数の関連図があると思いますので、自分が理解しやすい関数関連図を作成してみてください。