1 pontos por GN⁺ 2024-07-31 | 1 comentários | Compartilhar no WhatsApp

Reflexão de macros C no Zig

  • Zig

    • Zig é uma nova linguagem de programação focada em programação de baixo nível e de sistemas, posicionando-se como uma linguagem capaz de substituir C
    • Ainda está em desenvolvimento, mas já é usada em projetos como Bun e TigerBeetle
    • Um dos recursos mais impressionantes do Zig é sua excelente interoperabilidade com C
  • Chamando bibliotecas externas

    • No Zig, é possível chamar bibliotecas externas com facilidade
    • Código de exemplo:
      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);
      }
      
    Publicidade
  • Importando arquivos de cabeçalho C

    • No Zig, é possível importar arquivos de cabeçalho C e usá-los como um import comum do Zig
    • Código de exemplo:
      const win32 = @cImport({
        @cInclude("windows.h");
        @cInclude("winuser.h");
      });
      pub fn main() !void {
        _ = win32.MessageBoxA(null, "world!", "Hello", 0);
      }
      
  • Programação para Windows

    Publicidade
    • Um aplicativo Windows típico tem uma função main e uma função window procedure
    • A função main inicializa o aplicativo e executa um loop que encaminha mensagens para a window procedure
    • A window procedure recebe e processa as mensagens
    • Código de exemplo:
      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;
      }
      
  • Reflexão

    • Mapear macros C pode ser trabalhoso
    • No Zig, é possível listar campos e declarações de structs usando a função @typeInfo
    • Com isso, dá para refletir macros C no Zig
    • Código de exemplo:
      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);
      }
      
    Publicidade
  • Conclusão

    • O Zig consegue realizar as mesmas tarefas do C de forma mais conveniente, usando estruturas de linguagem mais modernas
    • O Zig inclui um toolchain de compilador C, permitindo incorporar sem atrito as declarações de arquivos de cabeçalho C
    • A filosofia pragmática do Zig fica evidente assim que se começa a aprender a linguagem
    • O design intuitivo e consistente do Zig contribui para aumentar a produtividade

Resumo do GN⁺

  • Zig é uma nova linguagem focada em programação de baixo nível e de sistemas, com excelente interoperabilidade com C
  • Zig pode importar arquivos de cabeçalho C para uso direto e também refletir macros C dentro da linguagem
  • A filosofia pragmática e o design intuitivo do Zig ajudam bastante no aprendizado e no uso da linguagem
  • Zig oferece um caminho para migrar bases de código C existentes para Zig, superando barreiras de adoção da linguagem

1 comentários

 
GN⁺ 2024-07-31
Comentários do Hacker News
  • O recurso @cImport está previsto para ser removido

    • Ainda será possível importar arquivos C, mas isso exigirá mais trabalho
    • Querem remover esse recurso da linguagem para eliminar a dependência de libclang
  • Código de exemplo:

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

    import windows, winuser;
    void main() {
      MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • O compilador cuida do resto

  • Há pessoas pedindo uma sintaxe especial para importar arquivos C, mas essa simplicidade é melhor

  • Quero gostar de Zig, mas estou tendo alguns problemas

    • Acho que a maioria deles é porque ainda não chegou à versão 1.0
    • Por exemplo, a forma recomendada de iniciar um projeto com zig init tem muito código desnecessário
    • Recentemente descobri que é possível pular a parte de inicialização com zig build-exe filename.zig
    • Também tive muitos problemas com integração no editor
    • Instalei a extensão do VSCode, mas o autocompletar e afins não funcionam direito
    • Provavelmente é erro do usuário, então vou tentar de novo no fim de semana
  • O pré-processador do Clang não é implementado como uma etapa separada antes da compilação

    • Essencialmente, ele faz parte do lexer
    • Acho que o gcc também deve usar uma abordagem parecida
    • Acessar nomes de macros não é tecnicamente impossível
    • Isso não é implementado porque não há muita demanda
  • Escrevi no blog como fazer algo semelhante em D usando ImportC

  • Parece que isso adicionaria pelo menos UINT16_MAX*sizeof(intptr_t) bytes ao executável para cada enum

  • A definição de função parece muito fácil de ler

    • Já vi isso em outras linguagens, mas normalmente é algo bem horrível
    • Talvez valha a pena aprender Zig
    • Esse é um recurso matador
  • Gostei do site

    • Parece mesmo que Zig está ganhando popularidade