← Volver al listado de tecnologías

Interoperabilidad con C

Por: Artiko
zigcinteropffilinking

Interoperabilidad con C

Importar Headers C

const std = @import("std");

// Importar biblioteca estándar de C
const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("stdlib.h");
    @cInclude("string.h");
});

pub fn main() void {
    // Usar printf de C
    _ = c.printf("Hola desde C! %d\n", @as(c_int, 42));

    // Usar strlen
    const texto = "Zig y C juntos";
    const len = c.strlen(texto);
    std.debug.print("Longitud: {d}\n", .{len});
}

Tipos C en Zig

const std = @import("std");

// Equivalencias de tipos
const mi_int: c_int = 42;           // int
const mi_long: c_long = 100;        // long
const mi_uint: c_uint = 255;        // unsigned int
const mi_char: u8 = 'A';            // char
const mi_size: usize = 1024;        // size_t

// Punteros
const c_string: [*:0]const u8 = "Hola C";  // char*
const void_ptr: ?*anyopaque = null;         // void*

pub fn main() void {
    std.debug.print("int: {d}, long: {d}\n", .{ mi_int, mi_long });
}

Llamar Funciones C

const std = @import("std");
const c = @cImport({
    @cInclude("math.h");
});

pub fn main() void {
    // Funciones matemáticas de C
    const x: f64 = 2.0;

    const raiz = c.sqrt(x);
    const seno = c.sin(std.math.pi / 2.0);
    const potencia = c.pow(x, 3.0);

    std.debug.print("sqrt(2) = {d}\n", .{raiz});
    std.debug.print("sin(π/2) = {d}\n", .{seno});
    std.debug.print("2³ = {d}\n", .{potencia});
}

Exportar Funciones a C

const std = @import("std");

// Función exportada con convención C
export fn sumar_c(a: c_int, b: c_int) c_int {
    return a + b;
}

// Con nombre personalizado
export fn zig_multiplicar(a: c_int, b: c_int) c_int {
    return a * b;
}

// Callback compatible con C
export fn mi_callback(datos: ?*anyopaque) callconv(.C) void {
    if (datos) |ptr| {
        const valor: *i32 = @ptrCast(@alignCast(ptr));
        std.debug.print("Callback con valor: {d}\n", .{valor.*});
    }
}

Structs Compatibles con C

const std = @import("std");

// Struct con layout C
const Punto = extern struct {
    x: f64,
    y: f64,
};

const Rectangulo = extern struct {
    origen: Punto,
    ancho: f64,
    alto: f64,
};

// Packed para control exacto de bits
const Flags = packed struct {
    activo: bool,
    visible: bool,
    seleccionado: bool,
    _padding: u5,
};

export fn crear_punto(x: f64, y: f64) Punto {
    return Punto{ .x = x, .y = y };
}

export fn area_rectangulo(rect: *const Rectangulo) f64 {
    return rect.ancho * rect.alto;
}

pub fn main() void {
    const p = crear_punto(10.0, 20.0);
    std.debug.print("Punto: ({d}, {d})\n", .{ p.x, p.y });

    const rect = Rectangulo{
        .origen = p,
        .ancho = 100,
        .alto = 50,
    };
    std.debug.print("Área: {d}\n", .{area_rectangulo(&rect)});
}

Manejo de Memoria con C

const std = @import("std");
const c = @cImport({
    @cInclude("stdlib.h");
    @cInclude("string.h");
});

pub fn main() !void {
    // Asignar memoria con malloc de C
    const size: usize = 100;
    const ptr: [*]u8 = @ptrCast(c.malloc(size) orelse return error.OutOfMemory);
    defer c.free(ptr);

    // Inicializar con memset
    _ = c.memset(ptr, 0, size);

    // Copiar datos
    const mensaje = "Hola desde Zig";
    _ = c.memcpy(ptr, mensaje.ptr, mensaje.len);

    // Convertir a slice de Zig
    const slice: []u8 = ptr[0..mensaje.len];
    std.debug.print("Contenido: {s}\n", .{slice});
}

Allocator que usa C

const std = @import("std");
const c = @cImport({
    @cInclude("stdlib.h");
});

