「Windows API」の版間の差分

出典: フリー教科書『ウィキブックス(Wikibooks)』
削除された内容 追加された内容
編集の要約なし
406 行 406 行
なお、Win32APIプログラミングでは、いわゆる「無限ループ」をプログラマーが新規に書くことは、通常、ありません。もし、無理やり書いても、動作が遅くなったり、バグの原因になります。
なお、Win32APIプログラミングでは、いわゆる「無限ループ」をプログラマーが新規に書くことは、通常、ありません。もし、無理やり書いても、動作が遅くなったり、バグの原因になります。


Visual Studio での Win32APIのテンプレート作成時に、アプリを終了させないための無限ループのようなものが、すでに書かれているので、通常は、そのテンプレートを流用します。
Visual Studio での Win32APIのテンプレート作成時に、アプリを勝手に終了させないための無限ループのようなものが、すでに書かれているので、通常は、そのテンプレートを流用します。


== 文字表示の命令 ==
== 文字表示の命令 ==

2019年9月4日 (水) 17:52時点における版

初期設定で選ぶことになる開発用テンプレート

そもそもの疑問として、Windows API でアプリ開発するには、 Visual Studio による各プロジェクトの新規作成時の初期設定で、一般に、下記のいずれかを選ぶ必要があるでしょう。

C++の

・「Windows デスクトップ」メニューにある「Windows デスクトップアプリケーション」
・「全般」メニューにあるにある「空のプロジェクト」
・「Windows デスクトップ」メニューにある「Windows デスクトップウィザード」

の3つです。

C# では、Visual Studio にはWindows API 開発環境が存在せず、不可能です。なので、かならずC++を選ぶことになります。


もし「Windows デスクトップウィザード」を選ぶと、親切にも、初期設定をどうするか色々とダイアログでWindowsから尋ねられます。ですが、初心者である我々にとっては、ダイアログからの質問の意味が不明です。

なので、これ以外の「Windows デスクトップアプリケーション」または「空のプロジェクト」を選んでください。


とりあえず、「Windows デスクトップアプリケーション」を選んで、起動してみましょう。

やたらと長いソースコードが出てきますが、気にせず、とりあえずコンパイルしてビルドすると、ウィンドウだけが表示されます。コンパイルとビルドをするには、メニューバーから選ぶこともできますが、F5ボタンを押せば、1~2回の操作でコンパイルとビルドができます。


さて、まだ、このアプリはウィンドウ表示だけの機能のアプリなので、ウィンドウ右上にあるボタン「X」を押す等して、アプリを終了してください。


ソースコードの20行目くらいに、「wWinMain」というのがあります。これが、C言語でいうMain関数に相当する場所です。(なお、このような場所を、「エントリポイント」と言います。)

次に、この「Windows デスクトップアプリケーション」で起動したソリューションを終了してみましょう。Visual Studio で、通常のソリューション終了作業と同様に、終了すれば済みます。

「空のプロジェクト」

次に、「空のプロジェクト」でプロジェクトを開始してみましょう。Visual Studio 2017 では、「空のプロジェクト」で開始したプロジェクトは、本当に、ほぼ空であり、なんと、入力用のファイルすら存在しません。

ソースファイル用フォルダなどの各種のフォルダは用意されてますが、存在するのはフォルダだけです。フォルダの中に、そのファイルは、まだ無い状態です。なので、まだソースコードの入力すら、できません。

なので、ソースファイル用フォルダにマウスカーソルを合わせた状態で、右クリックで出現するメニュー一覧から「追加」を選択し、cppファイル(C++用のファイル形式)の追加を命令して、cppファイルを追加してください。

これで、ようやく、ソースコードの入力ができます。


では、入力できるようになったソースファイルに、さっそく、次のように入力してみましょう。

例1
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	MessageBox(NULL, TEXT("Hello, World!"), TEXT("アプリ名"), MB_OK);
	return 0;
}

これで、メッセージボックスが表示されます。これ以外に、ファイルを追加しなくても、メッセージボックスを表示できます。このように、いくつかの命令文は、Windows自身が命令を判断してくれるので、コードを大幅に省略できます。

上述のように、かならずしも、Windows デスクトップアプリケーション」のように何百行(行数がいくつあるかは数えてない)もありそうな長い設定ファイルを書かなくても、たったの数行でもWindows APIによるアプリケーションを開発することも、上述のコードのように可能です。

なお、上記コード 例1 の WinMain を 「wWinMain」 にすると、現状ではエラーになってしまいます。まだ、wWinMainを使うための設定がソースコードに書かれてないからです。


さて、命令 MessageBox は、API開発 にかぎらず、C#などの初心者むけGUIプログラミングでも使われる命令です。特に設定を定義する必要もなく、1行の命令でメッセージボックスを呼び出せるので、開発ではデバッグなどにも役立ちます。

また、

#include <windows.h>

は、APIプログラミングにかぎらず、C言語規格の標準ライブラリでは用意されてないWindows用ライブラリを使う際に、インクルードする必要があります。

当然、Windows API は、ウィンドウズ独自の規格なので、C言語標準ライブラリには無いので、よって、Windows APIアプリの開発でも、 windows.h をインクルードする必要があります。


上記のコードは、実行すると、メッセージボックスが表示され、表示されるOKボタンを押すとアプリが終了します。

重要なこととして、WinMain 関数ブロックの最後まで来るか return が実行されると、アプリが終了します。

ウィンドウの表示

ウィンドウを表示するプログラムで、簡単なコード例をあげます。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND aaa = CreateWindow("STATIC",
		TEXT("窓タイトルと本文"),
		WS_OVERLAPPEDWINDOW,
		0, 100, 200, 300, NULL, NULL,
		hInstance, NULL
	);

	ShowWindow(aaa, SW_SHOW);

	MessageBox(NULL, TEXT("ボタンを押してください"), TEXT("ボックスタイトル"), MB_OK);

	return 0;
}

上述のコードにある「aaa」は、われわれが自由に命名できるハンドル名です。

CreateWindow関数によって、ウィンドウが作成されます。ウィンドウ作成時に、どのようなウィンドウを作成するのかを引数(カッコ内の部分、「STATIC」「TEXT」など)で指定しなければなりません。

この命令は、単に作成するだけであり、まだ表示しません。

作成したウィンドウを表示するには、さらに、ShowWindow 命令を用いて、ウィンドウの表示状態を表示オンにする必要があります。この ShowWindow 関数は、ウィンドウ表示の関数ではなく、ウィンドウの表示状態を指定する関数です。なので、上述のコードのように引数で SW_SHOW を指定して、ウィンドウを表示させます。第一引数の「aaa」の部分は、対象となるウィンドウを指定しています。

そして、こうしてウィンドウ表示をしても、WinMain関数が終わるまでの間しか、ウィンドウを表示できません。

なので、なんらかの方法で、プログラムの進行を止めないと、パソコンではプログラム実行後に高速でプログラムが終了するので、人間の目には何のウィンドウも見えません。

よって、上記のコード例では、MessageBox 命令によって、プログラムの進行を止めています。


