EternalWindows
コンソール / スクリーンバッファ

スクリーンバッファとは、実際の文字が書き込まれているバッファのことです。 コンソールに表示されているのはこのスクリーンバッファの内容であり、 コンソールが作成された段階では既定で1つのスクリーンバッファが作成されています。 通常、アプリケーションが出力を行うのはこの既定のスクリーンバッファですが、 独自のスクリーンバッファを作成してそこに出力を行うこともできるようになっています。 スクリーンバッファを作成するには、CreateConsoleScreenBufferを呼び出します。

HANDLE WINAPI CreateConsoleScreenBuffer(
  DWORD dwDesiredAccess,
  DWORD dwShareMode,
  const SECURITY_ATTRIBUTES *lpSecurityAttributes,
  DWORD dwFlags,
  LPVOID lpScreenBufferData
);

dwDesiredAccessは、スクリーンバッファへのアクセス権を指定します。 通常は、GENERIC_READとGENERIC_WRITEを指定します。 dwShareModeは、スクリーンバッファを共有するための定数を指定します。 共有しない場合は0を指定します。 lpSecurityAttributesは、SECURITY_ATTRIBUTES構造体のアドレスを指定します。 通常は、NULLを指定します。 dwFlagsは、CONSOLE_TEXTMODE_BUFFERを指定します。 lpScreenBufferDataは、NULLを指定します。 戻り値であるスクリーンバッファのハンドルはCloseHandleで閉じることになります。

作成したスクリーンバッファは、SetConsoleActiveScreenBufferでアクティブにすることができます。 アクティブにしたスクリーンバッファの内容は、実際にコンソールに表示されるようになります。

BOOL WINAPI SetConsoleActiveScreenBuffer(
  HANDLE hConsoleOutput
);

hConsoleOutputは、スクリーンバッファのハンドルを指定します。

今回のプログラムは、既定のスクリーンバッファに簡単なメニューを表示します。 aまたはbという文字を入力すると、既定のバッファが自作のバッファに切り替わってメニューの内容が表示されます。

#include <windows.h>

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

	AllocConsole();

	hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	hStdInput = GetStdHandle(STD_INPUT_HANDLE);

	lstrcpy(szBuf, TEXT("メニューを選択してください。\na : ユーザー名表示\nb : コンピュータ名表示\n"));
	WriteConsole(hStdOutput, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

	hBufferA = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	dwSize = sizeof(szBuf) / sizeof(DWORD);
	GetUserName(szBuf, &dwSize);
	WriteConsole(hBufferA, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);
	
	hBufferB = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	dwSize = sizeof(szBuf) / sizeof(DWORD);
	GetComputerName(szBuf, &dwSize);
	WriteConsole(hBufferB, szBuf, lstrlen(szBuf), &dwWriteByte, NULL);

	for (;;) {
		SetConsoleTitle(TEXT("既定のバッファ"));

		ReadConsole(hStdInput, szBuf, sizeof(szBuf) / sizeof(TCHAR), &dwReadByte, NULL);
		szBuf[dwReadByte - 2] = '\0';

		if (lstrcmp(szBuf, TEXT("a")) == 0) {
			SetConsoleActiveScreenBuffer(hBufferA);
			SetConsoleTitle(TEXT("バッファA"));
		}
		else if (lstrcmp(szBuf, TEXT("b")) == 0) {
			SetConsoleActiveScreenBuffer(hBufferB);
			SetConsoleTitle(TEXT("バッファB"));
		}
		else
			break;

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

		SetConsoleActiveScreenBuffer(hStdOutput);
	}

	CloseHandle(hBufferA);
	CloseHandle(hBufferB);
	FreeConsole();
	
	return 0;
}

このプログラムでは、スクリーンバッファを2つ作成しています。 1つは、ユーザー名が書き込まれたバッファAであり、 もう1つはコンピュータ名が書き込まれたバッファBです。 ReadConsoleで取得した文字列がaである場合は、SetConsoleActiveScreenBufferでバッファAに切り替わり、 取得した文字列がbである場合はバッファBに切り替わります。 バッファを切り替える際には、SetConsoleTitleを呼び出して現在のバッファの名前をコンソールのタイトルに表示します。 ReadConsoleで取得した文字列に明示的にNULL文字を追加しているのは、 この文字列の終端がNULL文字になっていないためです。 改行コードであるCRとLFを考慮しないために、インデックスを2つ戻します。

切り替わったバッファから既定のバッファに戻すには、Enterキーを入力します。 これによりReadConsoleが制御を返し、hStdOutputを引数としたSetConsoleActiveScreenBufferが呼ばれることになります。 実際にプログラムを実行すると分かることですが、 既定のバッファが表示されていない状態でReadConsoleを呼び出しても、 入力した文字は現在アクティブであるバッファには表示されません(ただし第2引数には格納されます)。 これを解決するには、ReadConsoleに現在アクティブであるバッファのハンドルを指定すればよいように思えますが、 この関数に指定できるのはスクリーンバッファではなく入力バッファのハンドルなので、 これを表すhStdInputを必ず指定しなければなりません。 スクリーンバッファとは異なり入力バッファは常に1つであり、 入力された文字は既定のバッファからでしか確認できません。

スクリーンバッファのサイズ

AllocConsoleで表示されるコンソールのサイズを変更するには、 ウインドウのサイズとスクリーンバッファのサイズの両方を調整することになります。 まず1つの決まりとして、スクリーンバッファのサイズはウインドウのサイズより小さくてはいけません。 ウインドウにはバッファの内容が表示されることになるわけですが、 もしバッファのサイズが小さければ、ウインドウに何も表示されない領域ができてしまうからです。 よって、バッファのサイズはウインドウのサイズよりも1つ以上大きくなければなりません。 そして次に重要なのは、サイズは文字数単位で指定するという点です。 たとえば、コンソールの横方向に100個の文字を表示したいのであれば、幅の値は100となります。 以上の点に注意しながら、次のコードを確認します。

SMALL_RECT rc = {0, 0, 100, 24};
COORD      coord;

coord.X = rc.Right + 1;
coord.Y = rc.Bottom + 1;
SetConsoleScreenBufferSize(hStdOutput, coord);

SetConsoleWindowInfo(hStdOutput, TRUE, &rc);

rcはウインドウのサイズを格納し、coordはバッファのサイズを格納することになります。 既に述べたように、バッファのサイズはウインドウのサイズよりも1つ以上大きくなければなりませんから、 幅と高さを表すメンバに+1をして代入しています。 SetConsoleScreenBufferSizeでバッファのサイズを設定したら、 次にSetConsoleWindowInfoでウインドウのサイズを設定します。 これらは順序逆になってはいけません。

実際に上記のコードを実行してみると分かりますが、 ウインドウにはスクロールバーが表示されません。 理由は、バッファが完全にウインドウ内に入りきっているからです。 バッファがウインドウサイズより2つ以上大きくなれば、 バッファの全てをウインドウに表示することができませんから、 スクロールバーが表示されることになります。 たとえば、rc.Bottom + 2のようにすると、垂直方向に1回スクロールできるようになります。



戻る