Windows API/文字表示の命令

出典: フリー教科書『ウィキブックス(Wikibooks)』

文字表示の関数[編集]

基本[編集]

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

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

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

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

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

第2引数の「10」と第3引数の「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独自の型などを学ばなければならず、そっちが難しい。


TCHAR型とユーザ定義関数[編集]

TCHAR型でもchar型と同様の方法で、下記コードのようにユーザ定義関数の引数に使うことができます。(ただしwindowsAPI側の特殊事情として、HDC hdc などデバイスコンテクストも引数に加えないといけない。)

windowsAPIではあまり実用性の無い例だが、下記コードのようにTCHAR型もユーザ定義関数の引数にすることが可能である。

コード例

コード上部の「// グローバル変数」宣言部分には、

void testText(HDC hdc, int x, int y, TCHAR* str1) {
    TextOut(hdc, x, y, str1, lstrlen(str1));

}


case WM_PAINT: 側は、

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

            static TCHAR aisatu[60];
            lstrcpy(aisatu, TEXT("Hello2"));
            //TextOut(hdc, 10, 20, aisatu, lstrlen(aisatu));
            testText(hdc, 10, 20, aisatu);
実行結果
Hello2

となります。

MSDNのシンタックスの読みかた[編集]

たとえば、マイクロソフトの開発者むけサイト MSDN を見ると、TextOut関数について、

BOOL TextOutA(
  HDC    hdc,
  int    x,
  int    y,
  LPCSTR lpString,
  int    c
);

と、冒頭に、あかたも文法のような何か が書いてある。MSDN TextOutA function

なお、MSDN英語版では、このような冒頭に書かれているアレを「Syntax」と説明している。日本語に直訳すると、「統語論」、「公文法」、「文法」である(※ 参考文献: 『ジーニアス英和辞典 <改訂版> 2色刷』1997年3月1日 改訂版)。

これは、

戻り値の型    関数名   (引数)

が書かれている。

より詳細に説明すれば、

これから説明する関数を使ったときの戻り値の型    その関数名    (引数の順序と型と典型的な引数名)

である。

さて、このシンタックスに書かれている関数は TextOut関数ではなくTextOutA関数だが、これは単に文字コードのANSI版との互換性に対応したバージョンがTextOutA関数なだけである。TextOut関数を使用すれば、Visual Studioが内部でコンパイル時に文字コード関連の設定を適切な設定に置き換えてくれるので、初心者は気にせずTextOutで置き換えてよい。

msdnのサンプルコードでも、TextOut関数で説明している(2019年10月4日に閲覧)。

冒頭の BOOL が気になる人もいるだろうが、冒頭の型は、その関数を呼び出した時の戻り値の型である。BOOL型 という型がある。

TextOut関数にかぎらず、API関数を実際に使う場合、冒頭の型は宣言しない。つまり、

プログラミング中にTextOut関数を実際に呼び出して使用するための関数を記述したい場合は、単に

TextOutA(
  HDC    hdc,
  int    x,
  int    y,
  LPCSTR lpString,
  int    c
);

のような、冒頭のBOOL型を除去した書式で書く必要がある。

つまり、実際のプログラミング作業では、シンタックスにある冒頭の型を除去した書き方で、コードを記述することになる。

もし仮にシンタックスの記法のまま、冒頭の型をAPI関数の呼び出し時に宣言しても、単にコンパイル時にエラーになるだけだろう。


市販の解説書など、書籍によっては、シンタックスのことを「書式」などという用語で、こういう書き方をして説明しているが(※ 参考文献: 林晴彦『明快入門Visual C++ 2010 シニア編』ソフトバンククリエイティブ、2009年8月24日 初版 第1刷発行、24ページ。)、しかし、けっして実際のコードでは、こういうふうにBOOL型などを宣言してから使うわけではない。


またシンタックスでは、数行に分けて書かれているが、これは単に読みやすいようにマイクロソフト社が改行して紹介しているだけであるので、実際の使用では改行せずに1行にまとめて宣言しても良い。

実際、MSDNのサンプルコードでは、

TextOut(hdc,r.left,r.top,text, ARRAYSIZE(text));

のように、冒頭の型を除去したあとに1行でまとめて宣言している。


TextOut関数のほかにもDrawText関数など、さまざまなAPI関数があるが、それらのどれも、実際のコード中での使用宣言の際には、シンタックスの冒頭にある型を除去した形で宣言することになる。

文字列型[編集]

windowsAPIプログラミングの標準的な文字列型は TCHAR 型です。(char型は入門者には、おすすめできません。特殊な用途では char 型も使う。)

TCHR型の宣言は、 TCHAR moji[40] = TEXT("おはよう");

のようになります。

moji[40]の数値「40」の意味は、「文字列長が英数字の40文字ぶんまで」という意味です(日本語だと20文字ぶんになるかもしれません)。