さて、上記コード例の「"STATIC"」は、ウィンドウズで定められた意味をもつ宣言命令なので、上記の例では、変えてはいけません。命令なのに、なぜ「 " " 」で囲むのか疑問に感じるでしょうが、昔のマイクロソフトの人とかが、こう決めてしまったので、あきらめてください。

なお、この第一引数(上記コードでは「"STATIC"」)の部分で、そのウィンドウの大まかな性質を決定しており、これをウィンドウクラスといいます。

「クラス」といっても、C++でいうグループ化の機能をもつ「クラス」とは別物です。

たとえるなら、昔のコンピューターRPGゲーム(w:ウィザードリィ など)で、「戦士」「魔法使い」などの職業の種類のことを「クラス」と言ってました。このWindows API のウィンドウ「クラス」も、ウィンドウの機能の種類のことを言っています。


「0, 100, 200, 300, 」の部分は、表示位置のXY座標と(原点はパソコン画面の左上)、ウィンドウのヨコ幅とタテ幅です。


上記のコード例では、X=0、Y=100,の位置に、

ヨコ幅=200とタテ幅=300の、タテ長のウィンドウを作成しています。


Windows デスクトップアプリケーション

コード

「Windows デスクトップアプリケーション」を起動すると、次のようなコードが作成されます。(2018年8月4日にVisual Studio 2017で作成。)

// WindowsProject1.cpp: アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include "WindowsProject1.h"

#define MAX_LOADSTRING 100

// グローバル変数:
HINSTANCE hInst;                                // 現在のインターフェイス
WCHAR szTitle[MAX_LOADSTRING];                  // タイトル バーのテキスト
WCHAR szWindowClass[MAX_LOADSTRING];            // メイン ウィンドウ クラス名

// このコード モジュールに含まれる関数の宣言を転送します:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: ここにコードを挿入してください。

    // グローバル文字列を初期化しています。
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // アプリケーションの初期化を実行します:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));

    MSG msg;

    // メイン メッセージ ループ:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  関数: MyRegisterClass()
//
//  目的: ウィンドウ クラスを登録します。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   関数: InitInstance(HINSTANCE, int)
//
//   目的: インスタンス ハンドルを保存して、メイン ウィンドウを作成します。
//
//   コメント:
//
//        この関数で、グローバル変数でインスタンス ハンドルを保存し、
//        メイン プログラム ウィンドウを作成および表示します。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // グローバル変数にインスタンス処理を格納します。

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  関数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    メイン ウィンドウのメッセージを処理します。
//
//  WM_COMMAND  - アプリケーション メニューの処理
//  WM_PAINT    - メイン ウィンドウの描画
//  WM_DESTROY  - 中止メッセージを表示して戻る
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(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:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// バージョン情報ボックスのメッセージ ハンドラーです。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

なおコード中の「WindowsProject1」および大文字「WINDOWSPROJECT1」の部分にはファイル名が入っている。なので、ほかのファイル名の場合(たとえば「win32_test」)は、そのファイル名(「win32_test」および「WIN32_TEST」)に書き換わっている。

なので、「WindowsProject1」以外のファイル名の場合には、上記のコードをそのままコピーしてコンパイルを試しても、コンパイルエラーになる。

よく分からなければ、Visual Studioのウィザードで自動作成すれば済む。


「Windows デスクトップアプリケーション」の使い方

この起動時のテンプレートに無いコードは、自分でコードを書くことによって、追加していくことになります。

たとえば、キーボードからの文字入力を受けつける命令は、上記のテンプレートのコードには無いので、

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) ブロック内にある

case WM_PAINT: case WM_DESTROY: などの一連のブロックの前後に、

キーボードからの文字入力を受け付けられる case

case WM_KEYDOWN:
		switch (wParam)
		{

のように、キーボード入力受け付けを呼び出すコードを追加する必要があります。この名前「WM_KEYDOWN」は決まっていますので、これ以外に変更したら動作しません。


イベントドリブン方式

Win32APIのプログラムの実行順序は、ユーザーの操作によって変わります。

例えば、キーボード操作をされたときには、 case WM_KEYDOWN: のブロックが実行されます。

キーボード操作など、なんらかのイベントがされた時にだけプログラムの特定ブロックが実行されるので、イベントドリブン方式といいます。

Win32APIのプログラムの実行方式はイベントドリブン方式です。

なお、(グローバル変数の宣言などの例外を除くと、)プログラム中のなんらかの処理は、ほとんどが、なんらかのイベントと関連づけられます。


  • 中枢の関数は無い

もし、「操作に関わらず常に何度も実行させたい処理を書きたい」と思っても、そういう処理は書けません。仮にアナタが「C言語のコンソールアプリのmain関数のように、特定の関数に依存しない処理を書こう」と思っても、Win32APIで書くプログラムには、そういう中枢の関数は無いのです。

wWinMain 関数のブロックは、単に、Win32APIアプリの開始時点をあらわすブロックにすぎず、アプリ起動時に1回だけ実行されるプログラムにすぎません。

Win32APIでは、キーボード操作とは無関係にアプリ起動時に1回だけ実行する処理というのは書けますが、それだって、「アプリ起動」というイベントによって駆動されたプログラムです。


このように、Win32APIにおいて、原則的にプログラム内の個別の処理は、かならず、イベントと関連づけられます。(Win32APIでも、グローバル変数や、includeなど、冒頭文だけ、イベントと無関係に宣言できる。しかし、プログラムの具体的に動作は、必ず、発生条件のイベントと関連づけられる。)



  • イベント定義と、イベント発生後の処理は、通常、一緒に書かれる

キーボード操作イベントにかぎらず、基本的にwin32APIプログラムでは、たいてい、あるイベントの詳細の定義と、そのイベント発生後に行う処理を、一緒に書く場合になることが、ほとんどです。

たとえば、キーボード操作イベントの処理は、どのキーが押されたかというイベントと、キーが押された後に行う処理とを、一緒のブロックに書きます。


C言語のコンソールアプリの感覚だと、ついつい、イベントの詳細の定義と、イベント発生後に行う処理とを、べつべつのブロックに書こうと考えがちですが、しかしWin32APIでは、そういう書き方は通常は不可能です。


  • もし中枢のブロックが必要な場合、何かのイベントのブロックを流用する

もし、どうしても、プログラム全体を管理したいブロックが欲しい場合、アプリ起動中に何度も発生するイベントの処理を書くブロックを流用します。

キーボード操作イベント(WM_KETDOWN)や、時間経過イベント(WM_TIMER)など、何度でも発生できるイベントのブロックを流用して、そのブロック内に、目的の、プログラム管理的な処理を書くことになります。


このため、一見すると、そのイベントとは関係なさそうな、管理的な処理も、何らかのイベント発生後処理のブロックに書かれることも、あります。


このため、他人の書いたプログラムを読む際に、一見するとキーボード操作とは関係なさそうな処理でも、キーボード操作のブロックに処理が書かれている場合があります。

また、上記のような都合のため、書かれたプログラムではキーボード操作のブロックは長くなりがちなので、プログラムの全体像が分かりづらくなるので、もし複数人で共同開発する際には、あらかじめ仕様書などを作っておき、その仕様書でシステムの概要を説明しておき、全体像を把握しやすくすべきでしょう。


  • タイマー機能

なお、タイマー機能のある  case WM_TIMER: のブロックを追加したり SetTimer というコマンドを使い、「一定の秒数が経過したら○○を実行する」というタイマーのイベントを宣言することにより、キーボード操作とは無関係に自動的な処理を書くこともできます。しかし、これだって「○○秒の経過」というイベントによって駆動されるプログラムにすぎません。

そして、このタイマーのブロックにも、けっして「何秒、経過したか」という時間経過のプログラムだけでなく、さらに時間経過イベント後に必要になる処理も一緒に書くことになります。


  • 無限ループは書かない

なお、Win32APIプログラミングでは、いわゆる「無限ループ」をプログラマーが新規に書くことは、通常、ありません。もし、無理やり書いても、動作が遅くなったり、バグの原因になります。

Visual Studio での Win32APIのテンプレート作成時に、アプリを勝手に終了させないための無限ループのようなものが、すでに書かれているので、通常は、そのテンプレートを流用します。

文字表示の命令

基本

「Windows デスクトップアプリケーション」で自動作成されたコードをもとに説明しますので、そのコードを用意してください。(空のテンプレートで作ったコードでもウィンドウに文字や画像を描画することは可能。とりあえず本wikibooksでは、説明の単純化のために、先に「Windows デスクトップアプリケーション」で自動作成されたコードで説明する。)

Windows APIの文字表示で、いちばん簡単な命令は、 TextOut という命令です。

TextOut(hdc, 10, 30, TEXT("テスト"), 3);

のように使います。「hdc」は対象のウィンドウ名(「ハンドル名」などという)です。初期設定をウィザードで自動作成した場合に標準ではこの名前になってるので、本書では、特に断らない限り、この関数名を使う事とします。

上記の末尾の「3」は文字数です。これを2とすると「テス」と2文字しか表示されません。

10と20は、x座標が10、y座標が20、の位置という意味です。


ただし、このTextOut命令など、ウィンドウを描画するための命令を書ける位置は限られていて、

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

という関数(120行目あたりにある。)から始まるブロック中にある条件分岐「 case WM_PAINT: 」のブロック中で書きます。

「// TODO: HDC を使用する描画コードをここに追加してください...」とあるので、 その直後の行で書けばいいのです。

すでに、

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

            EndPaint(hWnd, &ps);
        }

