1 Punkte von GN⁺ 2024-07-31 | 1 Kommentare | Auf WhatsApp teilen

Abbildung von C-Makros in Zig

  • Zig

    • Zig ist eine neue Programmiersprache mit Schwerpunkt auf Low-Level- und Systemprogrammierung und etabliert sich als Sprache, die C ersetzen kann
    • Sie befindet sich zwar noch in Entwicklung, wird aber bereits in Projekten wie Bun und TigerBeetle eingesetzt
    • Eine der beeindruckendsten Funktionen von Zig ist die hervorragende Interoperabilität mit C
  • Aufruf externer Bibliotheken

    • In Zig lassen sich externe Bibliotheken einfach aufrufen
    • Beispielcode:
      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);
      }
      
  • Import von C-Header-Dateien

    • In Zig können C-Header-Dateien importiert und wie normale Zig-Imports verwendet werden
    • Beispielcode:
      const win32 = @cImport({
        @cInclude("windows.h");
        @cInclude("winuser.h");
      });
      pub fn main() !void {
        _ = win32.MessageBoxA(null, "world!", "Hello", 0);
      }
      
  • Windows-Programmierung

    • Eine typische Windows-Anwendung besitzt eine main-Funktion und eine Window-Procedure-Funktion
    • Die main-Funktion initialisiert die Anwendung und führt eine Schleife aus, die Nachrichten an die Window-Procedure weiterleitet
    • Die Window-Procedure empfängt und verarbeitet diese Nachrichten
    • Beispielcode:
      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;
      }
      
  • Reflection

    • Das Mapping von C-Makros kann umständlich sein
    • In Zig lassen sich mit der Funktion @typeInfo Strukturfelder und Deklarationen auflisten
    • Dadurch können C-Makros in Zig reflektiert werden
    • Beispielcode:
      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);
      }
      
  • Fazit

    • Zig kann die Funktionen von C komfortabler bereitstellen und nutzt dabei modernere Sprachkonstrukte
    • Zig enthält eine C-Compiler-Toolchain und kann Deklarationen aus C-Header-Dateien nahtlos einbinden
    • Zigs pragmatische Philosophie wird sofort deutlich, sobald man beginnt, die Sprache zu lernen
    • Das intuitive und konsistente Design von Zig trägt zur höheren Produktivität bei

Zusammenfassung von GN⁺

  • Zig ist eine neue Sprache mit Schwerpunkt auf Low-Level- und Systemprogrammierung und bietet hervorragende Interoperabilität mit C
  • Zig kann C-Header-Dateien importieren und verwenden sowie C-Makros in Zig reflektieren
  • Zigs pragmatische Philosophie und intuitives Design helfen stark beim Lernen und beim praktischen Einsatz der Sprache
  • Zig bietet einen Weg, bestehende C-Codebasen nach Zig zu überführen, und senkt damit Hürden für die Einführung der Sprache

1 Kommentare

 
GN⁺ 2024-07-31
Hacker-News-Kommentar
  • Die Funktion @cImport soll entfernt werden

    • Das Importieren von C-Dateien bleibt möglich, erfordert aber mehr Arbeit
    • Um die Abhängigkeit von libclang zu beseitigen, soll diese Funktion aus der Sprache entfernt werden
  • Beispielcode:

    const win32 = @cImport({
      @cInclude("windows.h");
      @cInclude("winuser.h");
    });
    
    pub fn main() !void {
      _ = win32.MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • Entsprechender Code in D:

    import windows, winuser;
    void main() {
      MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • Der Compiler erledigt den Rest

  • Manche wünschen sich eine spezielle Syntax zum Importieren von C-Dateien, aber diese Einfachheit ist besser

  • Ich würde Zig gern mögen, habe aber mit einigen Problemen zu kämpfen

    • Ich denke, vieles liegt daran, dass es noch keine Version 1.0 ist
    • Zum Beispiel enthält die empfohlene Methode, ein Projekt mit zig init zu starten, viel unnötigen Code
    • Kürzlich habe ich erfahren, dass man den Initialisierungsteil mit zig build-exe filename.zig überspringen kann
    • Es gab auch viele Probleme mit der Editor-Integration
    • Ich habe die VSCode-Erweiterung installiert, aber Autovervollständigung und Ähnliches funktionieren nicht richtig
    • Wahrscheinlich ist es eher ein Benutzerfehler, daher werde ich es am Wochenende noch einmal versuchen
  • Der Präprozessor von Clang ist nicht als separater Schritt vor der Kompilierung implementiert

    • Er ist im Wesentlichen ein Teil des Lexers
    • Ich denke, gcc verwendet vermutlich einen ähnlichen Ansatz
    • Auf Makronamen zuzugreifen ist technisch nicht unmöglich
    • Es wurde nicht implementiert, weil die Nachfrage nicht groß ist
  • Ich habe einen Blogbeitrag darüber geschrieben, wie man in D mit ImportC etwas Ähnliches machen kann

  • Es sieht so aus, als würde jedes Enum der Binärdatei mindestens UINT16_MAX*sizeof(intptr_t) Byte hinzufügen

  • Die Funktionsdefinitionen sehen sehr gut lesbar aus

    • Ich habe so etwas schon in anderen Sprachen gesehen, aber dort ist es meistens ziemlich schrecklich
    • Vielleicht lohnt es sich, Zig zu lernen
    • Das ist ein Killer-Feature
  • Mir gefällt die Website

    • Es wirkt, als würde Zig wirklich an Popularität gewinnen