Win32 コンソール API

#はじめに
コマンドプロンプト画面(コンソール)は、デフォルトでは黒に白の文字が表示されます。しかし、Win32 コンソール API 関数を使うといろいろな色で文字を表示できます。

この API はハイレベル関数とローレベル関数からなっていて、後者を使用するとコンソール画面の細かな制御が可能です。

参照：MSDN [Using the Console](https://docs.microsoft.com/en-us/windows/console/using-the-console)

#ハイレベル I/O 関数とローレベル I/O 関数
ハイレベル I/O 関数は ReadFile / WriteFile または ReadConsole / WriteConsole です。これらのグループはほぼ同じ動作を行いますが、ReadFile / WriteFile はワイド文字では使用できません。よって、ReadConsole / WriteConsole を使ったほうがよいでしょう。

このため、ReadFile / WriteFile を使う場合は、プロジェクトのプロパティで「全般」・「文字セット」をマルチバイト文字にする必要があります。

ローレベル I/O 関数を使うと、行単位の入出力だけでなく、文字単位の入出力が行えます。カーソルを制御して文字列の表示位置を変えたり、スクロールを制御したりと、様々な細かな処理ができますが、それだけ多くの関数を使いこなす必要があります。

ハイレベルでは CRLF があると自動的に改行しますが、ローレベルではモードによります。

#文字列の表示と入力
ハイレベル関数を使って文字列の入出力を行うためには、次のような手順が必要です。

- GetStdHandle 関数を使って標準入出力ハンドルを取得する。
- 必要なら標準出力の属性を変更する。(その場合は、デフォルトの属性を取得して保存しておく)
- GetConsoleScreenBufferInfo でデフォルトの属性を取得できる。SetConsoleTextAttribute で属性を変更できる。
- WriteConsole 関数で文字列を出力する。ReadConsole 関数で文字列(行単位)を入力する。
- 最後に、標準出力の属性を元に戻す。


#サンプル
##色を変更した文字列の出力
表示文字列の属性を変更するには、SetConsoleTextAttribute の第二引数で色と明るさを指定します。背景色の変更も同時に行うことができます。

前景色は、 FOREGROUND_xxxx というシステム定義定数を使って指定します。xxxx の部分が色の指定で、BLACK や RED などが指定できます。(指定できるのは原色だけで、任意の色は指定できません)

背景色は、BACKGROUND_xxxx というシステム定義定数を使って指定します。xxxxx の部分(色の名前)は前景色と同じです。

明るさは FOREGROUND_INTENSITY や BACKGROUND_INTENSITY で指定します。明るさの指定は明るい・暗いの2種類だけです。

これらの定数を | 演算子で結合したものが SetConsoleTextAttribute の第二引数になります。下に例を示します。

FOREGROUND_GREEN | FOREGROUND_INTENSITY (前景色が明るい緑、背景色は黒)
FOREGROUND_WHITE | BACKGROUND_BLUE | BAKCGROUND_INTENSITY (前景色が暗い白(シルバー)、背景色は明るい青)
次のサンプルは、 Fig.1 のように色を変えた文字列を表示した後、デフォルトに戻して終了するだけのものです。

![ConsoleAPI1.png](https://qiita-image-store.s3.amazonaws.com/0/62802/e934b582-62c2-50c0-2cb6-f5dbae496ecd.png)

Fig.1 色を変更した文字列の出力

##ヘッダーファイル

```
// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

// TODO: プログラムに必要な追加ヘッダーをここで参照してください
#include <Windows.h>
```

##CPP ファイル

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

#include "stdafx.h"

/*

  WriteConsole() の代わりに WriteFile() を使う場合、Unicode でなくマルチバイト文字にしないと正しく表示しない。

*/
int main()
{
	HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);

	if (h == INVALID_HANDLE_VALUE)
	{
		printf_s("ErrorCode = %d\n", GetLastError());
		return -1;
	}

	LPCSTR str = "Write Console.\n";
	LPCSTR done = "Done.\n";
	DWORD len = -1;
	DWORD n = strlen(str);
	DWORD nd = strlen(done);

	// デフォルトの表示文字の色を保存
	CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
	if (!GetConsoleScreenBufferInfo(h, &consoleInfo))
	{
		printf("ErrorCode = %d\n", GetLastError());
		return -1;
	}

	// 表示文字の色を変更
	WORD wAttr = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_BLUE;

	// 文字列を表示する。
	if (!SetConsoleTextAttribute(h, wAttr))
	{
		printf_s("ErrorCode = %d\n", GetLastError());
		return -1;
	}

	WriteConsole(h, str, n, &len, NULL);

	// 表示文字の色を元に戻す。
	wAttr = consoleInfo.wAttributes;
	SetConsoleTextAttribute(h, wAttr);
	WriteConsole(h, done, nd, &len, NULL);

	getchar();
	return 0;
}
```

##コンソールから行を入力する
コンソールから文字列を行単位で入力するには、ReadConsole 関数 (マルチバイト文字モードなら ReadFile 関数も使用可能) を使用します。

次のサンプルは、文字列を入力し、それをメッセージボックスに表示します。

![ConsoleAPI2.png](https://qiita-image-store.s3.amazonaws.com/0/62802/2d0a066f-7887-0b07-a2bd-583e37679f49.png)
Fig.2 コンソールから行を入力する

##ヘッダーファイル

```
// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

// TODO: プログラムに必要な追加ヘッダーをここで参照してください
#include <Windows.h>
```

##CPP ファイル

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

#include "stdafx.h"
#define BUFFLEN 1024

/*
    コンソール入力
	==============
*/
int main()
{
	WCHAR buffer[BUFFLEN];
	DWORD len;
	LPCWSTR str = L"ReadConsole ..\n";

	// 標準入出力ハンドルを得る。
	HANDLE ho = GetStdHandle(STD_OUTPUT_HANDLE);
	HANDLE hi = GetStdHandle(STD_INPUT_HANDLE);

	if (ho == INVALID_HANDLE_VALUE || hi == INVALID_HANDLE_VALUE)
	{
		wprintf_s(L"ErrorCode = %d\n", GetLastError());
		return -1;
	}

	// str を表示する。

	WriteConsole(ho, str, wcslen(str), &len, NULL);

	// キーボード入力。
	ReadConsole(hi, buffer, BUFFLEN, &len, NULL);

	if (len < 3)
	{
		// Ctrl+C や Enter のみが押されたとき
		return -1;
	}

	// 行末に CRLF が入るので削除する。(ついでに行末に'\0'を追加)
	buffer[len - 1] = '\0';
	buffer[len - 2] = '\0';

	MessageBox(NULL, buffer, L"Result", MB_OK);
	return 0;
}
```

-