1 points par GN⁺ 2024-07-31 | 1 commentaires | Partager sur WhatsApp

Réflexion des macros C dans Zig

  • Zig

    • Zig est un nouveau langage de programmation centré sur la programmation bas niveau et système, qui s’impose comme une alternative possible à C
    • Il est encore en cours de développement, mais il est déjà utilisé dans des projets comme Bun et TigerBeetle
    • L’une des fonctionnalités les plus impressionnantes de Zig est son excellente interopérabilité avec C
  • Appel de bibliothèques externes

    • Dans Zig, il est facile d’appeler des bibliothèques externes
    • Exemple de code :
      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);
      }
      
  • Importation de fichiers d’en-tête C

    • Dans Zig, on peut importer des fichiers d’en-tête C et les utiliser comme des imports Zig classiques
    • Exemple de code :
      const win32 = @cImport({
        @cInclude("windows.h");
        @cInclude("winuser.h");
      });
      pub fn main() !void {
        _ = win32.MessageBoxA(null, "world!", "Hello", 0);
      }
      
  • Programmation Windows

    • Une application Windows classique possède une fonction main et une fonction window procedure
    • La fonction main initialise l’application et exécute une boucle qui transmet les messages à la window procedure
    • La window procedure reçoit les messages et les traite
    • Exemple de code :
      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;
      }
      
  • Réflexion

    • Mapper des macros C peut être fastidieux
    • Dans Zig, la fonction @typeInfo permet d’énumérer les champs et les déclarations d’une structure
    • Cela permet de refléter les macros C dans Zig
    • Exemple de code :
      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);
      }
      
  • Conclusion

    • Zig permet d’accomplir les tâches de C de manière plus pratique, avec une structure de langage de programmation plus moderne
    • Zig inclut une toolchain de compilateur C, ce qui permet d’intégrer de façon fluide les déclarations des fichiers d’en-tête C
    • La philosophie pragmatique de Zig apparaît clairement dès qu’on commence à apprendre le langage
    • La conception intuitive et cohérente de Zig contribue à améliorer la productivité

Le résumé de GN⁺

  • Zig est un nouveau langage centré sur la programmation bas niveau et système, remarquable par son excellente interopérabilité avec C
  • Zig peut importer et utiliser des fichiers d’en-tête C, et permet aussi de refléter les macros C dans Zig
  • La philosophie pragmatique et la conception intuitive de Zig aident grandement à apprendre et à utiliser le langage
  • Zig offre une voie pour faire évoluer des bases de code C existantes vers Zig, ce qui réduit les obstacles à son adoption

1 commentaires

 
GN⁺ 2024-07-31
Commentaire Hacker News
  • La fonctionnalité @cImport devrait être supprimée

    • Il reste possible d’importer des fichiers C, mais cela demandera davantage de travail
    • Cette fonctionnalité devrait être retirée du langage afin de supprimer la dépendance à libclang
  • Exemple de code :

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

    import windows, winuser;
    void main() {
      MessageBoxA(null, "world!", "Hello", 0);
    }
    
  • Le compilateur se charge du reste

  • Certaines personnes demandent une syntaxe spéciale pour importer des fichiers C, mais cette simplicité est préférable

  • J’aimerais aimer Zig, mais je rencontre quelques problèmes

    • Je pense que c’est surtout parce qu’il ne s’agit pas encore d’une version 1.0
    • Par exemple, la méthode recommandée pour démarrer un projet avec zig init contient beaucoup de code inutile
    • J’ai récemment découvert qu’on pouvait ignorer toute la partie initialisation avec zig build-exe filename.zig
    • J’ai aussi eu beaucoup de problèmes d’intégration avec l’éditeur
    • J’ai installé l’extension VSCode, mais l’autocomplétion et d’autres fonctions ne marchent pas correctement
    • C’est probablement une erreur de ma part, donc je vais réessayer ce week-end
  • Le préprocesseur de Clang n’est pas implémenté comme une étape séparée avant la compilation

    • C’est essentiellement une partie du lexer
    • Je pense que gcc utilise probablement une approche similaire
    • Accéder aux noms des macros n’est pas techniquement impossible
    • Ce n’est pas implémenté parce que la demande n’est pas très forte
  • J’ai écrit un billet de blog sur la façon de faire quelque chose de similaire avec ImportC dans le langage D

  • Il semble que cela ajouterait au minimum UINT16_MAX*sizeof(intptr_t) octets à l’exécutable pour chaque enum

  • La définition des fonctions paraît très lisible

    • Je l’ai déjà vue dans d’autres langages, mais c’est généralement assez horrible
    • Zig vaut peut-être la peine d’être appris
    • C’est une fonctionnalité décisive
  • J’aime bien le site

    • On dirait vraiment que Zig est en train de gagner en popularité