1 puntos por GN⁺ 2024-07-31 | 1 comentarios | Compartir por WhatsApp

Reflexión de macros de C en Zig

  • Zig

    • Zig es un nuevo lenguaje de programación enfocado en la programación de bajo nivel y de sistemas, y se está posicionando como un lenguaje capaz de reemplazar a C
    • Aunque sigue en desarrollo, ya se usa en proyectos como Bun y TigerBeetle
    • Una de las características más impresionantes de Zig es su excelente interoperabilidad con C
  • Llamada a bibliotecas externas

    • En Zig se pueden llamar bibliotecas externas fácilmente
    • Código de ejemplo:
      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);
      }
      
  • Importación de archivos de cabecera de C

    • En Zig se pueden importar archivos de cabecera de C y usarlos como si fueran importaciones normales de Zig
    • Código de ejemplo:
      const win32 = @cImport({
        @cInclude("windows.h");
        @cInclude("winuser.h");
      });
      pub fn main() !void {
        _ = win32.MessageBoxA(null, "world!", "Hello", 0);
      }
      
  • Programación para Windows

    • Una aplicación típica de Windows tiene una función main y una función window procedure
    • La función main inicializa la aplicación y ejecuta un bucle que envía mensajes a la window procedure
    • La window procedure recibe y procesa los mensajes
    • Código de ejemplo:
      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;
      }
      
  • Reflexión

    • Mapear macros de C puede ser tedioso
    • En Zig se puede usar la función @typeInfo para enumerar campos y declaraciones de estructuras
    • Esto permite reflejar macros de C dentro de Zig
    • Código de ejemplo:
      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);
      }
      
  • Conclusión

    • Zig puede realizar las funciones de C de una forma más conveniente usando estructuras de lenguaje de programación más modernas
    • Zig incluye una cadena de herramientas de compilación de C, por lo que puede incorporar sin fricción las declaraciones de archivos de cabecera de C
    • La filosofía pragmática de Zig se hace evidente apenas se empieza a aprender el lenguaje
    • El diseño intuitivo y consistente de Zig contribuye a mejorar la productividad

Resumen de GN⁺

  • Zig es un nuevo lenguaje enfocado en la programación de bajo nivel y de sistemas, con una excelente interoperabilidad con C
  • Zig puede importar y usar archivos de cabecera de C, y también puede reflejar macros de C dentro de Zig
  • La filosofía pragmática y el diseño intuitivo de Zig ayudan mucho a la hora de aprender y usar el lenguaje
  • Zig ofrece una ruta para migrar bases de código existentes en C hacia Zig, superando barreras para la adopción del lenguaje

1 comentarios

 
GN⁺ 2024-07-31
Comentarios de Hacker News
  • La función @cImport está por eliminarse

    • Sigue siendo posible importar archivos C, pero requiere más trabajo
    • Quieren eliminar esta función del lenguaje para quitar la dependencia de libclang
  • Código de ejemplo:

    const win32 = @cImport({
      @cInclude("windows.h");
      @cInclude("winuser.h");
    });
    
    pub fn main() !void {
      _ = win32.MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • Código equivalente en D:

    import windows, winuser;
    void main() {
      MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • El compilador se encarga del resto

  • Hay personas que piden una sintaxis especial para importar archivos C, pero esta simplicidad es mejor

  • Quiero que me guste Zig, pero me he topado con algunos problemas

    • Creo que en su mayoría se debe a que todavía no es la versión 1.0
    • Por ejemplo, la forma recomendada de iniciar un proyecto con zig init trae mucho código innecesario
    • Hace poco descubrí que se puede saltar la parte de inicialización con zig build-exe filename.zig
    • También tuve muchos problemas con la integración del editor
    • Instalé la extensión de VSCode, pero el autocompletado y demás no funcionan bien
    • Lo más probable es que sea error del usuario, así que voy a intentarlo de nuevo el fin de semana
  • El preprocesador de Clang no está implementado como una etapa separada previa a la compilación

    • Esencialmente forma parte del lexer
    • Creo que gcc probablemente use un enfoque similar
    • Acceder a los nombres de las macros no es técnicamente imposible
    • No se implementa porque no hay mucha demanda
  • Escribí en el blog cómo hacer algo similar en D usando ImportC

  • Parece que cada enum añadirá al ejecutable al menos UINT16_MAX*sizeof(intptr_t) bytes

  • La definición de funciones se ve muy fácil de leer

    • Lo he visto en otros lenguajes, pero normalmente es bastante horrible
    • Tal vez valga la pena aprender Zig
    • Esta es una función decisiva
  • Me gusta el sitio

    • Parece que Zig de verdad está ganando popularidad