ZigでWin32APIを利用する

# はじめに
ZigでWin32APIを利用してウィンドウを表示します。
以下のような表示を目指します。
![zigWindow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263901/1b4c0d4c-a08f-39f7-0da7-8f1fd9eb5ca2.png)

# 開発環境
````bat
zig version
0.13.0
````

# zigwin32を使う場合
[win32metadata](https://github.com/microsoft/win32metadata) から自動生成されたバインディングである [zigwin32](https://github.com/marlersoft/zigwin32) を使う場合です。
まずはプロジェクトを初期化します。
````bat
mkdir zig-window
cd zig-window
zig init
echo .zig-cache > .gitignore
echo zig-out >> .gitignore
git init
git add .
git commit -m "Add: 最初のコミット"
````

次に、`zigwin32` を追加します。
リポジトリの下にmodsフォルダを作って、そこにサブモジュールを置きます。
````bat
mkdir mods
cd mods
git submodule add https://github.com/marlersoft/zigwin32
git commit -m "Add: モジュールを追加"
````

依存関係をファイルに記述します。
````build.zig.zon
.{
    // 中略...

    .dependencies = .{
        .zigwin32 = .{ .path = "mods/zigwin32" },
    },
}
````

ビルドスクリプトにも記述します。
````build.zig
pub fn build(b: *std.Build) void {
    // 中略...
    
    const exe = b.addExecutable(.{ ... });
    const zigwin32 = b.dependency("zigwin32", .{});
    exe.root_module.addImport("zigwin32", zigwin32.module("zigwin32"));
}
````

これでWin32APIを利用する準備が整いました。
冒頭のようにただウィンドウを表示するだけならこれでOKです。
````main.zig
const std = @import("std");

pub const UNICODE: bool = true;

const zigwin32 = @import("zigwin32");
const win_messaging = zigwin32.ui.windows_and_messaging;
const win_zig = zigwin32.zig;
const win_foundation = zigwin32.foundation;
const win_libloader = zigwin32.system.library_loader;
const WINAPI = @import("std").os.windows.WINAPI;

fn WindowProcedure(hwnd: win_foundation.HWND, msg: u32, wparam: win_foundation.WPARAM, lparam: win_foundation.LPARAM) callconv(WINAPI) win_foundation.LRESULT {
    if (msg == win_messaging.WM_DESTROY) {
        win_messaging.PostQuitMessage(0);
        return 0;
    }
    return win_messaging.DefWindowProc(hwnd, msg, wparam, lparam);
}

pub fn main() !void {
    var w = std.mem.zeroes(win_messaging.WNDCLASSEX);
    w.cbSize = @sizeOf(win_messaging.WNDCLASSEX);
    w.lpfnWndProc = WindowProcedure;
    w.lpszClassName = win_zig._T("HelloWorld");
    w.hInstance = win_libloader.GetModuleHandle(null);
    _ = win_messaging.RegisterClassEx(&w);

    var wrc: win_foundation.RECT = .{ .left = 0, .top = 0, .right = 300, .bottom = 200 };
    _ = win_messaging.AdjustWindowRect(&wrc, win_messaging.WS_OVERLAPPEDWINDOW, 0);

    const hwnd: ?win_foundation.HWND = win_messaging.CreateWindowEx(win_messaging.WS_EX_LEFT, w.lpszClassName, win_zig._T("HelloWorld"), win_messaging.WS_OVERLAPPEDWINDOW, 0, 0, wrc.right - wrc.left, wrc.bottom - wrc.top, null, null, w.hInstance, null);

    _ = win_messaging.ShowWindow(hwnd, win_messaging.SW_SHOW);
    while (true) {
        var msg = std.mem.zeroes(win_messaging.MSG);
        if (win_messaging.PeekMessage(&msg, null, 0, 0, win_messaging.PM_REMOVE) != 0) {
            _ = win_messaging.TranslateMessage(&msg);
            _ = win_messaging.DispatchMessage(&msg);
        }
        if (msg.message == win_messaging.WM_QUIT) {
            break;
        }
    }
    _ = win_messaging.UnregisterClass(w.lpszClassName, w.hInstance);
    _ = win_messaging.DestroyWindow(hwnd);
}

test "simple test" {
    var list = std.ArrayList(i32).init(std.testing.allocator);
    defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
    try list.append(42);
    try std.testing.expectEqual(@as(i32, 42), list.pop());
}
````

下記コマンドで動作を確認できます。
````bat
zig build run
````

## 小ネタ
コード中では特に使っていないように見える `UNICODE` という変数ですが、実はこれがないとビルドできません。
`zigwin32/win32/zig.zig` を参照すると分かるのですが、この変数がないと内部的に未指定モードになり、@compileErrorによってビルドに失敗する仕組みになっています。

````zig.zig
const root = @import("root");
pub const UnicodeMode = enum { ansi, wide, unspecified };
pub const unicode_mode: UnicodeMode = if (@hasDecl(root, "UNICODE")) (if (root.UNICODE) .wide else .ansi) else .unspecified;
````
[抜粋元コードのライセンス](https://github.com/marlersoft/zigwin32/blob/main/LICENSE)

@import() で得た type オブジェクトを @hasDecl に渡すと対象に任意の名前の宣言が存在するかチェックできるようで、面白い仕組みです。
そして@import("root") のときは恐らくエントリポイントのファイルが指定される？っぽいです。

追記：
やはり"root"のときだけ特別扱いされているようです。
https://ziglang.org/documentation/master/#import

# zwin32を使う場合
ゲーム開発向けに作られたバインディングがあるようで、こちらも試してみました。
[zwin32](https://github.com/zig-gamedev/zwin32) というものです。

こちらもまずはプロジェクトを初期化します。
````bat
mkdir zig-game
cd zig-game
zig init
echo .zig-cache > .gitignore
echo zig-out >> .gitignore
git init
git add .
git commit -m "Add: 最初のコミット"
````

次に、`zwin32` を追加します。
リポジトリの下にmodsフォルダを作って、そこにサブモジュールを置きます。
````bat
mkdir mods
cd mods
git submodule add https://github.com/zig-gamedev/zwin32
git commit -m "Add: モジュールを追加"
````

同様に依存関係をファイルに記述します。
````build.zig.zon
.{
    // 中略...

    .dependencies = .{
        .zwin32 = .{ .path = "mods/zwin32" },
    },
}
````

ビルドスクリプトにも記述します。
````build.zig
pub fn build(b: *std.Build) void {
    // 中略...
    
    const exe = b.addExecutable(.{ ... });
    const zwin32 = b.dependency("zwin32", .{});
    exe.root_module.addImport("zwin32", zwin32.module("root"));

    @import("zwin32").install_xaudio2(&exe.step, .bin);
    @import("zwin32").install_d3d12(&exe.step, .bin);
    @import("zwin32").install_directml(&exe.step, .bin);
}
````

動作結果は同じですが、APIが微妙に異なるのでこちらもコードを下記に載せます。
````.zig
const std = @import("std");
const zwin32 = @import("zwin32");
const w32 = zwin32.w32;
const dwrite = zwin32.dwrite;
const dxgi = zwin32.dxgi;
const d3d12 = zwin32.d3d12;
const d3d12d = zwin32.d3d12d;
const dml = zwin32.directml;

fn WindowProcedure(hwnd: w32.HWND, msg: w32.UINT, wparam: w32.WPARAM, lparam: w32.LPARAM) callconv(w32.WINAPI) w32.LRESULT {
    if (msg == w32.WM_DESTROY) {
        w32.PostQuitMessage(0);
        return 0;
    }
    return w32.DefWindowProcA(hwnd, msg, wparam, lparam);
}

pub fn main() !void {
    var w = std.mem.zeroes(w32.WNDCLASSEXA);
    w.cbSize = @sizeOf(w32.WNDCLASSEXA);
    w.lpfnWndProc = WindowProcedure;
    w.lpszClassName = "HelloWorld";
    w.hInstance = @ptrCast(w32.GetModuleHandleA(null));
    _ = w32.RegisterClassExA(&w);

    var wrc: w32.RECT = .{ .left = 0, .top = 0, .right = 300, .bottom = 200 };
    _ = w32.AdjustWindowRectEx(&wrc, w32.WS_OVERLAPPEDWINDOW, 0, 0x00000000);

    const hwnd: ?w32.HWND = w32.CreateWindowExA(0x00000000, w.lpszClassName, "HelloWorld", w32.WS_OVERLAPPEDWINDOW | w32.WS_VISIBLE, 0, 0, wrc.right - wrc.left, wrc.bottom - wrc.top, null, null, w.hInstance, null);

    if (hwnd) |hwndInst| {
        while (true) {
            var msg: w32.MSG = std.mem.zeroes(w32.MSG);
            if (w32.PeekMessageA(&msg, null, 0, 0, w32.PM_REMOVE) != 0) {
                _ = w32.TranslateMessage(&msg);
                _ = w32.DispatchMessageA(&msg);
            }
            if (msg.message == w32.WM_QUIT) {
                break;
            }
        }
        _ = w32.DestroyWindow(hwndInst);
    }
}

test "simple test" {
    var list = std.ArrayList(i32).init(std.testing.allocator);
    defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
    try list.append(42);
    try std.testing.expectEqual(@as(i32, 42), list.pop());
}
````

# cInclude/cImportを使う場合
せっかくZigにはC言語との連携機能があるのですから、それを使えばいいように思います。
````.zig
const windows = @cImport(@cInclude("Windows.h"));
````

これだけではビルドに失敗します。
> src\main.zig:2:17: error: C import failed
const windows = @cImport(@cInclude("Windows.h"));
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src\main.zig:2:17: note: libc headers not available; compilation does not link against libc

なので、メッセージに従いlibcをリンクさせます。
````build.zig
exe.linkLibC();
````

すると今度は別のメッセージが表示され、失敗します。
> error: unable to translate macro: undefined identifier `A`
pub const __MINGW_NAME_AW = @compileError("unable to translate macro: undefined identifier `A`");

これを解決する方法が分からなかったので、この方法は諦めました。