Windows　コンソールバッファに直接書き込み(黒い画面に描画する原理)

## 初めに

### コンソールバッファとは
Windows上でC言語CUIのプログラムを書くと、printf などで文字が黒い画面（コンソール）に表示されます。
この時に内部で「コンソール画面バッファ」と呼ばれる領域に文字が書き込まれています。

普段はその存在を意識することはありませんが、今回はそのバッファの構造や動作原理を理解することを目的に、Windows APIを使ってバッファに直接文字を書き込むコードを紹介します。
(VGAのテキストモードに似ています。)

## 直接バッファに書き込む処理

### コード
```console_buffer.c
#include <Windows.h>
#include <stdio.h>

int main() {
    // 1. 新しいコンソール画面バッファを生成
    HANDLE hBuffer = CreateConsoleScreenBuffer(
        GENERIC_READ | GENERIC_WRITE,  // dwDesiredAccess: 権限、読み書きを許可
        FILE_SHARE_READ | FILE_SHARE_WRITE, // dwShareMode: 共有モード、他プロセスからの読み書きを許可
        NULL,                          // lpSecurityAttributes: セキュリティ属性、NULLでデフォルト
        CONSOLE_TEXTMODE_BUFFER,       // dwFlags: バッファの種類、CONSOLE_TEXTMODE_BUFFERはテキストモード
        NULL                           // lpScreenBufferData: 予約パラメータ、NULLで指定
    );

    // 生成失敗時の例外処理
    if (hBuffer == INVALID_HANDLE_VALUE) {
        printf("バッファの作成に失敗しました! エラー番号: %d\n", GetLastError());
        return 1;
    }

    // 2. 画面に表示
    SetConsoleActiveScreenBuffer(hBuffer);

    // 3. バッファの大きさを定義
    const int WIDTH = 80;   // 列数
    const int HEIGHT = 25;  // 行数
    CHAR_INFO buffer[HEIGHT][WIDTH]; // 文字と属性を格納する2次元配列

    // 4. バッファにデータを詰める
    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            // 文字を設定：ASCII文字（A〜Z）を循環
            buffer[y][x].Char.AsciiChar = 'A' + (x + y) % 26;

            // 属性を設定：行によって色を交互に変更
            buffer[y][x].Attributes = 
                (y % 2 == 0 ? 
                    FOREGROUND_RED | FOREGROUND_INTENSITY :  // 偶数行：明るい赤の前景色
                    FOREGROUND_GREEN | FOREGROUND_INTENSITY  // 奇数行：明るい緑の前景色
                ) | 
                BACKGROUND_BLUE;  // 共通の背景色として青を設定
        }
    }

    // 5. ユーザーバッファのデータをコンソールのバッファに書き込む
    COORD bufferSize = { WIDTH, HEIGHT };       // ユーザーバッファのサイズ（列数, 行数）
    COORD bufferCoord = { 0, 0 };               // ユーザーバッファの開始座標（0,0からコピー）
    SMALL_RECT writeRegion = { 
        0,          // Left: 書き込み先の左上X座標
        0,          // Top: 書き込み先の左上Y座標
        WIDTH - 1,  // Right: 書き込み先の右下X座標
        HEIGHT - 1  // Bottom: 書き込み先の右下Y座標
    };

    BOOL success = WriteConsoleOutput(
        hBuffer,            // hConsoleOutput: 書き込み先のバッファハンドル
        (CHAR_INFO*)buffer, // lpBuffer: ユーザーデータバッファへのポインタ
        bufferSize,         // dwBufferSize: ユーザーバッファの大きさ
        bufferCoord,        // dwBufferCoord: ユーザーバッファの開始座標
        &writeRegion        // lpWriteRegion: 書き込み先の座標範囲
    );

    if (!success) {
        printf("バッファへの書き込みに失敗しました! エラー番号: %d\n", GetLastError());
        return 1;
    }

    // 6. Enterが押されたら終了（ウィンドウがすぐ閉じないようにする）
    getchar();

    return 0;
}
```
## 処理の流れ

#### 1. 新しいコンソール画面バッファを作成
最初に、CreateConsoleScreenBuffer() を使って新しいコンソール画面バッファを作成します。このバッファは、画面に表示される文字を格納するために使用されます。これによって、初期のコンソール画面とは別のバッファが作成されます。

#### 2. 画面に表示
次に、SetConsoleActiveScreenBuffer() を使って、この作成した新しいバッファを表示に切り替えます。これで、コンソール画面にそのバッファが表示されるようになります。

#### 3. バッファの大きさを定義
次に、画面に表示するためのバッファのサイズを決定します。このコードでは、80列×25行の大きさのバッファを使用しています。

#### 4. バッファにデータを詰める(CHAR_INFO構造体の二次元配列)
バッファに表示する内容を準備します。文字（A〜Z）とその属性（色）を設定して、バッファに格納します。この例では、偶数行は赤色、奇数行は緑色に設定しています。

#### 5. バッファのデータをコンソールのバッファに書き込む
ここで、WriteConsoleOutput() を使って、準備した CHAR_INFO 配列の内容を作成したバッファに一括で書き込みます。この関数は、指定された領域にデータを転送するために使用されます。

### 実行結果
```bash
gcc -o console_buffer.exe console_buffer.c -Wall -lkernel32
console_buffer
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/6fe5da8b-d0e6-483a-ae21-bc350d6ae79d.png)

## 参考

[SetConsoleActiveScreenBuffer 関数](https://learn.microsoft.com/ja-jp/windows/console/setconsoleactivescreenbuffer)
[CHAR_INFO 構造体](https://learn.microsoft.com/ja-jp/windows/console/char-info-str)
[COORD 構造体](https://learn.microsoft.com/ja-jp/windows/console/coord-str)
[SMALL_RECT 構造体](https://learn.microsoft.com/ja-jp/windows/console/small-rect-str)
[WriteConsoleOutput関数](https://learn.microsoft.com/ja-jp/windows/console/writeconsoleoutput)

## 余談
今日上海に戻ってきました。
実家に古いパソコンがあったのでwindows7やlinuxのを入れて色々実験していましたが、流石に持ってこれないので又このwindows10で色々やって行こうかと思います。
中国で使っているパソコンにもlinuxをUSB起動という形で入れているのですがどうも日本語入力がうまく行かずQIITAに上げるのが難しいです。
実家の方はLinuxを入れた後成功してすぐ日本語入力が出来るようになったのですが。。。