call back to LCB from C code?
Posted: Sun Mar 08, 2026 8:09 pm
I was wondering if the C code dylib can call back into the LCB?
Questions and answers about the LiveCode platform.
https://forums.livecode.com/
I really don't know enough about LCB (few here do...)Emily-Elizabeth wrote: ↑Sun Mar 08, 2026 8:09 pmI was wondering if the C code dylib can call back into the LCB?
Claude AI wrote:Yes, C code in a dylib can call back into LCB, through handler references passed as function pointers.
The mechanism works like this:
On the LCB side, you declare a handler type that matches the C callback signature, then pass an LCB handler as a parameter to the foreign function:Key points to be aware of:Code: Select all
-- Declare the callback signature handler type MyCallbackType(in pValue as Integer) returns nothing -- Declare the foreign function that accepts a callback foreign handler C_DoSomethingWithCallback(in pCallback as MyCallbackType) \ returns nothing \ binds to "c:mylib>DoSomethingWithCallback" -- Your LCB handler that will be called from C handler MyCallback(in pValue as Integer) -- this runs when C calls back log pValue end handler -- Wire it up public handler CallIntoC() C_DoSomethingWithCallback(MyCallback) end handler On the C side, the dylib receives a plain function pointer it can invoke normally: ctypedef void (*MyCallbackType)(int value); void DoSomethingWithCallback(MyCallbackType callback) { // Do some work... callback(42); // calls back into LCB }
The LCB handler you pass must match the handler type signature exactly
The callback happens synchronously on the same thread — be cautious if your C code calls back from a different thread, as LCB is not thread-safe in that context
If you need asynchronous callbacks (e.g. from a C thread), you'll need to marshal the call back to the main thread, typically by posting a message or using a queue mechanism, which gets considerably more involved
Memory management: the LCB runtime keeps the handler alive as long as the reference exists, but if C holds onto the pointer longer than the LCB side expects, you can get dangling pointer issues
Claude AI wrote: Async callbacks are the tricky case because you cannot call the LCB handler directly from a C thread — it will crash or corrupt state. You need to marshal the call back onto LCB's main thread.
There are two practical approaches:
Option 1: Polling (Simpler)
C side maintains a thread-safe queue. LCB polls it on a timer.
C side:Code: Select all
#include <pthread.h> #include <stdlib.h> // Thread-safe queue static pthread_mutex_t s_mutex = PTHREAD_MUTEX_INITIALIZER; static int s_pending_value = 0; static int s_has_pending = 0; // Called from background thread void WorkerThread(void *arg) { // ... do async work ... int result = 42; pthread_mutex_lock(&s_mutex); s_pending_value = result; s_has_pending = 1; pthread_mutex_unlock(&s_mutex); } // LCB calls this on its timer to drain the queue int PollForResult(int *out_value) { pthread_mutex_lock(&s_mutex); int has = s_has_pending; if (has) { *out_value = s_pending_value; s_has_pending = 0; } pthread_mutex_unlock(&s_mutex); return has; }
LCB side:Code: Select all
foreign handler C_PollForResult(out pValue as Integer) \ returns Integer \ binds to "c:mylib>PollForResult" handler type MyCallbackType(in pValue as Integer) returns nothing private variable mCallback as optional MyCallbackType private variable mTimer as optional Timer public handler StartAsync(in pCallback as MyCallbackType) put pCallback into mCallback -- poll every 50ms create timer set the period of the result to 50 set the action of the result to MyPollHandler put the result into mTimer end handler handler MyPollHandler() variable tValue as Integer if C_PollForResult(tValue) is 1 then if mCallback is not nothing then mCallback(tValue) end if end if end handler
Option 2: MCEngineRunloopScheduleCallback (Cleaner)
The LC engine exposes a function that lets C schedule work onto the main thread's runloop — no polling needed.
C side:Code: Select all
// Declare the engine's scheduling function // (available when loaded inside the LC engine process) typedef void (*MCEngineRunloopCallback)(void *context); extern void MCEngineRunloopScheduleCallback( MCEngineRunloopCallback callback, void *context); typedef void (*LCBHandlerType)(int value); typedef struct { LCBHandlerType lcb_handler; int value; } CallbackContext; // Called on main thread by the engine runloop static void MainThreadCallback(void *context) { CallbackContext *ctx = (CallbackContext *)context; ctx->lcb_handler(ctx->value); // safe — we're on main thread now free(ctx); } // Called from background thread void WorkerThread(void *arg) { LCBHandlerType handler = (LCBHandlerType)arg; // ... do async work ... int result = 42; CallbackContext *ctx = malloc(sizeof(CallbackContext)); ctx->lcb_handler = handler; ctx->value = result; // Marshal back to main thread MCEngineRunloopScheduleCallback(MainThreadCallback, ctx); }
LCB side:Code: Select all
handler type MyCallbackType(in pValue as Integer) returns nothing foreign handler C_StartWorker(in pCallback as MyCallbackType) \ returns nothing \ binds to "c:mylib>StartWorker" public handler StartAsync(in pCallback as MyCallbackType) C_StartWorker(pCallback) -- no timer needed; engine calls back when ready end handler
Thank you for the kind words. Check out my website for a few goodies that I've already made (just rename the oxtstack to livecodestack).
Seems like it is.NF is the extension code for Norfolk Island.