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?

ZigでWin32APIを利用する

Last updated at Posted at 2024-08-16

はじめに

ZigでWin32APIを利用してウィンドウを表示します。
以下のような表示を目指します。
zigWindow.png

開発環境

zig version
0.13.0

zigwin32を使う場合

win32metadata から自動生成されたバインディングである zigwin32 を使う場合です。
まずはプロジェクトを初期化します。

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フォルダを作って、そこにサブモジュールを置きます。

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());
}

下記コマンドで動作を確認できます。

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;

抜粋元コードのライセンス

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

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

zwin32を使う場合

ゲーム開発向けに作られたバインディングがあるようで、こちらも試してみました。
zwin32 というものです。

こちらもまずはプロジェクトを初期化します。

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フォルダを作って、そこにサブモジュールを置きます。

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");

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

0
0
1

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?