javacommons
@javacommons

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Zig言語でUTF-8の日本語を出力(Windows)

解決したいこと

Zig から派生した Zen 言語では Windows 上でも UTF-8 で書かれたソース(文字列)をなんの手を加えることもなく、コンソールに表示させることができますが、Zig for Windows では、コマンドプロンプトで予め「chcp 65001」としてUTF-8モードにしておかないと文字化けします。

以下のソースは、Zen言語で自動的に追加される「SetConsoleOutputCP」関数(Win32 API)を手動で追加したものです。mainの先頭で一回呼んでおけば、それ以降のコンソール出力がUTF-8を正常に受け付けるようになります。(chcp 932 の状態でも表示されます。また、chcp 932 の状態でも韓国語やアラビア語も表示できます)

main.zig
const std = @import("std");
const windows = @import("std").os.windows;
const WINAPI = windows.WINAPI;
const BOOL = windows.BOOL;
const UINT = windows.UINT;

extern "kernel32" fn SetConsoleOutputCP(wCodePageID: UINT) callconv(WINAPI) BOOL;

pub fn main() anyerror!void {
    _ = SetConsoleOutputCP(65001);
    std.log.info("Zig の道へようこそ。\n道案内はこちらからどうぞ:\nhttps://ziglang.org/ja/\n", .{});
    std.log.info("漢字(韓国語)=한자\n漢字(アラビア語)=كانجي\n", .{});
}

英語圏以外(でかつ Windowsのコンソールアプリ)なら必須の処理なので Zen 言語のように処理系が隠蔽してくれてもよさげに思いますが、「隠された処理」を嫌う Zig では取り込んでもらうのは無理かもしれません。

せめて、Linux や macOS とソースを同一化するために、「SetConsoleOutputCP」関数の宣言と呼び出しを Windows でない場合には削除したいのですが、どうすればよいのかわかりません。もしわかる人がいたら教えてください。

また、Zig for Windows で UTF-8 文字列を表示する他の良い方法があればそれでもかまいません。

よろしくお願いします。

追伸:
https://zenn.dev/javacommons/articles/de2a99ecf588f9
のコメントにあるように以下のコードで Windows と Ubuntu で同一ソースで日本語(UTF-8)を出力できました。

const builtin = @import("builtin");
const std = @import("std");
const windows = @import("std").os.windows;
const WINAPI = windows.WINAPI;
const BOOL = windows.BOOL;
const UINT = windows.UINT;

extern "kernel32" fn SetConsoleOutputCP(wCodePageID: UINT) callconv(WINAPI) BOOL;

pub fn main() anyerror!void {
    switch (builtin.os.tag) {
        .windows => _ = SetConsoleOutputCP(65001),
        else => {}
    }
    std.log.info("Zig の道へようこそ。\n道案内はこちらからどうぞ:\nhttps://ziglang.org/ja/\n", .{});
}
1

このPRがそうなのでしょうか?有望そうに見えますが、英語が分からないので状況を正確に把握できません:sweat_smile:
https://github.com/ziglang/zig/pull/14411

少なくともすぐに取り込んでくれそうな雰囲気ではないですね。

C (実際には C++) との連携でクロスプラットフォームのソースができました:
https://github.com/blog-memo/zig-setconsoleoutputcp-2023-0909-1622

main.zig
const std = @import("std");
const c = @cImport({
    @cInclude("code.h");
});

pub fn main() void {
    c.calling_c_from_zig();
    std.log.info("Zig の道へようこそ。\n道案内はこちらからどうぞ:\nhttps://ziglang.org/ja/\n", .{});
    std.log.info("漢字(韓国語)=한자\n漢字(アラビア語)=كانجي\n", .{});
}
code.h
#pragma once

#ifdef __cplusplus
extern "C" {
#endif

void calling_c_from_zig(void);

#ifdef __cplusplus
}
#endif
code.cpp
#include "code.h"

#ifdef _WIN32
#include <windows.h>
#endif

void
calling_c_from_zig(void)
{
#ifdef _WIN32
  SetConsoleOutputCP(CP_UTF8);
#endif
}
1Like

build.zig の中で使用するソースファイルを切り替えるようにしてみました。
0.11以降じゃないと動かないと思います。(最初、0.10で作っていたのに0.11で動かなかったので作り直しました‥)

やってることは @javacommons さんと同じようなことで、

  • Windows → コードページ設定関数を呼ぶ
  • それ以外 → 何もしない(ダミー関数を呼ぶ)

となっています。

2Like

#ifdef とかがつかえない生 zig プロジェクトでは、(主に) Windows とそれ以外のプラットフォームとの間でのコード切り替えに役立つ大発見ですね!

    exe.addModule("console", b.createModule(.{
        .source_file = .{ .path = if (target.isWindows()) "src/console/windows.zig" else "src/console/default.zig" },
    }));
1Like

Your answer might help someone💌