GN⁺: Zig에서의 C 매크로 리플렉션
(jstrieb.github.io)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가 정말로 인기를 끌고 있는 것 같음