とあるので、そこに TextOut 命令を挿入し、

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

			TextOut(hdc, 10, 30, TEXT("テスト"), 3);

            EndPaint(hWnd, &ps);
        }

のようになります。

なお、TextOut関数は改行を表現できません。改行したい場合は、DrawText関数に改行の機能があります。


TEXT() とは何か

TEXT("テスト")のように TEXT()とありますが、これは何かというと、単にWindows独自の関数(のようなもの)で、カッコ内にある二重引用符 「"」 と 「"」 で囲まれた文字列を、文字型だと認識される機能のものです。

これが必要な理由は、単に、最近のWindowsの開発者が、文法をそう決めたからです。

なので、2001年ぐらいの古いWin32APIの技術書を読むと、TEXT() が使われていない書籍もあります(例えば CQ出版社『Win32 API 完全マスター』山本潔 著、2001年3月1日初版、2006年8月1日第8版、41ページのコード「リスト2.2」「定番 hello のプログラム」など)。過去のWin32APIでは、このTEXT()という記法を採用してなかった時代もあります。


しかし、現在のWin32APIでは、文字列を宣言する際には TEXT() を使わないとコンパイルエラーになる仕様になっているので、TEXT() を使ってください。


ネット上にはTEXT() の存在意義について、「Unicodeがどうこう」とか「リテラルがどうこう」とか、もっともらしい理屈が転がってますが、しかし本当の理由は マイクロソフト社が単に、TEXT() という記法を採用したというだけに過ぎません。


たぶん、マイクロソフト社でVisual Studioコンパイラを作っている人にとっては、こういう記法があったほうが、コンパイラ内での二重引用符「"」 と 「"」 の管理の実装がラクなのでしょう。プログラム上では、文字列を使うたびに何度も「"」を使用しますので、この「"」が文字列の始めの「"」か、それとも終わりの「"」なのかをTEXT()という文法を使わずに判別できるコンパイラを製作することは、きっと、メンドウくさいのでしょう(原理的には不可能ではないが)。

