Skip to content

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_VERSION at load time.
  • Model every opaque handle as an opaque pointer in the host language.
  • Treat gln_backend_result_t as 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:

  • Backend drops with gln_close_backend.
  • BackendResult drops with gln_destroy_backend_result.
  • Continuation drops with gln_destroy_continuation.
  • Secret drops with gln_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