ホーム
C言語入門ウィンドウズ編
サンプルコードのダウンロード
準備
Visual Studio Express 2012 for Desktop (VS2012)と Windows SDK
をダウンロードし、インストールする。
VS2012 を起動し、
[ツール]→[オプション]→[テキストエディター]→[C/C++]→[詳細]→
[参照/ナビゲーション]→[データベースの無効化]を[True]にする。
これで、プロジェクトのファイルサイズが小さくなる。
空のウィンドウを表示する
- VS2012 を起動する
- [ファイル]⇒[新しいプロジェクト]⇒[名前]にサンプル1と入力⇒[OK]⇒
[次へ]⇒[Security Development Lifecycle (SDL) チェック]のチェックを外す⇒完了
(チェックを入れたままにすると、_sのついたANSI C関数を使えと警告が出るようになる)
- [ビルド]⇒[ソリューションのビルド]
ビルドとは、CPPファイルをコンパイルし、LIBファイルをリンクし、EXEファイルを作成する事だ。
- 下のウィンドウに
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========
と表示されたら、[デバッグ]⇒[デバッグ開始]
- サンプル1ウィンドウが表示されたら、そのウィンドウを終了する
下のウィンドウに
プログラム 'サンプル1.exe' はコード 0 (0x0) で終了しました。
と表示されたら、成功
- Visual Studioを終了する
- [スタート]⇒[ドキュメント]⇒[Visual Studio 2012]⇒[Projects]⇒
[サンプル1]⇒[Debug]⇒サンプル1.exe をダブルクリックして動作を確認する
- 再びコードを編集したい場合は、サンプル1.slnかサンプル1.vcxprojをダブルクリックする
- EXEファイルを一般公開する場合は、[ビルド]⇒[構成マネージャー]
[アクティブ ソリューション構成]で[Release]を選択⇒[閉じる]
[ビルド]⇒[ソリューションのビルド]でリリースバージョンのEXEファイルを作成する。
デバッグバージョンよりもリリースバージョンの方がファイルサイズが小さいから処理速度も速い。
文字列の表示
- プロジェクト名サンプル2として新しいプロジェクトを作成する
- 以下の青文字コードを追加する
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 描画コードをここに追加してください...
TextOut(hdc, 0, 0, _T("Hello C"), 7);
EndPaint(hWnd, &ps);
break;
- [ビルド]⇒[ソリューションのビルド]
- [デバッグ]⇒[デバッグ開始]でウィンドウに文字が表示されるのを確認する
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情報は、必ずしも正しいとは限らないため、エラーが出てコンパイルできない
場合は、同じ手順で、その関数を定義しているヘッダーファイルを検索し、引数を調べる必要がある。
メニューアイテムの追加
メニューから、メッセージボックスを表示するサンプル
- テキストエディタなら何でも良いのだが、Peggy Padをダウンロードする。
メモ帳やワードパッドでも良い。
- プロジェクト名サンプル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;
- 右ペインのソリューション エクスプローラーで、Resource.hをクリック
- 以下の青文字部分を追記した後、上書き保存し、
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
- C:\Users\user\Documents\Visual Studio 2012\Projects\サンプル3\サンプル3フォルダを開く
- サンプル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
- Visual Studioに戻って、[ビルド]⇒[ソリューションのリビルド]
- リビルドが成功したら、[デバッグ]⇒[デバッグ開始]
- 表示されたウィンドウで[ファイル]⇒[メッセージボックス表示]で動作を確認する
MENUITEM SEPARATOR はセパレータで、書かなくても良い。
サンプル3.rcは、リソースファイルで、アイコンやメニュー、ダイアログボックスなどの
情報が記述されている。
Resource.hは、リソースファイルのヘッダーファイルで、アイコンやメニューアイテムなど
のIDが記述されている。
このIDは、そのリソースファイルの同一ウィンドウやダイアログやコントロール内の
同一リソース内(この場合はメニューアイテム)で番号が被ってなかったら、何番でも良い。
ヘッダーファイルやリソースファイルなど、CPPファイル以外のファイルを変更した時
は、ビルドに失敗する事があり、その場合はリビルドする。
二重起動防止
既にソフトが起動している場合は、アイコンをダブルクリックしても、
新たにソフトを起動させないようにする。
他にCreateMutex関数を使用する方法もある。
- リソースファイルに記述されているウィンドウクラス名を変更する。
デフォルトでは、MY になっているが、他のウィンドウと自分のウィンドウを識別するため
変更する必要がある。
IDS_APP_TITLEは、ウィンドウタイトルだ。
//
// ストリング テーブル
//
STRINGTABLE
BEGIN
IDS_APP_CLASS "サンプル4クラス"
IDS_APP_TITLE "サンプル4"
END
- Resource.hにも以下の青文字コードを追加する(番号は104以外なら何でも可)
#define IDS_APP_CLASS 103
#define IDS_APP_TITLE 104
- サンプル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;
}
コマンドライン引数の取得
アイコンにドラッグ&ドロップされたファイル名を取得する。
ユニコードとその他の文字セットでは、方法が異なる。
- 以下の青文字コードを追加する
#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);
- [ビルド]⇒[ソリューションのビルド]
- サンプル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()でメモリを解放する仕様になっている。
ウィンドウへドラッグ&ドロップされたファイル名の取得
- 以下の青文字コードを追加する
#include "stdafx.h"
#include <shellapi.h>
- CreateWindowを以下のように置き換える
hWnd = CreateWindowEx(WS_EX_ACCEPTFILES, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
- プロシージャに以下の青文字コードを追加する
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 に追加する方法]
- プロジェクト名サンプル8として新しいプロジェクトを作成する
- CPPファイルに以下のコードを追加する
#include <commctrl.h>
#include <wininet.h>
- [プロジェクト]⇒[サンプル8のプロパティ]⇒[構成プロパティ]⇒
[リンカー]⇒[コマンドライン]⇒[追加オプション]に
comctl32.lib wininet.lib
と入力する⇒[OK]
リソース ファイルのビットマップを読み込む
- プロジェクト名サンプル9として新しいプロジェクトを作成する
- C:\Users\user\Documents\Visual Studio 2012\Projects\サンプル9\サンプル9
にsample.bmpを入れる
- サンプル9.rcに以下のコードを追加する
/////////////////////////////////////////////////////////////////////////////
//
// ビットマップ
//
IDB_SAMPLE BITMAP DISCARDABLE "sample.bmp"
- Resource.hに以下のコードを追加する(番号は何でも可)
#define IDB_SAMPLE 110
- サンプル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);
ビットマップは、使用後、必ずメモリを解放する。