1P by neo 2달전 | favorite | 댓글 1개

Zig의 C 매크로 반영

  • 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 헤더 파일을 가져와서 일반 Zig 가져오기처럼 사용할 수 있음
    • 예시 코드:
      const win32 = @cImport({
        @cInclude("windows.h");
        @cInclude("winuser.h");
      });
      pub fn main() !void {
        _ = win32.MessageBoxA(null, "world!", "Hello", 0);
      }
      
  • 윈도우 프로그래밍

    • 일반적인 윈도우 애플리케이션은 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로 전환할 수 있는 경로를 제공하여 언어 채택의 장애물을 극복함
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가 정말로 인기를 끌고 있는 것 같음