ホーム

C言語入門ウィンドウズ編

サンプルコードのダウンロード


準備 Visual Studio Express 2012 for Desktop (VS2012)と Windows SDK をダウンロードし、インストールする。 VS2012 を起動し、 [ツール]→[オプション]→[テキストエディター]→[C/C++]→[詳細]→ [参照/ナビゲーション]→[データベースの無効化]を[True]にする。 これで、プロジェクトのファイルサイズが小さくなる。
空のウィンドウを表示する
  1. VS2012 を起動する
  2. [ファイル]⇒[新しいプロジェクト]⇒[名前]にサンプル1と入力⇒[OK]⇒ [次へ]⇒[Security Development Lifecycle (SDL) チェック]のチェックを外す⇒完了 (チェックを入れたままにすると、_sのついたANSI C関数を使えと警告が出るようになる)
  3. [ビルド]⇒[ソリューションのビルド] ビルドとは、CPPファイルをコンパイルし、LIBファイルをリンクし、EXEファイルを作成する事だ。
  4. 下のウィンドウに ========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ========== と表示されたら、[デバッグ]⇒[デバッグ開始]
  5. サンプル1ウィンドウが表示されたら、そのウィンドウを終了する 下のウィンドウに プログラム 'サンプル1.exe' はコード 0 (0x0) で終了しました。 と表示されたら、成功
  6. Visual Studioを終了する
  7. [スタート]⇒[ドキュメント]⇒[Visual Studio 2012]⇒[Projects]⇒ [サンプル1]⇒[Debug]⇒サンプル1.exe をダブルクリックして動作を確認する
  8. 再びコードを編集したい場合は、サンプル1.slnかサンプル1.vcxprojをダブルクリックする
  9. EXEファイルを一般公開する場合は、[ビルド]⇒[構成マネージャー] [アクティブ ソリューション構成]で[Release]を選択⇒[閉じる] [ビルド]⇒[ソリューションのビルド]でリリースバージョンのEXEファイルを作成する。 デバッグバージョンよりもリリースバージョンの方がファイルサイズが小さいから処理速度も速い。

文字列の表示
  1. プロジェクト名サンプル2として新しいプロジェクトを作成する
  2. 以下の青文字コードを追加する case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: 描画コードをここに追加してください... TextOut(hdc, 0, 0, _T("Hello C"), 7); EndPaint(hWnd, &ps); break;
  3. [ビルド]⇒[ソリューションのビルド]
  4. [デバッグ]⇒[デバッグ開始]でウィンドウに文字が表示されるのを確認する
