Chamar Funções C de Zig

Introdução

A interoperabilidade com C é uma das features mais poderosas de Zig. Com @cImport e @cInclude, você pode importar headers C diretamente e chamar funções C sem escrever bindings manuais. Esta receita cobre os padrões mais comuns.

Para o tutorial completo, veja Interoperabilidade C-Zig. Para conversão de ponteiros, consulte Converter Ponteiros C para Zig. Para portar bibliotecas inteiras, veja Portar uma Biblioteca C para Zig.

Pré-requisitos

Importar Header C

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

pub fn main() void {
    _ = c.printf("Olá do C! %d\n", @as(c_int, 42));
}

Chamar Funções da libc

Funções de String

const c = @cImport(@cInclude("string.h"));

fn comprimentoC(str: [*:0]const u8) usize {
    return c.strlen(str);
}

pub fn main() void {
    const texto: [*:0]const u8 = "Zig Brasil";
    const len = comprimentoC(texto);
    std.debug.print("Comprimento: {}\n", .{len});
}

Funções Matemáticas

const c = @cImport(@cInclude("math.h"));

fn hipotenusa(a: f64, b: f64) f64 {
    return c.hypot(a, b);
}

test "hipotenusa" {
    const resultado = hipotenusa(3.0, 4.0);
    try std.testing.expectApproxEqAbs(@as(f64, 5.0), resultado, 0.001);
}

Usar Biblioteca do Sistema

SQLite

const c = @cImport(@cInclude("sqlite3.h"));

pub fn main() !void {
    var db: ?*c.sqlite3 = null;

    const rc = c.sqlite3_open(":memory:", &db);
    if (rc != c.SQLITE_OK) {
        std.debug.print("Erro: {s}\n", .{c.sqlite3_errmsg(db)});
        return error.SqliteError;
    }
    defer _ = c.sqlite3_close(db);

    // Criar tabela
    var err_msg: ?[*:0]u8 = null;
    const sql = "CREATE TABLE teste (id INTEGER PRIMARY KEY, nome TEXT)";
    const rc2 = c.sqlite3_exec(db, sql, null, null, &err_msg);
    if (rc2 != c.SQLITE_OK) {
        std.debug.print("Erro SQL: {s}\n", .{err_msg.?});
        c.sqlite3_free(err_msg);
        return error.SqlError;
    }

    std.debug.print("Banco criado com sucesso!\n", .{});
}

No build.zig:

exe.linkSystemLibrary("sqlite3");
exe.linkLibC();

zlib

const c = @cImport(@cInclude("zlib.h"));

fn obterVersaoZlib() []const u8 {
    const versao = c.zlibVersion();
    return std.mem.span(versao);
}

test "versão do zlib" {
    const versao = obterVersaoZlib();
    try std.testing.expect(versao.len > 0);
}

Veja Compressão com zlib para exemplos completos.

Converter Tipos entre C e Zig

Strings

// Zig string para C string
fn zigParaC(allocator: std.mem.Allocator, zig_str: []const u8) ![*:0]u8 {
    return allocator.dupeZ(u8, zig_str);
}

// C string para Zig string (slice)
fn cParaZig(c_str: [*:0]const u8) []const u8 {
    return std.mem.span(c_str);
}

test "conversão de strings" {
    const allocator = std.testing.allocator;

    const c_str = try zigParaC(allocator, "Zig Brasil");
    defer allocator.free(c_str[0 .. std.mem.len(c_str) + 1]);

    const volta = cParaZig(c_str);
    try std.testing.expectEqualStrings("Zig Brasil", volta);
}

Arrays e Buffers

fn processarComC(dados: []u8) void {
    // Passar slice Zig para função C que espera (ptr, len)
    c.processar(dados.ptr, dados.len);
}

fn receberDeC() ![]u8 {
    var buffer: [1024]u8 = undefined;
    const n = c.ler_dados(&buffer, buffer.len);
    return buffer[0..@intCast(n)];
}

Callbacks

const c = @cImport(@cInclude("minha_lib.h"));

fn meuCallback(dados: ?*anyopaque) callconv(.C) void {
    if (dados) |ptr| {
        const contexto: *Contexto = @ptrCast(@alignCast(ptr));
        contexto.processar();
    }
}

pub fn registrar(contexto: *Contexto) void {
    c.registrar_callback(meuCallback, @ptrCast(contexto));
}

Structs C

const c = @cImport(@cInclude("minha_struct.h"));

// Structs C são importadas automaticamente
fn criarPonto() c.Ponto {
    return c.Ponto{
        .x = 1.0,
        .y = 2.0,
    };
}

// Acessar campos
fn distancia(p: c.Ponto) f64 {
    return @sqrt(p.x * p.x + p.y * p.y);
}

Configuração do 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 = "meu-programa",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // Incluir diretório de headers
    exe.addIncludePath(b.path("c-headers"));

    // Linkar com bibliotecas do sistema
    exe.linkSystemLibrary("sqlite3");
    exe.linkSystemLibrary("z");
    exe.linkLibC();

    // Ou compilar código C junto
    exe.addCSourceFile(.{
        .file = b.path("c-src/minha_lib.c"),
        .flags = &.{ "-std=c11", "-Wall" },
    });

    b.installArtifact(exe);
}

Veja Migrar de Makefile para build.zig e Migrar de CMake para build.zig.

Erros Comuns

1. Esquecer de linkar libc

// Se usar @cImport, geralmente precisa:
exe.linkLibC();

2. Casting de ponteiros incorreto

// ERRADO: direto
const ptr: *MeuTipo = c_ptr;

// CORRETO: com cast e alinhamento
const ptr: *MeuTipo = @ptrCast(@alignCast(c_ptr));

3. Strings sem null terminator

// Zig strings NÃO são null-terminated por padrão
const str: []const u8 = "Zig";
// NÃO passe str.ptr diretamente para funções C que esperam \0

// Use dupeZ para adicionar null terminator
const c_str = try allocator.dupeZ(u8, str);
defer allocator.free(c_str[0..str.len + 1]);

Conclusão

Chamar código C de Zig é direto e seguro. @cImport importa headers automaticamente, e o sistema de tipos de Zig garante conversões corretas entre os mundos C e Zig. Para projetos maiores, considere criar wrappers idiomáticos como descrito em Portar uma Biblioteca C para Zig.

Para mais sobre interop, veja Interoperabilidade C-Zig, Converter Ponteiros C para Zig e Substituir Macros C por Comptime.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.