Windows API を活用した自作 printf の実装とコンソールバッファ操作

## 始めに
本記事の目的は、Windows上で標準のの`printf`機能をできるだけ原始的に再現することです。具体的には、Windowsコンソールアプリケーションにおいて、`printf`に近い動作を自作関数で実装し、内部ではバッファを活用する方法を紹介します。

尚、本実装では日本語の出力には対応していません。これは、コンソール上での日本語処理には文字コードやバッファ管理の複雑さが伴い、実装の難易度が大幅に上がるためです。

バッファの操作や`printf`のフォーマット指定子の詳しい説明については、以下に挙げた以前の記事をご参照下さい。

## 前に投稿した記事
[Windows APIを用いてprintf関数を再現](https://qiita.com/earthen94/items/11a0e65a54f27991560b)
[Windows　コンソールバッファに直接書き込み(黒い画面に描画する原理)](https://qiita.com/earthen94/items/b950d823a1de18ce5920)

## コード

<details>
<summary>main.c</summary>

```main.c
// 動作確認用
#include "mystdio.h"
#include "console_buffer.h"
#include <stdio.h>

int main() {
	init_console_buffer();
    int printfRtn = my_printf("num=%d str=%s num2=%d str2=%s hex=%x hex2=%x\n",3,"aiueo",678,"kakikukeko",17,15);
    my_printf("myPrintfRtn = %d\n",printfRtn);
	return 0;
}
```
</details>

<details>
<summary>console_buffer.h</summary>

```console_buffer.h
#ifndef CONSOLE_BUFFER_H
#define CONSOLE_BUFFER_H

#include <windows.h>

void init_console_buffer();
int console_put_char(char c);

#endif

```
</details>

<details>
<summary>console_buffer.c</summary>

```console_buffer.c
#include "console_buffer.h"

// 標準出力ハンドル（コンソール）を格納する変数
static HANDLE hConsole;

// カーソルの現在位置を管理する変数
static COORD cursor;

// コンソールのカーソル位置を初期化
void init_console_buffer() {
    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);   // 標準出力ハンドルを取得
    CONSOLE_SCREEN_BUFFER_INFO csbi;              // コンソールの画面バッファ情報を格納するための構造体
    GetConsoleScreenBufferInfo(hConsole, &csbi);  // 現在のバッファ情報を取得
    cursor = csbi.dwCursorPosition;               // 現在のカーソル位置を保存
}

// 一文字をコンソールに出力する関数
int console_put_char(char c) {
    DWORD written;

    // 改行文字の場合、Xを0にリセットしてYを+1（次の行へ）
    if (c == '\n') {
        cursor.X = 0;
        cursor.Y += 1;//次の行に移動
        SetConsoleCursorPosition(hConsole, cursor); // カーソル位置を更新
        return 1;
    }

    // 通常の文字を現在のカーソル位置に出力
    if (WriteConsoleOutputCharacterA(hConsole, &c, 1, cursor, &written)) {
        cursor.X += 1;                              // 出力後にX座標を+1
        SetConsoleCursorPosition(hConsole, cursor); // カーソル位置を更新
        return (int)written;                        // 書き込んだ文字数（成功時: 1）
    } else {
        return 0; // 失敗時
    }
}


```
</details>

<details>
<summary>mystdio.h</summary>

```mystdio.h
#ifndef MYSTDIO_H
#define MYSTDIO_H

#include <stdarg.h>
int my_printf(const char *format, ...);
#endif // MYSTDIO_H
```
</details>

<details>
<summary>mystdio.c</summary>

```mystdio.c
#include "mystdio.h"
#include "console_buffer.h"
#include <windows.h>
#include <string.h>
#include <stdarg.h>

// 一文字出力
int write_char(char c) {
    return console_put_char(c);
}

// 文字列を出力
int write_str(const char *str) {
    int count = 0;
    while (*str) {
        count += write_char(*str++);
    }
    return count;
}

// 整数を出力
int write_int(int value) {
    char buffer[12];
    int i = 0;
    int count = 0;

    if (value < 0) {
        count += write_char('-');
        value = -value;
    }

    if (value == 0) {
        return count + write_char('0');
    }

    while (value > 0) {
        buffer[i++] = '0' + (value % 10);
        value /= 10;
    }

    while (i--) {
        count += write_char(buffer[i]);
    }

    return count;
}

// 10進数を16進数へ変換して出力
int write_hex(unsigned int value) {
    char buffer[9];
    int i = 0;
    int count = 0;

    if (value == 0) {
        return write_char('0');
    }

    while (value > 0) {
        int digit = value % 16;
        if (digit < 10)
            buffer[i++] = '0' + digit;
        else
            buffer[i++] = 'a' + (digit - 10);
        value /= 16;
    }

    while (i--) {
        count += write_char(buffer[i]);
    }

    return count;
}

// printf
int my_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    int count = 0;

    while (*format) {
        if (*format == '%' && *(format + 1)) {
            format++;  //%の次の文字を指す 
            if (*format == '%') {
                count += write_char('%'); // %%が連続した場合は１つ出力
            } else if (*format == 's') {
                const char *str = va_arg(args, const char*);
                count += write_str(str);
            } else if (*format == 'd') {
                int num = va_arg(args, int);
                count += write_int(num);
            } else if (*format == 'x') {
                unsigned int num = va_arg(args, unsigned int);
                count += write_hex(num);
            } else {
                count += write_char('%');
                count += write_char(*format);
            }
            format++;
        } else {  //%〇以外の文字はそのまま1文字ずつ出力する
            count += write_char(*format);
            format++;
        }
    }

    va_end(args);
    return count;
}

```
</details>

## 動作結果
```
C:\Users\nanashi\test>gcc main.c mystdio.c console_buffer.c -o myprintf.exe
C:\Users\nanashi\test>myprintf
num=3 str=aiueo num2=678 str2=kakikukeko hex=11 hex2=f
myPrintfRtn = 55
```

## 動作環境
Windows10 Version 10.0.19045.5737
gcc version 14.2.0