1 điểm bởi GN⁺ 2024-07-31 | 1 bình luận | Chia sẻ qua WhatsApp

Phản chiếu macro C trong Zig

  • Zig

    • Zig là một ngôn ngữ lập trình mới tập trung vào lập trình mức thấp và hệ thống, đang được định vị như một ngôn ngữ có thể thay thế C
    • Dù hiện vẫn đang trong quá trình phát triển, Zig đã được sử dụng trong các dự án như Bun và TigerBeetle
    • Một trong những tính năng ấn tượng nhất của Zig là khả năng tương tác xuất sắc với C
  • Gọi thư viện bên ngoài

    • Trong Zig, có thể gọi thư viện bên ngoài một cách dễ dàng
    • Mã ví dụ:
      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);
      }
      
    Quảng cáo
  • Import file header C

    • Trong Zig, có thể import file header C và sử dụng nó giống như import Zig thông thường
    • Mã ví dụ:
      const win32 = @cImport({
        @cInclude("windows.h");
        @cInclude("winuser.h");
      });
      pub fn main() !void {
        _ = win32.MessageBoxA(null, "world!", "Hello", 0);
      }
      
  • Lập trình Windows

    Quảng cáo
    • Một ứng dụng Windows điển hình có hàm main và hàm window procedure
    • Hàm main khởi tạo ứng dụng và chạy vòng lặp chuyển tiếp message tới window procedure
    • Window procedure nhận và xử lý các message đó
    • Mã ví dụ:
      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;
      }
      
  • Phản chiếu

    • Việc ánh xạ macro C có thể khá phiền phức
    • Trong Zig, có thể dùng hàm @typeInfo để liệt kê các field và declaration của struct
    • Nhờ đó có thể phản chiếu macro C trong Zig
    • Mã ví dụ:
      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);
      }
      
    Quảng cáo
  • Kết luận

    • Zig có thể thực hiện các khả năng của C một cách thuận tiện hơn bằng cách sử dụng cấu trúc ngôn ngữ lập trình hiện đại hơn
    • Zig tích hợp toolchain trình biên dịch C, cho phép đưa các declaration trong file header C vào một cách liền mạch
    • Triết lý thực dụng của Zig bộc lộ rất rõ ngay khi bắt đầu học ngôn ngữ này
    • Thiết kế trực quan và nhất quán của Zig góp phần nâng cao năng suất

Tóm tắt của GN⁺

  • Zig là một ngôn ngữ mới tập trung vào lập trình mức thấp và hệ thống, nổi bật với khả năng tương tác xuất sắc với C
  • Zig có thể import file header C để sử dụng, đồng thời có thể phản chiếu các macro C trong Zig
  • Triết lý thực dụng và thiết kế trực quan của Zig là trợ giúp lớn trong quá trình học và sử dụng ngôn ngữ
  • Zig cung cấp một lộ trình chuyển đổi codebase C hiện có sang Zig, giúp vượt qua rào cản trong việc chấp nhận ngôn ngữ

1 bình luận

 
GN⁺ 2024-07-31
Ý kiến trên Hacker News
  • Tính năng @cImport sắp bị loại bỏ

    • Vẫn có thể import file C nhưng sẽ cần nhiều thao tác hơn
    • Họ muốn loại bỏ tính năng này khỏi ngôn ngữ để bỏ phụ thuộc vào libclang
  • Mã ví dụ:

    const win32 = @cImport({
      @cInclude("windows.h");
      @cInclude("winuser.h");
    });
    
    pub fn main() !void {
      _ = win32.MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • Mã tương đương trong ngôn ngữ D:

    import windows, winuser;
    void main() {
      MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • Trình biên dịch xử lý phần còn lại

  • Có người muốn có cú pháp đặc biệt để import file C, nhưng sự đơn giản này tốt hơn

  • Tôi muốn thích Zig nhưng đang gặp một vài vấn đề

    • Tôi nghĩ phần lớn là vì nó vẫn chưa đến phiên bản 1.0
    • Ví dụ, cách được khuyến nghị để bắt đầu dự án bằng zig init có khá nhiều mã không cần thiết
    • Gần đây tôi phát hiện có thể bỏ qua phần khởi tạo bằng zig build-exe filename.zig
    • Tôi cũng gặp khá nhiều vấn đề với tích hợp editor
    • Tôi đã cài extension VSCode nhưng tự động hoàn thành và các tính năng tương tự không hoạt động đúng
    • Có lẽ phần lớn là do lỗi người dùng nên tôi sẽ thử lại vào cuối tuần
  • Bộ tiền xử lý của Clang không được triển khai như một bước riêng trước khi biên dịch

    • Về bản chất nó là một phần của lexer
    • Tôi nghĩ gcc cũng dùng cách tiếp cận tương tự
    • Việc truy cập tên macro về mặt kỹ thuật không phải là bất khả thi
    • Chỉ là chưa được triển khai vì nhu cầu không nhiều
  • Tôi đã viết một bài blog về cách làm việc tương tự trong ngôn ngữ D bằng ImportC

  • Có vẻ như mỗi enum sẽ thêm ít nhất UINT16_MAX*sizeof(intptr_t) byte vào file thực thi

  • Định nghĩa hàm trông rất dễ đọc

    • Tôi đã thấy kiểu này trong các ngôn ngữ khác nhưng thường rất kinh khủng
    • Có lẽ Zig đáng để học
    • Đây là một tính năng sát thủ
  • Tôi thích trang web này

    • Có vẻ như Zig thực sự đang ngày càng phổ biến