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
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig e C. Consulte a introdução ao Zig
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.