1 ポイント 投稿者 GN⁺ 2024-07-31 | 1件のコメント | WhatsAppで共有

ZigのCマクロ反映

  • Zig

    • Zigは低レベルおよびシステムプログラミングに重点を置いた新しいプログラミング言語で、Cを置き換えうる言語として位置づけられている
    • 現在も開発中だが、すでにBunやTigerBeetleのようなプロジェクトで使われている
    • Zigの最も印象的な機能の1つは、Cとの優れた相互運用性である
  • 外部ライブラリの呼び出し

    • Zigでは外部ライブラリを簡単に呼び出せる
    • 例示コード:
      const win = @import("std").os.windows;
      extern "user32" fn MessageBoxA(?win.HWND, [*:0]const u8, [*:0]const u8, u32,) callconv(win.WINAPI) i32;
      pub fn main() !void {
        _ = MessageBoxA(null, "world!", "Hello", 0);
      }
      
  • Cヘッダーファイルの取り込み

    • ZigではCヘッダーファイルを取り込み、通常のZigのimportのように使える
    • 例示コード:
      const win32 = @cImport({
        @cInclude("windows.h");
        @cInclude("winuser.h");
      });
      pub fn main() !void {
        _ = win32.MessageBoxA(null, "world!", "Hello", 0);
      }
      
  • Windowsプログラミング

    • 一般的なWindowsアプリケーションはmain関数とwindow procedure関数を持つ
    • main関数はアプリケーションを初期化し、メッセージをwindow procedureへ渡すループを実行する
    • window procedureはメッセージを受け取って処理する
    • 例示コード:
      const std = @import("std");
      const windows = std.os.windows;
      const win32 = @cImport({
        @cInclude("windows.h");
        @cInclude("winuser.h");
      });
      var stdout: std.fs.File.Writer = undefined;
      pub export fn WindowProc(hwnd: win32.HWND, uMsg: c_uint, wParam: win32.WPARAM, lParam: win32.LPARAM) callconv(windows.WINAPI) win32.LRESULT {
        _ = switch (uMsg) {
          win32.WM_CLOSE => win32.DestroyWindow(hwnd),
          win32.WM_DESTROY => win32.PostQuitMessage(0),
          else => {
            stdout.print("Unknown window message: 0x{x:0>4}\n", .{uMsg}) catch undefined;
          },
        };
        return win32.DefWindowProcA(hwnd, uMsg, wParam, lParam);
      }
      pub export fn main(hInstance: win32.HINSTANCE) c_int {
        stdout = std.io.getStdOut().writer();
        var class = std.mem.zeroes(win32.WNDCLASSEXA);
        class.cbSize = @sizeOf(win32.WNDCLASSEXA);
        class.style = win32.CS_VREDRAW | win32.CS_HREDRAW;
        class.hInstance = hInstance;
        class.lpszClassName = "Class";
        class.lpfnWndProc = WindowProc;
        _ = win32.RegisterClassExA(&class);
        const hwnd = win32.CreateWindowExA(win32.WS_EX_CLIENTEDGE, "Class", "Window", win32.WS_OVERLAPPEDWINDOW, win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, null, null, hInstance, null);
        _ = win32.ShowWindow(hwnd, win32.SW_NORMAL);
        _ = win32.UpdateWindow(hwnd);
        var message: win32.MSG = std.mem.zeroes(win32.MSG);
        while (win32.GetMessageA(&message, null, 0, 0) > 0) {
          _ = win32.TranslateMessage(&message);
          _ = win32.DispatchMessageA(&message);
        }
        return 0;
      }
      
  • リフレクション

    • Cマクロをマッピングするのは煩雑になりうる
    • Zigでは@typeInfo関数を使って構造体フィールドと宣言を列挙できる
    • これにより、CマクロをZig上でリフレクトできる
    • 例示コード:
      const window_messages = get_window_messages();
      fn get_window_messages() [65536][:0]const u8 {
        var result: [65536][:0]const u8 = undefined;
        @setEvalBranchQuota(1000000);
        for (@typeInfo(win32).Struct.decls) |field| {
          if (field.name.len >= 3 and std.mem.eql(u8, field.name[0..3], "WM_")) {
            const value = @field(win32, field.name);
            result[value] = field.name;
          }
        }
        return result;
      }
      pub export fn WindowProc(hwnd: win32.HWND, uMsg: c_uint, wParam: win32.WPARAM, lParam: win32.LPARAM) callconv(windows.WINAPI) win32.LRESULT {
        _ = switch (uMsg) {
          win32.WM_CLOSE => win32.DestroyWindow(hwnd),
          win32.WM_DESTROY => win32.PostQuitMessage(0),
          else => {
            stdout.print("{s}: 0x{x:0>4}\n", .{ window_messages[uMsg], uMsg }) catch undefined;
          },
        };
        return win32.DefWindowProcA(hwnd, uMsg, wParam, lParam);
      }
      
  • 結論

    • Zigでは、Cの機能をよりモダンなプログラミング言語の構造で、より便利に扱える
    • ZigはCコンパイラのツールチェーンを含んでおり、Cヘッダーファイルの宣言をシームレスに取り込める
    • Zigのプラグマティズムな哲学は、言語を学び始めるとすぐに見えてくる
    • Zigの直感的で一貫した設計は、生産性の向上に貢献する

