Chamar C
Arcane.C: abrir uma biblioteca nativa, declarar a assinatura, ler o layout de uma struct e chamar função — sobre ctypes, sem dependência.
A ponte para o Python resolve "preciso de uma biblioteca que alguém já escreveu em Python". Faltava o degrau de baixo: chamar uma função de uma biblioteca C — a libm, a libz, o .so que a empresa mantém há quinze anos — sem escrever módulo de extensão.
adopt Arcane.C as C
libm := C.matematica()
raiz := libm.funcao("sqrt", ["f64"], "f64")
potencia := libm.funcao("pow", ["f64", "f64"], "f64")
assert raiz(16.0) is 4.0
assert potencia(2.0, 10.0) is 1024.0A assinatura é declarada#
funcao(nome, [argumentos], retorno) obriga a dizer os tipos. Adivinhar erra fora do caso comum: um i32 onde o C espera i64 passa em quase toda chamada e corrompe memória no resto — em silêncio, e longe da linha que causou.
| Tipo | No C |
|---|---|
i8 i16 i32 i64 | int8_t … int64_t |
u8 u16 u32 u64 | uint8_t … uint64_t |
f32 f64 | float, double |
bool char | bool, char |
texto | const char* (vai e volta como texto UTF-8) |
bytes | const char* cru, sem conversão |
ponteiro | void* — qualquer endereço |
tamanho | size_t |
void | só como retorno |
adopt Arcane.C as C
libc := C.padrao()
tamanho := libc.funcao("strlen", ["texto"], "tamanho")
absoluto := libc.funcao("abs", ["i32"], "i32")
assert tamanho("dataforge") is 9
assert absoluto(-42) is 42
assert libc.tem("strlen")
assert not libc.tem("nao_existe_mesmo")Quando não dá, a mensagem diz o que fazer#
Biblioteca que não abre e símbolo que não existe são os dois erros mais comuns de FFI — e os dois chegam do sistema ilegíveis. Aqui eles dizem o nome, onde foi procurado e o caminho de saída, por sistema operacional.
adopt Arcane.C as C
monitor:
C.carregar("libnaoexisteaqui")
assert no
handle RuntimeError as e:
assert "libnaoexisteaqui" in e.messageStruct: o layout de verdade#
Tamanho, alinhamento e deslocamento vêm da ABI da plataforma — é a parte que ninguém acerta de cabeça, e a que quebra quando a struct do C muda.
adopt Arcane.C as C
Ponto := C.estrutura([["x", "f64"], ["y", "f64"]])
assert Ponto.tamanho() is 16
assert Ponto.deslocamentos() is {"x": 0, "y": 8}
// o padding aparece: um i8 antes de um i32 não ocupa 5 bytes
Mista := C.estrutura([["flag", "i8"], ["valor", "i32"]])
assert Mista.tamanho() is 8
assert Mista.deslocamentos()["valor"] is 4
assert Mista.alinhamento() is 4adopt Arcane.C as C
Ponto := C.estrutura([["x", "i32"], ["y", "i32"]])
p := Ponto.criar({"x": 3, "y": 4})
assert p.ler("x") is 3
p.escrever("x", 30)
assert p.tudo() is {"x": 30, "y": 4}
// a união ocupa o maior campo
U := C.uniao([["i", "i32"], ["f", "f64"]])
assert U.tamanho() is C.tamanho_de("f64")Um enum do C é inteiro com nome, e aqui ele é um vault — C.enumeracao({"VERMELHO": 0, "VERDE": 1}). Não há tipo novo a criar: o que atravessa a fronteira é o número, e um embrulho só esconderia isso.
O que o sistema responde#
adopt Arcane.C as C
assert C.tamanho_de("i8") is 1
assert C.tamanho_de("i32") is 4
assert C.tamanho_de("f64") is 8
assert C.tamanho_de("ponteiro") in [4, 8]
assert C.endianness() in ["little", "big"]
assert "i32" in C.tipos()