いっぽう、TEXT()という記法があれば、TEXT(の後に1回目にくる「"」は必ず、文字列の開始をあらわす記号だと分かります。このため、コンパイラを作りやすくなります。


コールバック関数

さて、コールバック関数の冒頭の「WndProc」の部分は我々が変更できる関数名ですが、初期設定をウィザードで自動作成したときに、この名前になります。


また、このコールバック関数をウィンドウと関連づける必要があるため、そのための登録を、冒頭のクラス宣言で行う必要があったのですが、初期設定により既に自動登録されています。

どこで自動登録されているか言うと、60行目あたりを見れば、

//  目的: ウィンドウ クラスを登録します。

とありますが、

そのクラス登録の一連のコード中の70行目あたりに、

wcex.lpfnWndProc    = WndProc;

とあるはずです。これにより、コールバック関数 WndProc が、ウィンドウと関連づけられています。

文字表示での変数などの活用

TextOut(hdc, 10, 20, TEXT("Hello"), 5);

のように、毎回、字数を入力するのは、面倒である。

そこで、「lstrlen()」という、文字列の長さを返す命令を使う。これは、C言語のstrlen命令のWindows版である。

APIプログラミングでは、strlen命令ではなく、lstrlen命令を使わないといけないだろう。


また、毎回メッセージごとにTextOut命令を書き換えるのは非効率であるので、たとえば変数aisatuをあらかじめ用意して、実用的にはTextOut(hdc, 10, 20, aisatu, lstrlen(aisatu)); のように変数(このコードではaisatuが変数)を使うのが効率的である。

コード例

static TCHAR aisatu[60] ;
lstrcpy(aisatu, TEXT("Hello"));
TextOut(hdc, 10, 20, aisatu, lstrlen(aisatu));

のように書くのが実用的である。


TCHARは型であり、C言語でいう文字型 char のWindows版である。このほかにもWindowsにはWinodws版の文字型変数はあるが、とりあえず初心者はずTCHARを知っておけばいい。

C言語では、文字列を宣言するときには、配列として必要な要素数を決めて宣言しなければならないので、とりあえず60とした。

文字列については、ユニコードなど文字コードとの対応の問題があるが、とりあえずTCHARをつかっておけば、あまり問題が起きないだろう。


lstrcpyは、C言語でいう文字列のコピー命令strcpyのWindows版である。

このように、Windows独自の命令がいろいろとあるので、とりあえず初心者は、上記のコードをそのままコピーして、自分の使いたいように改造すれば効率的だろう。

TextOutする事じたいは難しくないのだが、それを実用的なコードにするためにWindows独自の型などを学ばなければならず、そっちが難しい。


変数の値を画面表示

ウィンドウ内に変数を文字描画できなければ、実用性がありません。

たとえば、タロウくんの去年の身長が約153センチだったとして、今年は約157センチだったとしましょう。(説明を単純化するため、小数部分は切り捨て or 丸めた。)

タロウくんの身長を変数 sintyo としましょう。


さて、TextOut 命令では、変数の値をそのまま表示することは不可能です。

よって、まずC言語の組み込み関数 sprintf という、これは数値型を文字列型に変換できる組み込み関数ですが、これのWindows版で _stprintf_s というのがありますので、Win32 API では _stprintf_s を使って、変数を文字列型に変換します。

しかし、この _stprintf_s 関数による文字列への変換は、整数型から文字列への変換しか、うまく変換できません。

浮動小数を文字列に変換すると、誤差が出てきます。たとえば、数値 152.7 を変換した場合、文字列 152.69997 みたいに変換される場合があります。 なので、整数以外の変換は、あまり、オススメできません。

もし、小数点2桁まで文字列に変換したい場合、例えば

あらかじめ、C言語の関数などを使って、100倍するなどしてから、小数点以下を切り捨てるとか、

あるいは、

小数部分と整数部分を別の変数にするなど、

工夫してください。

結局、身長の変数 sintyo を画面に表示するには・・・

int sintyo = 153 ; // この 153 を画面に表示したい
static TCHAR henkan[50] ; // 文字列を格納するための変数 henkan を準備
_stprintf_s( henkan, 200, TEXT("%d"), sintyo); // ここで153を文字列に置き換え、文字列変数 henkan に格納してる
TextOut(hdc, 10, 20, henkan, lstrlen(henkan));

のように、なります。


_stprintf_s の第2引数(上記コードでは200)は、文字バッファーのサイズです。


  • その他の方法

数値を文字列に変換する方法には、_stprintf_s の他にもあります。

_itoa_s や _itot_s などの型変換の組み込み関数を使う方法です。

しかし、これは、型を厳密に指定する必要があり、そのため、windows独自のさまざまな型や、ポインタ型などについても、プログラマは把握しておく必要があります。

なお、int整数値を TCHAR 形に変換するには、_itot_s を使います。

_itot_s(sintyo , henkan, 200, 10); // 第4引数の「10」は10進数という意味。
TextOut(hdc, 10, 20, henkan, lstrlen(henkan));

のように、なります。

_itot_s の第3引数(上記コードでは200)は、文字バッファーのサイズです。

詳しくは、マイクロソフトの公式サイトを参照してください。 msdn

図形を塗りつぶすには?

図形を塗りつぶしたい場合には、図形描画命令の直前に、下記のように描画のモードをブラシに設定します。

塗りつぶし専用の命令は、無いのです。

Windows API でいう「ブラシ」とは、図形の塗りつぶしの色を設定するためのツールのことです。画像作成ソフトで例えるなら、Windows API でいうブラシとは、画像作成ソフトの「バケツ」ツールに近いです。世間一般の画材でいう「ブラシ」とは、意味が違います。

			HBRUSH brasi_buhin_1;
			brasi_buhin_1 = CreateSolidBrush(RGB(255, 100, 100)); // ピンク色のブラシを作成
			SelectObject(hdc, brasi_buhin_1); // ウィンドウhdcと、さきほど作成したブラシを関連づけ
			Rectangle(hdc, 20, 30, 50, 70); // 図形の描画

上記の2段階のブラシ作成の作業は、CreateSolidBrush はブラシの色作成のためのアドレス確保を意味し、もう片方 SelectObject は、どのウィンドウでどのブラシ色を使うかを定義するための情報を保存するアドレスの確保を意味しています。

「Rectangle」は、単に、四角形を描くための命令です。これが塗りつぶしか、それとも輪郭線だけをペンで描くのかは、このRectangle命令だけでは指定できず、この命令が来る前の設定に依存します。


当然、これらの命令は、WM_PAINT ケースにある

「// TODO: HDC を使用する描画コードをここに追加してください...」

の下のブロックで書く必要があります。



上記コードを実際に描画すると、四角形の輪郭が黒色で描画されます。これは、ペンの設定が黒色になってるからです。

図形をピンク色の長方形だけにしたい場合、ペンの色もピンク色にします。

Rectangle 命令の前に、あらかじめ

			HPEN pen_buhin_1;
			pen_buhin_1 = CreatePen (PS_SOLID, 0, RGB(255, 100, 100));
			SelectObject(hdc, pen_buhin_1);

のように、ペンを指定します。 CreatePen 命令の第2引数の「0」は、ペン幅の追加の線幅です。「0」に指定しても、最低でも1ドットぶんの線幅はあります。

よって、今までの話をまとめると、

			HPEN pen_buhin_1; // ペンの設定用
			pen_buhin_1 = CreatePen (PS_SOLID, 0, RGB(255, 100, 100));
			SelectObject(hdc, pen_buhin_1);

			HBRUSH brasi_buhin_1; // ブラシの設定用
			brasi_buhin_1 = CreateSolidBrush(RGB(255, 100, 100));
			SelectObject(hdc, brasi_buhin_1);

			Rectangle(hdc, 20, 30, 50, 70); // 図形の描画

のように、なります。

複数個の描画を関数にしたい場合は

ソースの冒頭で関数を宣言する際、通常の関数と同じように、

void kansuu(HDC hdc){
    // ここに描画命令を書く
}

のように宣言する必要があります。引数の型と変数名が、単にハンドル型(HDC)とハンドル変数(hdc)になっただけです。通常のC言語の関数と同じです。


また、呼び出し側(親側)で、

kansuu(hdc);

のように、hdcの値を渡す必要があります(通常のC言語の関数と同じです)。

もし、上述の引数定義の手間を面倒くさがって、hdc の定義を static に変更すると、コードの他の部分の動作結果がバグります。なので、hdc は static に変更せず、ウィザードの初期設定のままにしておいてください。


当然、呼び出し側(親側)の関数のコードのある位置は、

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

// TODO: HDC を使用する描画コードをここに追加してください...

より下の部分です。


つまり、

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

            (中略)
            kansuu(hdc);
            (中略)

のように書かれます。

入力

キー入力

キー入力に反応させるには、単に、関数

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

の関数ブロック内に、下記のように case WM_KEYDOWN: を作成し、

	case WM_KEYDOWN:
		switch (wParam)
		{
		case 'Z':
			MessageBox(NULL, TEXT("Zが押されました。"), TEXT("キーテスト"), MB_OK);
			break;
		case 'X':
			MessageBox(NULL, TEXT("Xが押されました。"), TEXT("キーテスト"), MB_OK);
			break;
		}

のように、すればいい。上記のコードはZボタンとXボタンにだけ反応する。


文字入力

まず、入力欄を作らなければいけない。そのためには、「エディット ボックス」というのを使うと便利である。

「エディット ボックス」を呼び出せる関数は2つあり、CreateWindow() 関数と、CreateWindowEX() 関数である。

CreateWindow() 関数の使い方

  • 方法1

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

の関数ブロック内に、下記のように case WM_CREATE: を作成し、

CreateWindow() 関数を使う。

	case WM_CREATE:
		CreateWindow(TEXT("EDIT"), TEXT("ここに名前を入力"),
			WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT,
			10, 10, 200, 30, hWnd, (HMENU)1,((LPCREATESTRUCT)(lParam))->hInstance, NULL);

と書く。

((LPCREATESTRUCT)(lParam))->hInstance について

最近のVisual Studio では、これを単に hInst と置き換えて

	case WM_CREATE:
		CreateWindow(TEXT("EDIT"), TEXT("ここに名前を入力"),
			WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT,
			10, 10, 200, 30, hWnd, (HMENU)1, hInst, NULL);

と書いても、コンパイルできて、エディットボックスを実行できる。(Visual Studio 2017 で動作確認してある。)


この CreateWindow() 関数は、名前はあたかもウィンドウ作成の関数っぽくて、まぎらわしいが、ウィンドウだけでなく、ボタンなどのコントロールも作成できる。

そして、作成できるコントロールの中には、文字入力欄を簡単に作れるエディットボックスがあるので、それを使う。

第1引数の TEXT("EDIT") により、エディットボックスを使うことを宣言している。第2引数は、ボックス内に表示される文字を指定している。アプリ起動後にエディットボックス内に表示される この文字(上記の場合は「ここに名前を入力」)は、エディットボックスDeleteキーなどで消去できる。

アプリ起動後に、ボックス内をマウスクリックすれば、文字入力カーソルがボックス内に表示され、ボックス内の文字を編集できるようになる。


これによって、文字列の入力欄を作れるが、しかし、まだ入力欄を作っただけなので、入力してみてエンターを押しても、まだ何も起こらない。


よって、入力後の処理をコードに追記していく必要がある。

  • 方法2

または、ウィザードで作ったテンプレートの100行目あたりに

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)

