1 คะแนน โดย GN⁺ 2024-07-31 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

การสะท้อน C มาโครใน Zig

  • Zig

    • Zig เป็นภาษาโปรแกรมใหม่ที่เน้นการเขียนโปรแกรมระดับล่างและระบบ และกำลังวางตำแหน่งตัวเองเป็นภาษาที่สามารถใช้แทน C ได้
    • แม้ปัจจุบันยังอยู่ระหว่างการพัฒนา แต่ก็ถูกใช้งานแล้วในโปรเจกต์อย่าง Bun และ TigerBeetle
    • หนึ่งในความสามารถที่น่าประทับใจที่สุดของ Zig คือการทำงานร่วมกับ 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 แล้วใช้งานได้เหมือนกับการ import แบบปกติของ Zig
    • โค้ดตัวอย่าง:
      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 เพื่อไล่ดูฟิลด์และการประกาศของ struct ได้
    • ด้วยวิธีนี้จึงสามารถสะท้อน 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 มาพร้อม toolchain ของ C และสามารถรวม declaration จากไฟล์เฮดเดอร์ C ได้อย่างราบรื่น
    • ปรัชญาแบบ practical ของ Zig จะเห็นได้ชัดทันทีเมื่อเริ่มเรียนรู้ภาษา
    • การออกแบบที่ใช้งานง่ายและสม่ำเสมอของ Zig ช่วยเพิ่มประสิทธิภาพในการทำงาน

สรุปโดย GN⁺

  • Zig เป็นภาษาใหม่ที่เน้นการเขียนโปรแกรมระดับล่างและระบบ พร้อมความสามารถในการทำงานร่วมกับ C ได้อย่างยอดเยี่ยม
  • Zig สามารถนำเข้าไฟล์เฮดเดอร์ C มาใช้งานได้ และยังสะท้อน C มาโครภายใน Zig ได้ด้วย
  • ปรัชญาแบบ practical และการออกแบบที่เข้าใจง่ายของ 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 มีโค้ดที่ไม่จำเป็นเยอะเกินไป
    • เพิ่งรู้ไม่นานมานี้ว่าสามารถข้ามส่วน initialisation ไปได้ด้วย zig build-exe filename.zig
    • ยังมีปัญหาเรื่องการเชื่อมกับเอดิเตอร์เยอะพอสมควร
    • ติดตั้งส่วนขยาย VSCode แล้ว แต่ autocomplete และอย่างอื่นทำงานได้ไม่ดี
    • น่าจะเป็นความผิดพลาดของผู้ใช้เอง เลยกะว่าจะลองใหม่อีกครั้งช่วงสุดสัปดาห์
  • พรีโปรเซสเซอร์ของ Clang ไม่ได้ถูกทำเป็นขั้นตอนแยกก่อนคอมไพล์

    • โดยพื้นฐานแล้วมันเป็นส่วนหนึ่งของ lexer
    • คิดว่า gcc ก็น่าจะใช้แนวทางคล้ายกัน
    • การเข้าถึงชื่อแมโครไม่ได้เป็นไปไม่ได้ในทางเทคนิค
    • ที่ยังไม่มีการทำก็เพราะไม่ได้มีความต้องการมากนัก
  • มีการเขียนบล็อกเกี่ยวกับวิธีทำงานคล้ายกันในภาษา D โดยใช้ ImportC

  • ดูเหมือนว่าแต่ละ enum จะเพิ่มอย่างน้อย UINT16_MAX*sizeof(intptr_t) ไบต์เข้าไปในไฟล์ executable

  • นิยามฟังก์ชันดูอ่านง่ายมาก

    • เคยเห็นในภาษาอื่นมาบ้าง แต่ปกติแล้วมักจะน่ากลัวมาก
    • Zig อาจคุ้มค่าที่จะลองเรียนรู้
    • นี่คือฟีเจอร์ขายหลัก
  • ชอบเว็บไซต์นี้

    • ดูเหมือนว่า Zig กำลังได้รับความนิยมขึ้นจริงๆ