EternalWindows
コンソール / コンソールへの入出力

Windowsアプリケーションからコンソールを表示するには、 呼び出し側プロセスにコンソールを割り当てる必要があります。 これには、AllocConsoleを呼び出します。

BOOL WINAPI AllocConsole(void);

この関数に引数はありません。

割り当てたコンソールを開放するには、FreeConsoleを呼び出します。 これにより、コンソールは削除されるようになります。

BOOL WINAPI FreeConsole(void);

この関数に引数はありません。

コンソールを割り当てれば、GetStdHandleで標準入出力のハンドルを取得できます。

HANDLE WINAPI GetStdHandle(
  DWORD nStdHandle
);

nStdHandleは、取得するハンドルのタイプを指定します。 STD_INPUT_HANDLEならば標準入力ハンドル、STD_OUTPUT_HANDLEならば標準出力ハンドル、 STD_ERROR_HANDLEは標準エラーハンドルが返ります。 既にAllocConsoleが呼び出されている場合は、標準入力ハンドルが入力バッファのハンドルとなり、 標準出力ハンドルがスクリーンバッファのハンドルになります。 取得したハンドルを閉じる必要はありません。

コンソールに文字列を書き込みたい場合は、WriteConsoleを呼び出します。

BOOL WINAPI WriteConsole(
  HANDLE hConsoleOutput,
  const VOID *lpBuffer,
  DWORD nNumberOfCharsToWrite,
  LPDWORD lpNumberOfCharsWritten,
  LPVOID lpReserved
);

hConsoleOutputは、スクリーンバッファのハンドルを指定します。 lpBufferは、書き込む文字列を格納したバッファを指定します。 nNumberOfCharsToWriteは、書き込む文字数を指定します。 lpNumberOfCharsWrittenは、実際に書き込まれた文字数を受け取る変数のアドレスを指定します。 lpReservedは、予約されているためNULLを指定します。

コンソールから文字列を読み取りたい場合は、ReadConsoleを呼び出します。

BOOL WINAPI ReadConsole(
  HANDLE hConsoleInput,
  LPVOID lpBuffer,
  DWORD nNumberOfCharsToRead,
  LPDWORD lpNumberOfCharsRead,
  LPVOID pInputControl
);

hConsoleInputは、入力バッファのハンドルを指定します。 lpBufferは、入力バッファから読み取った文字列を格納するバッファを指定します。 nNumberOfCharsToReadは、読み取りたい文字数を指定します。 lpNumberOfCharsReadは、実際に読み取った文字数を受け取る変数のアドレスを指定します。 pInputControlは、読み取りの終了を表す文字を格納したCONSOLE_READCONSOLE_CONTROL構造体のアドレスまたはNULLを指定します。 ANSIでコンパイルされているアプリケーションは、必ずNULLを指定します。

コンソールに書き込まれる文字は、設定されている属性によって前景色(テキストの色)や背景色が変化します。 属性を設定するには、SetConsoleTextAttributeを呼び出します。

BOOL WINAPI SetConsoleTextAttribute(
  HANDLE hConsoleOutput,
  WORD wAttributes
);

hConsoleOutputは、スクリーンバッファのハンドルを指定します。 wAttributesは、文字の前景色と背景色を表す定数を指定します。

今回のプログラムは、割り当てたコンソールに文字を書き込みます。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE                     hStdOutput, hStdInput;
	DWORD                      dwWriteByte, dwReadByte;
	TCHAR                      szBuf[256];
	CONSOLE_SCREEN_BUFFER_INFO screenBuffer;

	AllocConsole();

	hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	hStdInput = GetStdHandle(STD_INPUT_HANDLE);
	GetConsoleScreenBufferInfo(hStdOutput, &screenBuffer);
	
	lstrcpy(szBuf, TEXT("Hello World\n"));
	WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

	SetConsoleTextAttribute(hStdOutput, FOREGROUND_RED);
	WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);
	
	SetConsoleTextAttribute(hStdOutput, FOREGROUND_RED | FOREGROUND_INTENSITY);
	WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);
	
	SetConsoleTextAttribute(hStdOutput, BACKGROUND_BLUE);
	WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);
	
	SetConsoleTextAttribute(hStdOutput, BACKGROUND_BLUE | BACKGROUND_INTENSITY);
	WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);
	
	SetConsoleTextAttribute(hStdOutput, screenBuffer.wAttributes);
	lstrcpy(szBuf, TEXT("Enterキーを押すと終了します\n"));
	WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

	ReadConsole(hStdInput, szBuf, sizeof(szBuf) / sizeof(TCHAR), &dwReadByte, NULL);
	
	FreeConsole();

	return 0;
}