というのがあり、その下に、

   if (!hWnd)
   {
      return FALSE;
   }

というのがあるので、その下でCreateWindowEX() 関数を使う。


   if (!hWnd)
   {
      return FALSE;
   }

   CreateWindow(TEXT("EDIT"), TEXT("ここに名前を入力"),
	   WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT,
	   0, 0, 150, 30, hWnd, (HMENU)1, hInst, NULL);

なお、この位置は、lParam の効果が及ばない位置なのでエラーになので、それを使わないように引数を書く必要がある。

CreateWindowEX() 関数の使い方

ウィザードで作ったテンプレートの100行目あたりに

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)

というのがあり、その下に、

   if (!hWnd)
   {
      return FALSE;
   }

というのがあるので、その下でCreateWindowEX() 関数を使う。

最終的に、次のようなコードになる。

   if (!hWnd)
   {
      return FALSE;
   }

   CreateWindowEx(0, TEXT("EDIT"), TEXT("ここに名前を入力"), 
	   WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT,
	   10, 10, 200, 30, hWnd, (HMENU)1, hInstance, NULL);

なお、この位置は、lParam の効果が及ばない位置なのでエラーになので、それを使わないように引数を書く必要がある。


確定ボタンの配置

エディットボックスに文字を打ち込んだら、文字列を確定したことをOSに知らせる処理が必要です。(さらにその後、確定した文字列を変数に代入する処理が必要になります。)


まず、どうやって文字列を確定したことを宣言するか、考える必要があります。

実用的には、決定ボタンなどのコントロールをアプリウィンドウ上に配置するのが、簡単でしょう。

ボタンコントロールもまた、CreateWindow()関数で作成できます。

		CreateWindow(
			TEXT("BUTTON"), TEXT("決定"),
			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
			110, 70, 100, 50, hWnd, NULL, hInst, NULL);

なおBS_PUSHBUTTON,の次の

110, 70, 100, 50,  

について、これは

ボタンの左上のx座標110, y座標70, 幅 100, 高さ 50

という意味です。


後の処理のために、このボタンを呼び出すためのIDをつける必要があります。

後ろから3番目の引数(hWnd, NULL, hInst のNULL の部分)がIDです。IDは数字でなければ、なりません。

通常、 define マクロで定義するのが慣例です。

例えば、

		CreateWindow(
			TEXT("BUTTON"), TEXT("決定"),
			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
			110, 70, 100, 50, hWnd, (HMENU)BUTTON1_ID, hInst, NULL);

と書いたら、 さらにソースファイルの冒頭の、defineマクロのある箇所に、

#define BUTTON1_ID 10

のように定義して、BUTTON1_ID に任意の数字を与えます。

なお HMENU は型名であり、ハンドルの識別番号をあらわす型です。

文字列の取得

上述のように、ウィンドウ上に、ボタンコントロールで作成した決定ボタンを配置したとしましょう。

まず、ボタンコントロールが押されたときの処理は、

    case WM_COMMAND:

の段落に書きます。この段落は、既にウィザードによって自動作成されています。

既に、

case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 選択されたメニューの解析:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
		break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;

というようなコードが自動生成されているので、


case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 選択されたメニューの解析:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;

            case BUTTON1_ID: // このcaseが追加された
                MessageBox(hWnd, TEXT("決定ボタンが押されました。"), TEXT("テスト"), MB_OK); 
                break;

            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;

のように、case ボタンID を追加するだけです。

さて、われわれが行いたいのは、エディットボックスの文字列の取得ですから、上述の MessageBox() の代わりにエディットボックスの文字列を取得するコードを記述する必要があります。

まず、エディットボックスに名前をつける必要があります。どのエディットボックスから文字を読みこむか指定するためです。

とりあえず editbox1 という名前をつけたとしましょう。

エディットボックスに名前をつけるには、エディットボックスの作成時に、下記のように代入する必要があります。

	case WM_CREATE:
		editbox1 = CreateWindow(TEXT("EDIT"), TEXT("ここに名前を入力"), // この行の冒頭が変わった。
			WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT,
			10, 10, 200, 40, hWnd, (HMENU)1, hInst, NULL);

		CreateWindow(
			TEXT("BUTTON"), TEXT("決定"),
			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
			110, 70, 100, 50, hWnd, (HMENU)BUTTON_ID1, hInst, NULL);


エディットボックスにある文字列を取得するには、GetWindowText() 関数を使います。

GetWindowText() 関数の第1引数で、どのコントロールから取得するかを指定するので、そこに 先ほど名づけたエディットボックスの名前を入れます。また、取得した変数を代入するための変数として、事前に文字列型を定義しておきます。下記のコードでは、エディットボックスから取得した文字列を、 変数 kakunou に格納します。

			case BUTTON1_ID:
				GetWindowText(editbox1, kakunou, 100);
				
				InvalidateRect(hWnd, NULL, FALSE);
				UpdateWindow(hWnd);

これだけだとエラーになってしまいますので、次のようなコードをソース冒頭のグローバル領域に追加します。

static HWND editbox1;
TCHAR kakunou[256];


文字を取得しただけでは、まだ画面の描画内容の更新は行われません。

