Pular para o conteúdo

Callbacks: o C chamando você

Uma ação DataForge vista pelo C como ponteiro de função — com tempo de vida explícito, porque um callback coletado derruba o processo.

Metade das bibliotecas C úteis pede um ponteiro de função: qsort pede o comparador, a libcurl pede o recebedor, a libz pede o alocador. C.retorno_de_chamada transforma uma ação da linguagem nisso.

dataforge
adopt Arcane.C as C

libc := C.padrao()
qsort := libc.funcao("qsort",
                     ["ponteiro", "tamanho", "tamanho", "ponteiro"], "void")

numeros := [42, 7, 19, 3]
bloco := C.alocar(len(numeros) * C.tamanho_de("i32"))
p := C.ponteiro(bloco, "i32")
cycle i in range(0, len(numeros)):
    p.deslocar(i).escrever(numeros[i])

action comparar(a, b):
    yield C.ponteiro(a, "i32").ler() - C.ponteiro(b, "i32").ler()

comparador := C.retorno_de_chamada(comparar, ["ponteiro", "ponteiro"], "i32")
qsort(bloco, 4, C.tamanho_de("i32"), comparador)

assert [p.deslocar(i).ler() cycle i in range(0, 4)] is [3, 7, 19, 42]
C.liberar(bloco)

O qsort do C chamou uma ação escrita em DataForge, quatro a seis vezes, e ordenou memória crua com o resultado.

Tempo de vida: o detalhe que derruba processo#

O callback precisa continuar vivo enquanto o C puder chamá-lo. Um callback coletado no meio de um qsort derruba o processo — e a pilha não fala do DataForge. Por isso ele é um objeto que se segura, e que responde se ainda vale.

dataforge
adopt Arcane.C as C

action dobro(x):
    yield x * 2

cb := C.retorno_de_chamada(dobro, ["i32"], "i32")

assert cb.vivo()
assert cb.chamar(21) is 42           // chama pela ponte do C
assert cb.endereco() bigger 0        // é um ponteiro de função de verdade

cb.soltar()
assert not cb.vivo()
Item da literaturaAqui
function pointercb.endereco() — e o próprio cb passa como ponteiro
closure como callbacka ação leva o fechamento dela; o C não sabe disso, e não precisa
context pointer (void* user_data)declare um ponteiro a mais na assinatura e leia-o dentro da ação
static trampolineé o que o ctypes monta por baixo — não há o que escrever
callback lifecycleexplícito: vivo() e soltar()
ABI-safea assinatura é declarada; errar o tipo aqui é o mesmo desastre de errar no C