TextOutは、文字を表示するWin32 APIで、1つ目の引数は、デバイスコンテキスト、 2つ目は表示位置のX座標、3つ目はY座標、4つ目は表示文字列、5つ目は表示文字列の 文字数となる。 今回は、ユニコード文字セットでビルドしたから、半角文字も全角文字も普通にカウントできる。 TextOut(hdc, 0, 0, _T("ハロー"), 3); ANSI文字セットやマルチバイト文字セットでビルドする場合は、全角文字は1文字を2文字 としてカウントする。 TextOut(hdc, 0, 0, _T("ハロー"), 6); 数えるのが面倒な場合は、変数を使って、次のように記述することもできる。 case WM_PAINT: { hdc = BeginPaint(hWnd, &ps); // TODO: 描画コードをここに追加してください... TCHAR s[16] =_T("Hello C"); TextOut(hdc, 0, 0, s, _tcslen(s)); EndPaint(hWnd, &ps); } break; {}はネストで、これを入れないとcase文の中では、変数を明示的に初期化する事ができない。 ネストは、変数の有効範囲を決めるスコープでもあり、スコープ内で宣言された変数は、 制御がスコープから出るとメモリが解放される。 _tcslenは、文字列長を取得する関数だ。 これは、APIではなく、ANSI Cの命令だが、使い方はAPIと同じだ。 ANSI Cでは、文字列変数は、char型、文字列長取得関数は、strlenだが、どの文字セットでも 使えるようにするには、TCHAR型と_tcslen関数を使う。 Win32 APIは、MSDNの
Windows API リストで参照できる。 文字セットの変更は、[プロジェクト]⇒[サンプル1のプロパティ]⇒[構成プロパティ]⇒ [全般]⇒[文字セット]で行うが通常はそのままで良い。 これは、CPPファイルにそれぞれ以下のように記述したのと同じ意味だ。 [ANSI文字セット] なし [マルチバイト文字セット] #define _MBCS [ユニコード文字セット] #define _UNICODE #define UNICODE ANSI Cの文字列操作関数をTCHAR型の関数に変換する対応表は、以下の手順で探す。 [編集]⇒[検索と置換]⇒[フォルダーを指定して検索] [検索と置換]ダイアログでをクリック [検索フォルダーの選択]ダイアログで[フォルダーセット名]に例えば VC2012 include folder と入力し、[選択されたフォルダー]の[Visual C++ インクルード ディレクトリ]を選択して [Del]キーを押し、削除する。 [使用できるフォルダー]のテキストボックスに C:\ と入力し、その下のフォルダー選択ウィンドウで、 C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC まで移動し、[include]フォルダーを選択したら、を押して、[OK] [検索と置換]ダイアログで[検索する文字列]に _tcslen と入力し、[検索対象]に先ほど入力した[VC2012 include folder]を選択し、 [すべて検索]をクリックする。 以下のように検索結果が表示されるから、 C:\Program Files 〜 tchar.h(1044):#define _tcslen strlen をクリックすると、tchar.hが表示される。 tchar.hが表示されているウィンドウをクリックしてアクティブにした後、 [編集]⇒[検索と置換]⇒[クイック検索]で strlen を検索すると以下のように対応表が表示される #define _tcscat strcat #define _tcscat_s strcat_s #define _tcscpy strcpy #define _tcscpy_s strcpy_s #define _tcsdup _strdup #define _tcslen strlen #define _tcsnlen strnlen #define _tcsxfrm strxfrm #define _tcsxfrm_l _strxfrm_l #define _tcserror strerror #define _tcserror_s strerror_s #define __tcserror _strerror #define __tcserror_s _strerror_s MSDNに載っているAPI情報は、必ずしも正しいとは限らないため、エラーが出てコンパイルできない 場合は、同じ手順で、その関数を定義しているヘッダーファイルを検索し、引数を調べる必要がある。
メニューアイテムの追加 メニューから、メッセージボックスを表示するサンプル
  1. テキストエディタなら何でも良いのだが、Peggy Padをダウンロードする。 メモ帳やワードパッドでも良い。
  2. プロジェクト名サンプル3として新しいプロジェクトを作成する
  3. 以下の青文字コードを追加する case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 選択されたメニューの解析: switch (wmId) { case IDM_MESBOX: MessageBox(hWnd, _T("メッセージボックス表示テスト"), szTitle, MB_OK); break; case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break;
  4. 右ペインのソリューション エクスプローラーで、Resource.hをクリック
  5. 以下の青文字部分を追記した後、上書き保存し、 Resource.hタブの[X]をクリックして閉じる #define IDS_APP_TITLE 103 #define IDR_MAINFRAME 128 #define IDD_MY_DIALOG 102 #define IDD_ABOUTBOX 103 #define IDM_ABOUT 104 #define IDM_EXIT 105 #define IDM_MESBOX 106 #define IDI_MY 107 #define IDI_SMALL 108 #define IDC_MY 109
  6. C:\Users\user\Documents\Visual Studio 2012\Projects\サンプル3\サンプル3フォルダを開く
  7. サンプル3.rcをテキストエディタで開き、青文字部分を追記し、保存する // // メニュー // IDC_MY MENU BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "メッセージボックス表示(&M)", IDM_MESBOX MENUITEM SEPARATOR MENUITEM "アプリケーションの終了(&X)", IDM_EXIT END POPUP "ヘルプ(&H)" BEGIN MENUITEM "バージョン情報(&A)...", IDM_ABOUT END END
  8. Visual Studioに戻って、[ビルド]⇒[ソリューションのリビルド]
  9. リビルドが成功したら、[デバッグ]⇒[デバッグ開始]
  10. 表示されたウィンドウで[ファイル]⇒[メッセージボックス表示]で動作を確認する
MENUITEM SEPARATOR はセパレータで、書かなくても良い。 サンプル3.rcは、リソースファイルで、アイコンやメニュー、ダイアログボックスなどの 情報が記述されている。 Resource.hは、リソースファイルのヘッダーファイルで、アイコンやメニューアイテムなど のIDが記述されている。 このIDは、そのリソースファイルの同一ウィンドウやダイアログやコントロール内の 同一リソース内(この場合はメニューアイテム)で番号が被ってなかったら、何番でも良い。 ヘッダーファイルやリソースファイルなど、CPPファイル以外のファイルを変更した時 は、ビルドに失敗する事があり、その場合はリビルドする。
二重起動防止 既にソフトが起動している場合は、アイコンをダブルクリックしても、 新たにソフトを起動させないようにする。 他にCreateMutex関数を使用する方法もある。
  1. リソースファイルに記述されているウィンドウクラス名を変更する。 デフォルトでは、MY になっているが、他のウィンドウと自分のウィンドウを識別するため 変更する必要がある。 IDS_APP_TITLEは、ウィンドウタイトルだ。 // // ストリング テーブル // STRINGTABLE BEGIN IDS_APP_CLASS "サンプル4クラス" IDS_APP_TITLE "サンプル4" END
  2. Resource.hにも以下の青文字コードを追加する(番号は104以外なら何でも可) #define IDS_APP_CLASS 103 #define IDS_APP_TITLE 104
  3. サンプル4.cppに、以下の青文字コードを追加する // グローバル文字列を初期化しています。 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDS_APP_CLASS, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // 二重起動防止 HWND hWnd = FindWindow(szWindowClass, szTitle); if(hWnd){ return 0; } // アプリケーションの初期化を実行します: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; }

コマンドライン引数の取得 アイコンにドラッグ&ドロップされたファイル名を取得する。 ユニコードとその他の文字セットでは、方法が異なる。
  1. 以下の青文字コードを追加する #include "stdafx.h" #include <shellapi.h> int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: ここにコードを挿入してください。 int dwArgc; PTCHAR *pszArgv; #ifdef UNICODE pszArgv = CommandLineToArgvW(GetCommandLine(), &dwArgc); #else dwArgc = __argc; pszArgv = __argv; #endif int i; for(i=0; i<dwArgc; i++){ MessageBox(NULL, pszArgv[i], _T("コマンドライン引数"), MB_OK); } LocalFree(pszArgv);
  2. [ビルド]⇒[ソリューションのビルド]
  3. サンプル5.exeにいくつかファイルをDrag&Dropして、ファイル名が 表示されるのを確認する。 1つ目はプログラム名で、2つ目以降がファイル名になる。
このコードは、初心者には難解だから、理解する必要はない。 #include <shellapi.h> これは、CommandLineToArgvW関数を使用するために必要なヘッダーファイルをインクルードしている。 int dwArgc; これは、整数型変数の宣言だ。dwArgcという変数が作成される。 PTCHAR *pszArgv; これは、TCHAR型ポインタのポインタの宣言だ。TCHAR **pszArgv;と書いても同じだ。 配列で言えば、二次元配列に該当する。 #ifdef UNICODE @ #else A #endif これは、もし、ユニコード文字セットが設定されていたら、@を実行し、 それ以外だったらAを実行するというプリプロセッサ命令だ。 pszArgv = CommandLineToArgvW(GetCommandLine(), &dwArgc); GetCommandLine()は、コマンドライン引数を取得する関数だ。 &dwArgcは、変数dwArgcのアドレスで、CommandLineToArgvW()を実行すると、 コマンドライン引数の数が入る。 pszArgvには、コマンドライン引数の内容が入った配列の先頭アドレスが入る。 dwArgc = __argc; __argcには、コマンドライン引数の数が入っている。 pszArgv = __argv; __argvには、コマンドライン引数の内容が入った配列が入っている。 for(i=A; i<B; i++){ C } これは、Cの処理をAからB−1まで繰り返す命令だ。 LocalFree(pszArgv); CommandLineToArgvW()の戻り値は、使用後、LocalFree()でメモリを解放する仕様になっている。
ウィンドウへドラッグ&ドロップされたファイル名の取得
  1. 以下の青文字コードを追加する #include "stdafx.h" #include <shellapi.h>
  2. CreateWindowを以下のように置き換える hWnd = CreateWindowEx(WS_EX_ACCEPTFILES, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
  3. プロシージャに以下の青文字コードを追加する switch (message) { case WM_DROPFILES: { TCHAR fname[MAX_PATH]; int i,num; HDROP hDrop = (HDROP) wParam; num = DragQueryFile(hDrop, (UINT)-1, NULL, 0); for(i=0; i<num; i++){ DragQueryFile(hDrop, i, fname, sizeof(fname)); MessageBox(hWnd, fname, _T("ウィンドウにドラッグ&ドロップ されたファイル名の表示"), MB_OK); } DragFinish(hDrop); } break;
DragQueryFile関数などを使うのに、shellapi.h が必要だ。 CreateWindowEx()にWS_EX_ACCEPTFILESを指定すると、ウィンドウがファイルの ドラッグ&ドロップを許可するようになる。 case WM_DROPFILES: ウィンドウのWM_DROPFILESメッセージを処理する。 TCHAR fname[MAX_PATH]; TCHAR型の文字列の宣言をする。 _MAX_PATH は、stdlib.h で、260 に設定されている。 HDROP hDrop = (HDROP) wParam; wParamは、プロシージャの引数の1つで、ウィンドウメッセージによって異なる値が入る。 HDROP型変数を宣言し、wParamを代入する。 wParamの変数型はWPARAMだから、(HDROP)でキャストする。 変数をキャストすると、型が違っていても同じデータ型式、あるいは区切り方を変更するだけで 別の型に変換できる場合に一時的に型変更できる。 例えば、WORD型は、BYTE型の丁度2倍だから、WORDからBYTEには必ずキャストでき、 データによっては、BYTEからWORDもできる場合がある。 DragQueryFile()、DragFinish()については、各APIの使い方を参照。 sizeof(fname)は、sizeof(TCHAR)*MAX_PATHのことだ。
ライブラリ ファイルのリンク設定 ライブラリのリンク設定方法は、2種類ある。 1つは、ソースファイルに記述する方法で、もう1つは、Visual Studio に追加する方法だ。 例えば、comctl32.lib と wininet.lib をリンクする場合は、 [ソースファイルに記述する方法] #include <commctrl.h> #include <wininet.h> #pragma comment(lib, "comctl32.lib") #pragma comment(lib, "wininet.lib") [Visual Studio に追加する方法]
  1. プロジェクト名サンプル8として新しいプロジェクトを作成する
  2. CPPファイルに以下のコードを追加する #include <commctrl.h> #include <wininet.h>
  3. [プロジェクト]⇒[サンプル8のプロパティ]⇒[構成プロパティ]⇒ [リンカー]⇒[コマンドライン]⇒[追加オプション]に comctl32.lib wininet.lib と入力する⇒[OK]

リソース ファイルのビットマップを読み込む
  1. プロジェクト名サンプル9として新しいプロジェクトを作成する
  2. C:\Users\user\Documents\Visual Studio 2012\Projects\サンプル9\サンプル9 にsample.bmpを入れる
  3. サンプル9.rcに以下のコードを追加する ///////////////////////////////////////////////////////////////////////////// // // ビットマップ // IDB_SAMPLE BITMAP DISCARDABLE "sample.bmp"
  4. Resource.hに以下のコードを追加する(番号は何でも可) #define IDB_SAMPLE 110
  5. サンプル9.cppに、以下の青文字コードを追加する LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; static HBITMAP hBmp; switch (message) { case WM_CREATE: hBmp = (HBITMAP) LoadBitmap(hInst, MAKEINTRESOURCE(IDB_SAMPLE)); break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 選択されたメニューの解析: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: { hdc = BeginPaint(hWnd, &ps); // TODO: 描画コードをここに追加してください... BITMAP bmp; GetObject(hBmp, sizeof(BITMAP), (PSTR)&bmp); HDC hDCcp = CreateCompatibleDC(hdc); HBITMAP hBmpPre = (HBITMAP) SelectObject(hDCcp, hBmp); BitBlt(hdc, 0, 0, bmp.bmWidth, bmp.bmHeight, hDCcp, 0, 0, SRCCOPY); SelectObject(hDCcp, hBmpPre); DeleteDC(hDCcp); EndPaint(hWnd, &ps); } break; case WM_DESTROY: DeleteObject(hBmp); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
static HBITMAP hBmp; 変数は、制御がスコープから出るとメモリが解放されるが、変数宣言で型の前に static を付けると静的変数となり、スコープから出ても解放されない。 静的変数は、プログラム終了時にメモリが解放される。 case WM_CREATE: WM_CREATEメッセージは、ウィンドウ作成時に一度だけ処理されるため、 外部変数などの初期化に使われる事が多い。 LoadBitmap()は、リソースのビットマップのハンドルを取得する関数。 BITMAP bmp; GetObject(hBmp, sizeof(BITMAP), (PSTR)&bmp); ビットマップ情報を取得する。 HDC hDCcp = CreateCompatibleDC(hdc); HBITMAP hBmpPre = (HBITMAP) SelectObject(hDCcp, hBmp); BitBlt(hdc, 0, 0, bmp.bmWidth, bmp.bmHeight, hDCcp, 0, 0, SRCCOPY); SelectObject(hDCcp, hBmpPre); DeleteDC(hDCcp); ビットマップを表示するための定型処理。 CreateCompatibleDC()でメモリデバイスコンテキスト(MDC)を作成し、 SelectObject()でビットマップをそれに関連付けて、BitBlt()で描画し、 再び、SelectObject()で元のビットマップに戻してDeleteDC()でMDCを解放する。 WM_DESTROYは、ウィンドウ終了時に処理される。 DeleteObject(hBmp); ビットマップは、使用後、必ずメモリを解放する。