GN⁺の要約

  • Zigは低レベルおよびシステムプログラミングに重点を置いた新しい言語で、Cとの優れた相互運用性を誇る
  • ZigはCヘッダーファイルを取り込んで利用でき、CマクロをZig上でリフレクトすることもできる
  • Zigのプラグマティズムな哲学と直感的な設計は、言語を学び使ううえで大いに役立つ
  • Zigは既存のCコードベースをZigへ移行する道筋を提供し、言語採用の障壁を乗り越えられる

1件のコメント

 
GN⁺ 2024-07-31
Hacker News のコメント
  • @cImport 機能は削除予定

    • C ファイルを取り込むことは可能だが、より多くの作業が必要になる
    • libclang への依存をなくすため、この機能を言語から削除しようとしている
  • サンプルコード:

    const win32 = @cImport({
      @cInclude("windows.h");
      @cInclude("winuser.h");
    });
    
    pub fn main() !void {
      _ = win32.MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • D 言語での同等のコード:

    import windows, winuser;
    void main() {
      MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • コンパイラが残りを処理する

  • C ファイルを取り込むための特別な構文を求める人もいるが、このシンプルさのほうが良い

  • Zig を好きになりたいが、いくつか問題に遭遇している

    • その大半は、まだ 1.0 ではないことが原因だと思う
    • たとえば、zig init でプロジェクトを始める推奨方法には不要なコードが多い
    • 最近、zig build-exe filename.zig で初期化部分を飛ばせることを知った
    • エディタ統合の問題も多かった
    • VSCode 拡張をインストールしたが、自動補完などがきちんと動かない
    • おそらくユーザー側のミスの可能性が高いので、週末にもう一度試すつもり
  • Clang のプリプロセッサは、コンパイル前の独立した段階として実装されていない

    • 本質的にはレキサの一部
    • gcc も似た方式を使っていると思う
    • マクロ名にアクセスすることは技術的に不可能ではない
    • 需要が高くないため、実装されていない
  • D 言語で ImportC を使って同様のことを行う方法をブログに書いた

  • enum ごとに、少なくとも UINT16_MAX*sizeof(intptr_t) バイトが実行ファイルに追加されそう

  • 関数定義がとても読みやすく見える

    • 他の言語でも見たことはあるが、たいていはかなりひどい
    • Zig は学ぶ価値があるかもしれない
    • これはキラー機能だ
  • このサイトが気に入った

    • Zig は本当に人気が出てきているようだ