まず、AllocConsoleでコンソールを割り当てます。 次に、GetStdHandleを呼び出して、WriteConsoleに必要な標準出力ハンドルとReadConsoleに必要な標準入力ハンドルを取得します。 この標準出力ハンドルはスクリーンバッファのハンドルとして使用することができ、 標準入力ハンドルは入力バッファのハンドルとして使用することができます。 GetConsoleScreenBufferInfoを呼び出してCONSOLE_SCREEN_BUFFER_INFO構造体を初期化しているのは、 文字の属性のデフォルト値がwAttributesメンバに格納されているからです。 このメンバは、属性を元に戻す場合に使用します。 各WriteConsoleの呼び出しは、次のようになっています。

lstrcpy(szBuf, TEXT("Hello World\n"));
WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

SetConsoleTextAttribute(hStdOutput, FOREGROUND_RED);
WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

SetConsoleTextAttribute(hStdOutput, FOREGROUND_RED | FOREGROUND_INTENSITY);
WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

SetConsoleTextAttribute(hStdOutput, BACKGROUND_BLUE);
WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

SetConsoleTextAttribute(hStdOutput, BACKGROUND_BLUE | BACKGROUND_INTENSITY);
WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

SetConsoleTextAttribute(hStdOutput, screenBuffer.wAttributes);
lstrcpy(szBuf, TEXT("キーを押すと終了します\n"));
WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

1回目の呼び出しでは事前に属性を変更していないため、デフォルトの属性で文字が書き込まれます。 2回目の呼び出しではSetConsoleTextAttributeにFOREGROUND_REDを指定しているため、 文字が赤色で書き込まれることになります。 3回目の呼び出しではFOREGROUND_REDとFOREGROUND_INTENSITYを指定しているため、 テキストの赤色が強調されることになります。 4回目の呼び出しではBACKGROUND_BLUEを指定しているため、背景色が青になります。 5回目の呼び出しではBACKGROUND_BLUEとBACKGROUND_INTENSITYを指定しているため、 背景色の青色が強調されることになります。 最後の呼び出しではデフォルトの属性を指定しているため、 文字はデフォルトの属性で書き込まれることになります。

ReadConsoleは、Enterキーが押されるまで制御を返しませんから、 アプリケーションが直ぐに終了することを防ぐことができます。 入力された文字列は第2引数から参照できますが、この文字列の終端にはCRLF(改行コード)が格納されている点に注意してください。 取得した文字列を扱いたい場合は、CRLFに代わりにNULL文字を格納する必要があります。

今回取り上げた関数は、main関数から始まるコンソールアプリケーションでも使用しても問題ないのでしょうか。 結論から述べると問題はありませんが、WriteConsoleの呼び出しには注意しておく必要があります。 この関数は名前が示す通りコンソールへの書き込みを行う関数ですから、 標準出力としてコンソールが割り当てられていない場合は関数が失敗することになります。 つまり、この関数を通じてリダイレクトを行うことはできません。 今度は逆に、AllocConsoleを呼び出すアプリケーションからprintfを呼び出してもよいかですが、 これはCランタイムライブラリの実装に依存します。 通常は失敗すると思われますし、文字列をTCHAR型として扱うためにもWriteConsoleを呼び出したほうがよいでしょう。

CreateFileによる入出力

ファイルをオープンする場合などに使用されるCreateFileは、 コンソールへの入出力にも使用することができます。 この関数を呼び出せば、標準入出力のハンドルを取得できるため、 後はWriteFileまたはReadFileを呼び出せばよいことになります。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE hStdOutput, hStdInput;
	DWORD  dwWriteByte, dwReadByte;
	char   szBuf[256];

	AllocConsole();

	hStdOutput = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
	hStdInput = CreateFile(TEXT("CONIN$"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
	
	lstrcpyA(szBuf, "Enterキーを押すと終了します\n");
	WriteFile(hStdOutput, szBuf, lstrlenA(szBuf), &dwWriteByte, NULL);

	ReadFile(hStdInput, szBuf, sizeof(szBuf), &dwReadByte, NULL);
	
	FreeConsole();

	return 0;
}

標準出力のハンドルを取得する場合はCreateFileの第1引数にCONOUT$を指定し、 標準入力のハンドルを取得する場合は第1引数にCONIN$を指定します。 WriteFileやReadFileは、WriteConsoleやReadConsoleと違ってUNICODE文字列を扱えないため、 文字列はchar型で定義することになります。



戻る