FFI Examples
Binding patterns for the current Galanthus C ABI.
Binding Rules
- Generate or mirror declarations from the installed
gln_capi.h. - Check
gln_get_api_version() == GLN_API_VERSIONat load time. - Model every opaque handle as an opaque pointer in the host language.
- Treat
gln_backend_result_tas the owner of borrowed result views. - Release each library-owned object with its matching
gln_*release, destroy, or close function. - Confine each handle, secret, continuation, and result envelope to a single thread. Distinct handles may be used from distinct threads concurrently.
C Backend Pattern
#include <galanthus/c_api/gln_capi.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
gln_error_t error = {0};
gln_default_error(&error);
gln_status_t s = gln_init_runtime(NULL, &error);
if (s != GLN_OK) {
gln_release_error(&error);
return 1;
}
if (gln_get_api_version() != GLN_API_VERSION) {
gln_shutdown_runtime();
return 1;
}
gln_state_store_t* state_store = NULL;
s = gln_create_file_state_store("alice.state", NULL, &state_store, &error);
if (s != GLN_OK) {
fprintf(stderr, "%s\n", error.message ? error.message : "");
gln_release_error(&error);
gln_shutdown_runtime();
return 1;
}
gln_fints_config_t config = {0};
gln_default_fints_config(&config);
config.endpoint = "https://bank.example/fints";
config.bank_code = "12345678";
config.user_id = "alice";
config.product_id = "GALANTHUSDEMO1234567890";
gln_secret_t* pin = NULL;
const char pin_text[] = "1234";
s = gln_create_secret((const uint8_t*)pin_text, strlen(pin_text), &pin);
if (s != GLN_OK) {
gln_destroy_state_store(state_store);
gln_shutdown_runtime();
return 1;
}
gln_backend_t* backend = NULL;
s = gln_open_fints_backend(&config, state_store, NULL, pin, &backend, &error);
gln_destroy_secret(pin);
if (s != GLN_OK) {
fprintf(stderr, "%s\n", error.message ? error.message : "");
gln_release_error(&error);
gln_destroy_state_store(state_store);
gln_shutdown_runtime();
return 1;
}
gln_backend_result_t* result = NULL;
s = gln_retrieve_accounts(backend, &result);
if (s == GLN_OK && gln_get_backend_result_outcome(result) == GLN_BACKEND_OUTCOME_SUCCESS) {
const gln_accounts_t* accounts = gln_get_backend_result_accounts(result);
for (size_t i = 0; i < gln_get_accounts_count(accounts); ++i) {
const gln_account_t* account = gln_get_account_at(accounts, i);
printf("%s %s\n",
gln_get_account_iban(account),
gln_get_account_owner(account) ? gln_get_account_owner(account) : "");
}
}
else if (s == GLN_OK && gln_get_backend_result_outcome(result) == GLN_BACKEND_OUTCOME_ACTION_REQUIRED) {
gln_continuation_t* continuation = gln_take_backend_result_continuation(result);
gln_destroy_continuation(continuation);
}
else if (s == GLN_OK) {
const gln_error_t* result_error = gln_get_backend_result_error(result);
fprintf(stderr, "%s\n",
result_error && result_error->message ? result_error->message : "");
}
gln_destroy_backend_result(result);
gln_close_backend(backend);
gln_destroy_state_store(state_store);
gln_shutdown_runtime();
return 0;
}
The cleanup order is result envelope, backend, stores, shutdown. Borrowed typed views and strings are copied before the envelope is destroyed.
Python ctypes
Use ctypes.CDLL for the shared library. On Windows this matches the C calling
convention used by GLN_CALL.
Opaque handles should be ctypes.c_void_p. For T** out_handle parameters,
pass a ctypes.c_void_p() by reference and wrap the resulting integer pointer
in a small owner object whose finalizer calls the matching destroy function.
Do not bind library-allocated char* outputs as restype = ctypes.c_char_p.
That converts the pointer to Python bytes and loses the original allocation.
Use a ctypes.c_void_p slot, copy the UTF-8 bytes, then call
gln_release_string:
def take_string(lib, ptr):
if not ptr:
return None
try:
return ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.gln_release_string(ptr)
For borrowed strings returned from row accessors, copy the Python string while
the owning gln_backend_result_t is still alive and do not release the borrowed
pointer.
Mirror structs exactly from gln_capi.h, including struct_size fields, and
initialize public fixed-size structs through their gln_default_* helpers. For
example, a binding-created gln_error_t must be initialized with
gln_default_error before calling functions that populate it.
Rust
Prefer bindgen against the installed gln_capi.h for layout fidelity. A
small hand-written unsafe extern "C" block is workable for a narrow subset,
but copy function signatures and struct field order exactly.
A safe wrapper should own each opaque pointer and implement Drop:
Backenddrops withgln_close_backend.BackendResultdrops withgln_destroy_backend_result.Continuationdrops withgln_destroy_continuation.Secretdrops withgln_destroy_secret.- Store wrappers drop with their matching store destroy function.
Typed views borrowed from BackendResult should not escape the BackendResult
lifetime. Convert them to owned Rust values before dropping the envelope.
Go
Use cgo with the public header on the include path:
package main
/*
#cgo LDFLAGS: -lgalanthus
#include <galanthus/c_api/gln_capi.h>
*/
import "C"
func main() {
if C.gln_get_api_version() != C.GLN_API_VERSION {
return
}
}
gln_get_library_version() returns a borrowed static string; do not release it.
Caller-owned result strings from char** out parameters still need
C.gln_release_string after copying. Borrowed typed-accessor strings under a
gln_backend_result_t do not need release and become invalid when the envelope
is destroyed.
C# P/Invoke
Use IntPtr for opaque handles and borrowed pointers. Mirror structs with
StructLayout(LayoutKind.Sequential) and include struct_size fields. For
library-owned char* outputs, copy with Marshal.PtrToStringUTF8 and call
gln_release_string. For borrowed row strings, copy while the backend result
envelope is alive and do not release the borrowed pointer.
Declare destroy and release functions as returning void. Wrap each owned
IntPtr in SafeHandle or an equivalent owner so exceptional paths still
release handles.
Common Pitfalls
Treating the function return as the operation result. For backend
operations, GLN_OK means an envelope exists. Check
gln_get_backend_result_outcome and gln_get_backend_result_status to learn whether
the provider operation succeeded, paused, was rejected, or failed.
Destroying the envelope too early. All typed views and borrowed strings
from gln_get_backend_result_*, collection accessors, row accessors, error
accessors, and interrupt accessors live under the envelope. Copy first, destroy
afterward.
Forgetting to take a continuation. An action-required envelope owns its
continuation until gln_take_backend_result_continuation transfers it. Destroy
or save the taken continuation yourself.
Freeing with the host allocator. Use gln_release_string,
gln_release_buffer, gln_release_error, or the
matching destroy function. Never call free, delete, or a host runtime free
on library allocations.
Mismatching vtable callback ownership. Store and key-store callbacks allocate output buffers; the library returns those buffers through the callback free function. Callback inputs are borrowed and valid only during the callback.
Sharing one handle across threads. Each handle, secret, continuation, and
result envelope is single-threaded. Confine an individual handle to one
thread; distinct handles may run in parallel. gln_init_runtime and
gln_shutdown_runtime must not interleave with operations.
See Also
- Conventions - status, envelopes, ownership, and threading.
- Continuations - resume flow.
- Stores - store callback ownership.
- Key store - EBICS key-store callbacks.
- Troubleshooting - failure-mode checklist.