const c_allocator = std.mem.Allocator{
    .ptr = undefined,
    .vtable = &.{
        .alloc = cAlloc,
        .resize = cResize,
        .free = cFree,
    },
};

fn cAlloc(_: *anyopaque, len: usize, _: u8, _: usize) ?[*]u8 {
    return @ptrCast(c.malloc(len));
}

fn cResize(_: *anyopaque, _: []u8, _: u8, _: usize, _: usize) bool {
    return false; // C no soporta resize in-place
}

fn cFree(_: *anyopaque, buf: []u8, _: u8, _: usize) void {
    c.free(buf.ptr);
}

pub fn main() !void {
    // Usar el allocator de C con estructuras de Zig
    var lista = std.ArrayList(i32).init(c_allocator);
    defer lista.deinit();

    try lista.append(1);
    try lista.append(2);
    try lista.append(3);

    std.debug.print("Lista: {any}\n", .{lista.items});
}

Testing de Interop

const std = @import("std");
const testing = std.testing;
const c = @cImport({
    @cInclude("string.h");
    @cInclude("stdlib.h");
});

const Punto = extern struct {
    x: f64,
    y: f64,

    pub fn distancia(self: Punto, otro: Punto) f64 {
        const dx = self.x - otro.x;
        const dy = self.y - otro.y;
        return @sqrt(dx * dx + dy * dy);
    }
};

fn duplicarStringC(s: [*:0]const u8) ![]u8 {
    const len = c.strlen(s);
    const allocator = testing.allocator;

    const copia = try allocator.alloc(u8, len);
    _ = c.memcpy(copia.ptr, s, len);

    return copia;
}

fn compararStringsC(a: []const u8, b: []const u8) bool {
    if (a.len != b.len) return false;
    return c.memcmp(a.ptr, b.ptr, a.len) == 0;
}

test "tipos C básicos" {
    const i: c_int = 42;
    const l: c_long = 1000;

    try testing.expectEqual(@as(c_int, 42), i);
    try testing.expectEqual(@as(c_long, 1000), l);
}

test "strlen de C" {
    const texto: [*:0]const u8 = "Hola mundo";
    const len = c.strlen(texto);

    try testing.expectEqual(@as(usize, 10), len);
}

test "memcmp de C" {
    const a = "abc";
    const b = "abc";
    const d = "abd";

    try testing.expectEqual(@as(c_int, 0), c.memcmp(a.ptr, b.ptr, 3));
    try testing.expect(c.memcmp(a.ptr, d.ptr, 3) < 0);
}

test "extern struct layout" {
    // Verificar que el struct tiene layout de C
    try testing.expectEqual(@as(usize, 16), @sizeOf(Punto));

    const p = Punto{ .x = 3.0, .y = 4.0 };
    try testing.expectEqual(@as(f64, 3.0), p.x);
}

test "método en extern struct" {
    const p1 = Punto{ .x = 0, .y = 0 };
    const p2 = Punto{ .x = 3, .y = 4 };

    try testing.expectEqual(@as(f64, 5.0), p1.distancia(p2));
}

test "duplicar string con funciones C" {
    const original: [*:0]const u8 = "Test string";
    const copia = try duplicarStringC(original);
    defer testing.allocator.free(copia);

    try testing.expectEqualStrings("Test string", copia);
}

test "comparar strings con memcmp" {
    try testing.expect(compararStringsC("hola", "hola"));
    try testing.expect(!compararStringsC("hola", "mundo"));
    try testing.expect(!compararStringsC("hola", "hol"));
}

Build con Bibliotecas C

En tu build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "mi_app",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // Linkear con biblioteca C
    exe.linkLibC();

    // Linkear biblioteca específica
    exe.linkSystemLibrary("m"); // libm (math)
    exe.linkSystemLibrary("pthread");

    // Agregar ruta de includes
    exe.addIncludePath(b.path("include"));

    b.installArtifact(exe);
}

Ejercicios

  1. Crea un wrapper de Zig para una función C de tu elección
  2. Implementa un struct que sea compatible con una estructura C existente
  3. Escribe una biblioteca en Zig que pueda ser llamada desde C

Resumen