0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-05-07

始めに

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

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

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

前に投稿した記事

Windows APIを用いてprintf関数を再現
Windows コンソールバッファに直接書き込み(黒い画面に描画する原理)

コード

main.c
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;
}
console_buffer.h
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

console_buffer.c
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; // 失敗時
    }
}


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

#include <stdarg.h>
int my_printf(const char *format, ...);
#endif // MYSTDIO_H
mystdio.c
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('%'); // %%が連続した場合は1つ出力
            } 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;
}

動作結果

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?