Win32APIでコンソールをゴリゴリと

今まですべてC#と.Netに関係することを書いてきましたが、
今回はC/C++とWin32APIでのあれこれについて書きます。


ということで今回はコンソール用APIについてです。いきなりGUIについてではなくCUIについてです。
むしろWin32APIでGUIをあれこれする方法については頑張って探せばすでに記事として書いてあるものを見つけられると思います。
ですがCUIであれこれする方法というのはなかなかないのかな?ということで、CUI用のAPIについてです。


そもそも、
Q.つうかWin32APIにCUI用のAPIなんてあったの?
A.あります。


むしろ結構必要そうなものはそろっていて、がんばれば面白そうなものも作れると思います。
かなりがんばればですが。
そんなわけで、最近少しだけ頑張って簡単なCUIのアプリを作ったんでそのときに詰まったことを書きます。


んで、なにに詰まったかというとコンソールのフォントサイズを変える方法です。
現在のフォント情報を取得する場合はGetCurrentConsoleFontという関数があるのですが、
GetがあるのならSetもあるだろうとSetCurrentConsoleFontという名前の関数もあるだろうと探してみましたが、見当たらない。
ですがSetCurrentConsoleFontExという名前の関数があったので使ってみると、


「プロシージャエントリポイントSetCurrentConsoleFontExがダイナミックリンクライブラリKERNEL32.dllから見つかりませんでした。」


というメッセージが。
早い話がその関数がないということ。
んでよくよく調べてみるとサポートはWindows Vistaからということ。
僕が主に使っているマシンはXP。ダメじゃん。
VistaになってCUI用のAPIが追加されていることに驚きつつ、ダメならほかの方法を探すしかない。


なんだけど、本当にdllの中にないのかなーとkernel32.dllの中を見てみる。
やっぱSerCurrentConsoleFontExはない。
ん?SetConsoleFont?
ん?こんなのmsdnにあったっけ?
ん?ヘッダファイルにも書いてないぞ?


さっそく調べます。ググります。
結論から言うと、SerConsoleFontはいわゆる隠しAPIのようです。
んでもっと調べてみると、ズバリコンソールのフォントを変える方法を見つけたのでそれを書きます。


まず非公開関数はエントリポイントがわからないので自前で関数ポインタを取得します。
ちなみに今回使う非公開関数は全部で3つ。

  • BOOL WINAPI GetConsoleFontInfo(HANDLE, BOOL, DWORD, PCONSOLE_FONT_INFO)
  • DWORD WINAPI GetNumberOfConsoleFonts(VOID)
  • BOOL WINAPI SetConsoleFont(HANDLE, DWORD)
です。
んでエントリポイントを取得する方法。

HINSTANCE hLib = LoadLibrary(_T("KERNEL32.DLL"));
BOOL (WINAPI* GetConsoleFontInfo)(HANDLE,BOOL,DWORD,PCONSOLE_FONT_INFO) = 
	(BOOL(WINAPI*)(HANDLE,BOOL,DWORD,PCONSOLE_FONT_INFO))GetProcAddress(hLib, "GetConsoleFontInfo");
//残りの二つも同じように関数ポインタを取得

FreeLibrary(hLib);

ちなみにエラー処理は行ってないのでその辺は適当に。
あとやたらと長いのは関数の引数が多かったのと、GetProcAddress関数はFARPROCという型を返すのでキャストするために再び関数の型を書く必要があるためです。
なので関数ポインタの型をtypedefすることを推奨。


んでエントリポイントが取得できたら、コンソールのフォントサイズを変える方法です。

DWORD fontNum = GetNumberOfConsoleFonts();
CONSOLE_FONT_INFO* fonts = (CONSOLE_FONT_INFO*)malloc(sizeof(CONSOLE_FONT_INFO) * fontNum);

GetConsoleFontInfo(hConsoleScreen, FALSE, fontNum, fonts);

for(int index = 0;index < fontNum;++index)
{
	fonts[index].dwFontSize = GetConsoleFontSize(hConsoleScreen, fonts[index].nFont);
	if(fonts[index].dwFontSize.Y == fontHeight && fonts[index].dwFontSize.X == fontWidth)
	{
		SetConsoleFont(hConsoleScreen, fonts[index].nFont);
		break;
	}
}

free(fonts);

ちなみに変数の、

  • hConsoleScreen
  • fontHeight
  • fontWidth
は別の場所で宣言されて適当な値の入った変数とします。
ちなみにhConsoleScreenはコンソールスクリーンバッファのハンドルです。もしかするとGENERIC_WRITE アクセス権が必要かもしれません。


コードの流れをざっと説明すると、
まずGetNumberOfConsoleFontsで設定できるフォントの数を取得します。
そしてすべてのフォント情報を取得するのに必要な大きさのCONSOLE_FONT_INFOの配列を作ります。
次にGetConsoleFontInfoですべてのフォント情報を取得します。
そしてGetConsoleFontSizeで取得したフォント情報のうち、フォントのインデックス情報を使ってフォントサイズを取得。
取得したフォントサイズが要求するフォントサイズと等しければそれをSetConsoleFontで設定します。


という流れになっています。
今回設定したいフォントサイズが完全に等しければ設定するという書き方をしていますが、この方法はあまり推奨できません。
なぜかというとすべての大きさのフォントが用意されているかどうか分らないからです。
なので実際にフォントサイズを設定する場合はもう少し柔軟な書き方をしてください。


一応注釈ですが、
今回書いた記事は非公開関数についてで、情報のソースは公式のものではありません。
なので使う場合は十分に注意して検証を行ってからにしてください。
trial and errorです。