ボタンコントロールを押した場合にかぎらず、一般にwin32APIで画面の更新をするには、さらに下記の InvalidateRect 命令と UpdateWindow 命令が必要です。

				InvalidateRect(hWnd, NULL, TRUE);
				UpdateWindow(hWnd);

は決まり文句ですので、鵜呑みにしてください。


また、 case WM_PAINT: に、描画内容を書く必要があります。

たとえば case WM_PAINT: に、

				TextOut(hdc, 200, 200, kakunou, lstrlen(kakunou));

のコードを追加したりします。


上述のこれらのコードを記述すれば、エディットボックスの入力結果を、ウィンドウに表示できます。


ファイルの入出力

Win32APIには、CreateFile関数などのファイル入出力の専用コマンドが用意されている。

しかし、Win32APIでも標準C言語のfopen関数などのC言語規格の関数も使える。


初心者にとっては、fopenなどC言語規格の関数を使うほうが簡単だろう。


ウィザードで自動作成したコードに、ファイル入出力のコードを追記するのが、簡単である。

(※ 白紙状態のコードから作る方法では、当ページの巻末の参考文献にあるネット上にある手本のコードが、そのコードのバージョンが古いなどの理由で、現在のWindowsでは使えないので、自分で調べなおす必要が生じる。2018年9月1日に確認。)


CreateFile

さて、CreateFile関数を使う場合、たとえば、「test.txt」というファイルを作成したい場合なら、

// TODO: ここにコードを挿入してください。

の下に、下記のように書き加える。

// TODO: ここにコードを挿入してください。

	HANDLE hFile;

	hFile = CreateFile(
		TEXT("test.txt"), GENERIC_READ, 0, NULL,
		CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL
	);

	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(
			NULL, TEXT("ファイルを作成できませんでした。"),
			TEXT("エラー"), MB_OK
		);
		return 1;
	}

	CloseHandle(hFile);

ウィザードの自動作成したコードに上記コードを追加したコードを、コンパイルする。

成功すれば、特にメッセージは表示されないが、パソコンでソースコードのあるフォルダに、「test.txt」というファイルが加わっているのが分かる。