TCHR 型を用いたサンプルコードなどについては、後述の節を参照してください。

いくつかの例[編集]

変数の値を画面表示[編集]

とにかく整数を表示したい場合[編集]

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

たとえば、タロウくんの去年の身長が約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 で代入先になる第一引数になる文字列変数(例では henkan )は TCHAR 型でなければなりません。(もし char型だと コンパイルでエラーになる。)

_stprintf_s 関数で代入する数値(例では sintyo )は、整数型でなければなりません。もし実数型(たとえば double 型)で数を定義してある場合には、型変換 (int) を使ってから代入します。

実数型の場合のコード例
double sintyo = 153 ; // double型の場合
static TCHAR henkan[50] ; 
_stprintf_s( henkan, 200, TEXT("%d"), (int) sintyo); // (int)は整数型への型変換である。
TextOut(hdc, 10, 20, henkan, lstrlen(henkan));

となります。


小数を表示したい場合[編集]

[編集]

とりあえず、値 0.38 くらいの数値の1未満の大きさの入っている変数 hensuu の中身の数値を画面表示する方法を考えましょう。


まず、とりあえず100倍して、それを型変換の (int) 関数で、整数型として型変換したあとに、他の整数型の変数 bufseisuu に入れます。(バッファ用の整数でbufseisuu のつもり)

そのあと、上記の章と同様に sprintf すれば、小数点の上位2ケタの表示がされます。

// 正常に動作するコード例
int hensuu = 0.38 ;
static TCHAR henkan[50] ; // 文字列を格納するための変数 henkan を準備

int bufseisuu = (int) 100 * hensuu ; // ここで型変換している

_stprintf_s(henkan, 200, TEXT("%d"), bufseisuu); 
TextOut(hdc, 450, 415, henkan, lstrlen(henkan));
駄目なコード[編集]

C言語の文法上は問題が無くても、しかし次のコードは現実にはVisual Studio のコンパイラが正常動作せず、まちがった実行結果になります。

もし

_stprintf_s(henkan, 200, TEXT("%d"), (int) 100 * hensuu)    (ダメな例)

と _stprintf_s 内部で同時に宣言しても、異常な動作になります。

つまり、


// ダメなコード例
int hensuu = 0.38 ;
static TCHAR henkan[50] ; // 文字列を格納するための変数 henkan を準備

_stprintf_s(henkan, 200, TEXT("%d"), (int) 100 * hensuu ); // ここで異常に読み取りをする
TextOut(hdc, 450, 415, henkan, lstrlen(henkan));

このコードは駄目なのです。


正常動作すれば 0 が入ってるハズなのに、実際には 2034859527 (一例)みたいな、なんかオカシな数が入ってしまいます。

文法上は、このような同時宣言もありそうですが、しかし実際にはウィンドウズVisual Studio のコンパイラ製作の都合などにより、このような同時宣言は異常動作になります。


_stprintf_s 関数にかぎらず、Visual Studio にもとから用意されているライブラリ関数の中で、他のライブラリを使うと、異常動作をする場合が、しばしば、あります。

なので、あまり、使用するライブラリ関数のなかには、別のライブラリ関数を入れないようにしましょう。

その他の方法[編集]

数値を文字列に変換する方法には、_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

文字の連結[編集]

2つ以上の文字を連結したい場合は、単に、C言語の sprintfs のTCHAR版である _stprintf_s を使えばいいだけです。

_stprintf_s(mojibuf, TEXT("%s %s"), TEXT("私の名は"), person_name);
TextOut(hdc, 10, 20, mojibuf, lstrlen(mojibuf));

のようになります。 mojibuf をTCHARであらかじめ宣言しておいて、さらにTCHAR型で宣言された person_name にあらかじめ「山田太郎」が代入されていれば、上記のようなコードを実行すれば「私の名前は 山田太郎」と表示されます。


標準C言語(無印C言語)からの変更点は、「 TEXT("%s %s") 」のように、「TEXT( )」 で囲まれるのを忘れないようにしてください。

文字列の多重配列[編集]

TCHAR型の配列は、下記のようになります。

TCHAR syouhinName[3][40] = { 
    TEXT("牛乳"),
    TEXT("オレンジジュース")
};

宣言の配列の見た目が[3][40]と二重配列なので、ついつい初心者は初期値を代入する際の記述も二次元配列にしがちですが、

しかし最後の [40] は文字列長を意味するので、syouhinName[3][40]初期値の書き方は1次元配列のようになります。このような規則は何もwindowsAPIプログラミングに限らず、一般のC言語プログラミングでも同様です(同様に、char型での多重配列の宣言時の最後の数値は文字数を意味する。)

上記例のように、TCHAR 宣言時の配列は2次元配列の書式であっても、TextOut など利用時には次元が一段階だけ下がるので書式は1次元配列でアクセスすることになるので、注意してください(初心者はよく間違えてエラーになり悩む)。