( たとえばファイル名がWindowsProject1 なら WindowsProject1.cpp などがソースコードであり、これのあるフォルダに、一緒に「test.txt」というファイルが加わっている。

この「test.txt」は拡張子のとおり、通常のテキストファイルであるので、test.txt のアイコンをダブルクリックするなどして開くと、文字を書き足したり保存することもできる。


上述のコードのように、Win32API用の、無印C言語のファイル入出力関数ではない(つまりfopenなどではない)、ファイル入出力の関数がある。


C言語のfopenなども、stdio.h をインクルードすればWin32APIでも使用可能であるが、しかし、TCHAR型の扱いの際に、特殊な操作が必要になってしまう。

なので、Win32APIでは、なるべくCreateFileなどのウィンドウズAPI用のファイル関数を使うほうが簡単である。


なお、Win32 API にかぎらず、

一般に、ファイルの入出力のプログラミングでは、

まず、ファイルを操作する際、

ファイル操作用の変数を用意する。
ファイルを読み書きする前に、ファイルを開く必要がある。
読み書きの作業が終わったら、安全のためファイルを閉じる。閉じられた状態のファイルは、そのままでは読み書きできない。

などの特徴がある。


また、ファイルを作成したりオープンした直後に、本当にその作業が成功したかどうかの確認のためのコードを入れるのが一般的である。


なぜなら、ファイル入出力の関数は、パソコンのハードディスクを書き換えるので、エラーが起きた際、大きな被害に発展しかねない。


なので、そのような誤作動にそなえて、ファイルのオープンなどが上手くいったかを確認する必要がある。

そして、もしファイルのオープンに失敗した場合、終了する処理をコードに書くのが一般的である。(上記のコードでは、 return 1 で関数を終了させている。)


ファイルのオープンとクローズ

CreateFile() 関数の機能は、その名とは違い、この機能はファイルを作成またはオープン(開く)する機能である。

やや不正確な説明をするが、わかりやすく言うと CreateFile() 関数が作成しているものは、ファイル操作用ハンドルに必要な設定情報である。より正確に言うと、ハンドル(上記のコードでは hFile )がファイルを操作できるようにするために必要な諸設定を、 CreateFile() 関数は作成している。

設定情報を作成しただけでは、どのハンドルでファイルを扱うかは指定されてないので、なので

	hFile = CreateFile(
		TEXT("test.txt"), GENERIC_READ, 0, NULL,
		CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL
	);

のように、ハンドル (上記の例では hFile )を指定する必要がある。

C言語の入門書では、「変数は、入れ物(いれもの)である」と習ったのを思い出そう。ハンドル変数( hFile )に、ファイル操作用の設定情報( createFile で作成された設定情報)を入れる必要がある。


そして、このファイル操作用ハンドルの設定の作成をする際に、目的のファイルのオープンもされる仕様になっている。(CreateFileの第一引数で、"test.txt" のように、対象のファイルを指定しているので、どのファイルを開く必要があるかを、この関数を使う命令の実行時点でOSが既に知っている。)


すでに存在するファイルに読み書きをしたい場合にも、 CreateFile() を使って対象のファイルをオープンする必要がある。


CreateFile() は、引数での指定によって、ファイル作成やファイルオープンなどの動作を指定できる。

CreateFile() によって作成されたファイルハンドルは、自動的に開かれる。

なので、目的のファイル処理が終わったら、

CloseHandle() でファイルハンドルを閉じる必要がある。


関数の内容

CreateFile(
		TEXT("test.txt"), GENERIC_READ, 0, NULL,
		CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL
	);

とあるが、いくつもある引数の意味は、それぞれ

CreateFile(
		操作対象のファイル名, アクセスモード , 共有モード, セキュリティ記述子,
		作成方法, ファイル属性, テンプレートファイルのハンドル
	);

を設定している。

正確な情報は、マイクロソフトmsdn [1] を参照せよ。

アクセスモード

読み取りモードでアクセスする場合には、

アクセスモードを

GENERIC_READ

にする。

書き込みモードでアクセスする場合には、

アクセスモードを

GENERIC_WRITE

にする。

共有モードとは?

「共有モード」とは何かというと、上記のプログラムがファイルを開いている間、後続のアクセス命令がアクセスできるかどうかを指定することです。

共有モードを「0」にすると、ファイルハンドルを閉じてからでないと、ほかのプログラムがアクセスできません。

FILE_SHARE_READ に設定すると、ハンドルを閉じなくても、後続アクセスは読み込みだけ可能になります。

同様に、 FILE_SHARE_WRITE に設定すると、ハンドルを閉じなくても、後続アクセスは書き込みだけ可能になります。

セキュリティ記述子

難しいので、説明を省略。詳しくは マイクロソフトmsdn [2] を参照せよ。

NULL にすれば、特に指定しないことになります。


「作成方法」とは

名前こそ「作成方法」ですが(マイクロソフトMSDNがそう呼んでいる)、これは、ファイルが存在するとき(もしくは存在しないとき)、どう処理するかの指定です。

CREATE_NEW なら、

対象のファイルと同名のファイルが存在すれば、この関数CreateFile()は失敗する。(何も作成しない。)
対象のファイルと同名のファイルが存在すれば、この関数CreateFile()が実行される。(つまり、ファイルが作成される。)

という意味です。

他にも指定子はいろいろとありますが、すべて説明すると長くなってしまうので、詳しくは MSDN などを参考にしてください。

ファイル属性

ファイルの属性を、「読み込み専用」とか、いろいろと設定できるのですが、初心者にはめんどくさいので、何も設定しないでおきましょう。

FILE_ATTRIBUTE_NORMAL を指定すれば、何も設定しないことになります。

テンプレートファイルのハンドル

難しいので説明を省略。

よく分からなければ、 NULL を指定しとけばいい。

詳しくは、 マイクロソフト MSDN を参照せよ。

ファイルの読み書き

総論

まず、Win32APIには、ReadFile関数などのファイル入出力の専用コマンドが用意されている。

しかし、Win32APIでも標準C言語のfgets関数などのC言語規格の関数も使える。しかし、ワイド文字を扱う場合、fgetwsになることに注意しよう。


初心者にとっては、fgetwsなどC言語の関数を使うほうが簡単だろう。


さて、Win32 API でテキストファイルなどの読み書きをする際、文字コードに気をつけなければならない。

文字コードは、マイクロソフトの各ソフトウェアの標準設定に合わせておくのが、簡単である。

アクセサリ「メモ帳」などテキストエディタの文字コードは、WindowsではANSIが標準の文字コードなので、メモ帳で作成したテキストファイルの文字コードをANSIに合わせておくのが簡単である。テキストファイルの文字コードを「ANSI」に設定するには、「名前をつけて保存」の際に「文字コード」の欄を「ANSI」にすれば良い。

なお、マイクロソフト社の言う「ANSI」とは、けっして本物のANSI(いわゆるASCII(アスキー)コード)ではなく、マクロソフト社コード番号 CP932 という、ASCIIを拡張して日本JIS漢字規格など各国の規格などを追加したマイクロソフト社独自の文字コードのことである。日本では Shift-JIS として知られる。


Visual Studio の文字コードは当面のあいだ、いじらないのが安全であるが、もし、すでにいじってしまった場合、UnicodeがVisual Studioの標準設定なので、Unicodeにすればいい。

また、各アプリの文字コードと、Visual Stuido の文字コードとは、上述のように、別々のコードの場合があるので、混同しないように気をつけること。

※ ただし、2018年12月、将来的にWindows 10 ではWindowsのテキストエディタ「メモ帳」の文字コードが、従来のANSIからUTF-8に文字コードが統一される見通しになった[1]。おそらく Visual Studio の仕様も将来、この仕様変更に合わせて標準設定の文字コードがANSIからUTF-8に変更されるので、プログラマーは気をつける必要がある。


読み取ったファイルの文章表示

方法

日本語の場合、ワイド文字型 wchar_t を使わないと、TextOutするのが困難になる。 char型でも不可能ではないかもしれないが、かなり面倒くさいので、ワイド文字型で説明する。

例として

読み取りたいファイル
これはテストです。
this is a test.

としよう。

まず、このテキストファイルの文字コードを「ANSI」に設定する。「名前をつけて保存」で、「文字コード」の欄を「ANSI」にすれば良い。


いっぽう、Win32APIのプログラミングでは、まず、読み取った文字列を格納するための変数を、グローバル変数で、

wchar_t str1[100];

のように wchar_t 型の文字配列を宣言する。


グローバル変数にする理由は、ファイル読み取りの関数と、画面表示などの別の関数で、読み取り結果を共有するので、グローバル変数で宣言する必要があるからである。


そして次に、プログラム中のファイル読み取りを実行したい箇所で、

 // TODO: ここにコードを挿入してください。
	setlocale(LC_ALL, "");  // ロケールの設定。空白で良い。
	FILE *fp;
	
	fopen_s(&fp, "test.txt", "r");

	fgetws(str1, 100, fp); // fgetsではなく fgetws
	
	fclose(fp);

        // 行末に改行文字がつくので、それを除去
	wchar_t *p = wcschr(str1, '\n');
	if (p) *p = '\0';  // '\n' を除去


などとして、str1 に読み取ったワイド文字を入れる。


wcschr とは、 msdn に書いてあるが、ワイド型の文字列中で、ある文字(引数で指定する)が初めて出現する場所を見つけて、その位置を返す関数である。

標準C言語に、strchr という、文字を検索して位置を返す関数がある。wcschr は strchr のワイド文字版である。


上記のプログラム例では、改行文字\nを見つけて、それをヌル文字\0に置き変えている。ファイル検索の処理プログラムでよく使う定石なテクニックである。


あとは、例えば読み取った文字列を表示したいなら、単に WM_PAINT で

 // TODO: HDC を使用する描画コードをここに追加してください...
            
	TextOut(hdc, 30, 80, str1, lstrlen(str1));

のように、すれば済む。


実行結果

座標(30,80)の位置に

「これはテストです。」

(カギ括弧なしの状態で)と表示される。


背景事情

なぜ、このような wchar_t で宣言するという仕組みになってるかというと、

もちろん第一の理由は「マイクロソフト社がこういう風に作ってしまったから」であるが(マイクロソフト社の独自規格の問題なので)、

別の理由もある。


理由1

「Windowsではシステム内部の(ユーザーからは直接操作できない部分の)文字コードでは(自称)『Unicode』が使われており、

一方、Windowsのシステム外部の(ユーザーの操作する)メモ長などのOS付属アクセサリやMSエクセルなどオフィスソフトの標準の文字コードでは(自称)『ANSI』コードが標準として使われているから」

という事情がある。(マイクロソフト社の独自規格は、時代によって変更される可能性があるので(マイクロソフト社の都合により)、変更されても対応できるように、技術的な背景事情を把握しておく必要がある。)


そして、TextOutなどWin32APIのいくつかの関数では、Windowsのシステム内部では出力文字の管理は Unicode で行っているようである。

しかし、Windowsにおけるchar型は、どうやらASCIIコードや自称「ANSI」(Shift-JIS)を前提としてコンパイラ内部で処理しているようである。

このため、Windowsシステム内部の「Unicode」とWindowsシステム外部の「ANSI」という、これら2種類の文字コードの調整のため wchar_型 で宣言する手間が生じるようである。


理由2

もうひとつの理由として、英数字以外の文字の、バイト数の扱いがある。

本来、char型が1バイト型だとしたら、日本語文字などの2バイト文字は入らないハズであるが、しかし過去の経緯により、コマンドプロンプト画面用プログラムではchar型にも漢字を格納できてしまうし、printfなどで日本語もコマンドプロンプト画面に表示できてしまう。


しかし、TextOutなどWin32API専用の関数では、より厳密に型の管理をしているようであり、コマンドプロンプト画面とは違うようである。

TextOutなどWin32API専用の関数では、char型でなく wchar_t で宣言しないと、日本語を格納しづらい。

結論

上述のような様々な理由が合わさり、win32APIでは、日本語文字の格納は、wchar_t 型で行うのが望ましいだろう。

画像の操作

原理

Win32APIには、SetPixel 関数という、座標と(点の)色を指定して、ウィンドウ内に点を描画する関数がある。

理論的には、これさえあれば、ウィンドウ内なら、どんな描画もできるので、自分で画像ファイルのファイル形式を設計することすら可能である。

なぜなら、ビットマップやJPEGなどの画像の集まりは、(中学校などで習うように)所詮は画素という点の集まりであるから。


しかし、すでに存在する画像ファイル規格などを再度設計するのは時間の無駄だから、通常は、既存の規格であるビットマップ形式画像などを活用する。


なお、点のほか、線を描ける LineTo 関数や、円を描ける Ellipse 関数、長方形を描ける Rectangle 関数など、いくつかの基本図形には描画の関数が用意されている。

線などの簡単な図形を表示するだけなら、わざわざビットマップで作成する必要は無い。


画像操作の概要

Win32APIで扱える画像は、標準設定のままでは ビットマップ画像(.bmp)しか扱えない。

PNG画像などを扱いたい場合には、GDI+をインクルードする必要がある。


単に

#include <gdiplus.h>

と冒頭に書くだけで、GDI+をインクルードできる。

また、ビットマップには、画像の透明化の機能がないため、初等的な方法ではWin32APIで画像を透明化できない。例えば、矢印型マウスカーソル画像のように、矢印画像の外側を透明化するような機能は、ビットマップには無い。

どうしてもWin32APIで画像の透明化を行いたい場合には、MaskBlt 関数などを利用して、透明化の機能を自作する必要が生じてしまう。

しかし、透明化の自作はメンドウだし、せっかく自作しても互換性が悪いから、普通はそういうことはせず、GDI+のインクルードなどをして、PNG画像など透明化の機能をもった画像形式を活用するのが一般的である。


Win32 API での画像表示に必要な実用知識

画像の編集の宣言

まず、GDI+をインクルードしてない状態での操作を説明する。当然、ビットマップ形式しか、画像ファイルは扱えない。

なお、表示したいビットマップ画像そのものの作成は、アクセサリ「ペイント」で先にすませておく必要がある。


さて、Win32 API では LoadImage という命令で、ビットマップ画像の読み込みができる。

しかし、事前に、画像操作用のハンドルをHBITMAP 型を使って宣言しないといけない。「ハンドル」とは、なにやら正体が不明だが、ウィンドウプログラミングでファイル操作をするときとかに、宣言する必要になることになる、何かである。

マイクロソフト社の人が、こういう仕様で作ってしまったので、ユーザーは従うしかない。

おそらく、きっとファイル操作などに必要なメモリ領域やハードディスク領域の確保でも、たぶん宣言しているのだろう。画像のハンドルなら、きっと画像ファイルの操作に必要な領域の確保でもしているのだろう。

とにかくコードは、

	HBITMAP hbmp;

	// 画像をファイルから読み込む
	hbmp = (HBITMAP)LoadImage(NULL, TEXT("yomikomittai_gazou.bmp"), IMAGE_BITMAP, 0, 0,
		LR_LOADFROMFILE | LR_CREATEDIBSECTION);

のようになる。このような順序で、宣言することになる。上のコードでは、「yomikomittai_gazou」という名前のビットマップ画像を読み込む。


さて、Win32 API では、ウィンドウ内での画像の表示と、画像データの作成は、別の命令になる。当然だが、ウィンドウ外部には画像を表示できない。

ウィンドウ内への画像の表示には、BitBlt 関数を使う。

さらに、表示先の画面と、画像編集用の仮想的な画面とが、Win32APIでは概念が分かれている。


Win32APIでも表示先の画面上で、直接、画像を編集することも可能であるが、説明の簡単のため、本書では、画像編集用の仮想画面で編集をしてから、その編集結果を、表示先の画面に転送するという仕組みで説明する。


まず、表示先の画面は、通常の設定では「hdc」という名前である。

TextOut(hdc, 10, 30, TEXT("テスト"), 3);

のように、通常の設定では「hdc」という名前の画面が、ウィンドウ内の画像表示用の画面である。


さて、画像作成用の仮想的な画面は、自分で宣言する必要がある。

例えば、もし画像作成用の画面の名前を hbackDC にするなら、下記のように宣言する。

	HDC hbackDC = CreateCompatibleDC(hdc);

こうすることで、 hdc と互換性のある、画像作成用の領域が確保される。

なお、大文字のHDCは型名である。小文字の hdc とは意味が違うので、区別するように。


そして、 hbackDC で作成した画像を表示するには、例えば

	BitBlt(hdc, 0, 0, 700, 500, hbackDC, 0, 0, SRCCOPY);

のように、hdcに転写しなければならない。


実は、いちいち hbackDCなどの画像作成用の画面を作成しなくとも、直接的にhdcの上で作成・表示もまとめて出きる。

しかし、hdc上で直接作成した場合には、表示する画像の数が多いと、画像の貼り付け中の画面もそのまま表示されてしまう場合がある。

もし、画像の貼り付け中の過程を隠したい場合などには、hbacKDCのように作成画面を別途用意する必要がある。

後片付けが必要

使い終わった画像操作用ハンドルは、

        DeleteObject(hbmp);

で削除しなければならない。(そういう仕様になっている。削除しないと、一例として、画像描画の関数ブロックを2回目以降に利用するときに、前回の画像描画で使用した画像がメモリ内に残ってたりして、画像がバグったりする。)


しかし、使用中のほかの画面や画像と関連づけてると削除できない(そういう仕様)ので、

        hbmp = NULL; 
        DeleteObject(hbmp);

のように、NULLを入れてから削除すると、確実に削除できる。

使い終わった、画像作成用の画面を削除する場合は、DeleteDC を使う。これまた、使用中のほかのハンドルと関連づけていると削除できない(そういう仕様)ので、

        SelectObject(hbackDC, NULL);
        DeleteDC(hbackDC);

のように、NULLと関連づけさせてから、削除することで、確実に削除できる。

参考リンク

Win32 API を入門者むけに全般的に解説したサイトは、日本では少ない。

下記のサイトで、Win32 API を入門者むけに全般的に解説している。いっぽう、書籍などを読んでも、市販の Win32 API の書籍の多くは入門者むけでなく、さらに、その書籍すらも種類が少なく、出版年度も古いものが多い。よって初心者は、当面はwebサイトで勉強するのが合理的である。

  • Wisdomソフト
  • 標準 Windows API (Wisdomソフト の旧版だが、上記の新版がコンテンツ不足で、Windows APIの画像関係の機能の使い方が、新版では説明が無しなので、旧版を合わせ読む必要がある。)
  • Win32API講座
  • Win32API 紹介している画像関係・音声関係の機能の利用法のソースコードが実用的であり、また初心者むけである。

また、Visual Studio での初期設定「Windows デスクトップアプリケーション」が生成するコード自身も、ときどき参考にしよう。

  1. ^ インプレス(企業名)、窓の杜(サイト名)、『「メモ帳」に多数の改善、BOMなしUTF-8がデフォルト保存形式に ~「Windows 10 19H1」』