umka-lang icon indicating copy to clipboard operation
umka-lang copied to clipboard

Callbacks lack a `user_data` parameter

Open Readf0x opened this issue 3 weeks ago • 5 comments

Without a void *user_data parameter in callback registrars like umkaAddFunc, registered functions can only interact with global variables. This is a huge issue for me, since I was working on golang bindings for umka, and cgo is, quite frankly, limiting in what data can be sent.

Readf0x avatar Dec 02 '25 19:12 Readf0x

@Readf0x You can get the Umka instance with umkaGetInstance(). Then you can store in it and retrieve from it any user data with umkaSetMetadata()/umkaGetMetadata().

This is a per-instance, rather than a per-function storage. However, I would use it, e.g., for storing a C++ this pointer on which I called a method from a function in question.

vtereshkov avatar Dec 03 '25 00:12 vtereshkov

@vtereshkov Unfortunate that it's per instance, as binding user supplied go functions a C callback requires a shim go function exported to C that can use runtime info to determine what function is currently being called. Is there any way you know of to do this? Even just getting the module and function name should be sufficient.

Readf0x avatar Dec 03 '25 00:12 Readf0x

@Readf0x Can you provide a code example? Without it, I can hardly see what you are trying to get or why the per-instance data storage is not enough.

vtereshkov avatar Dec 03 '25 01:12 vtereshkov

@vtereshkov

The typical way to do this is something like

void foo(void *callback, void *user_data) {
	// example
	callback(20, user_data)
}
/*
#include "foo.h"

extern void goCallback(int i, void *user_data);
*/
import "C"

import "unsafe"

var store = map[unsafe.pointer]any{}

func register(f func(i int)) {
	var ptr unsafe.pointer = C.malloc(C.size_t(1))
	store[ptr] = f
	C.foo(C.goCallback, ptr)
}

//export goCallback
func goCallback(i int, user_data unsafe.pointer) {
	store[user_data](i)
}

The unsafe pointer prevent the garbage collector from cleaning it up, and the user_data is used to actually get the function we're running. You're not allowed to pass go functions to c functions for... some reason. But with umka...

/*
#cgo CFLAGS: -I .
#cgo LDFLAGS: -lm -Wl,-Bstatic -L/run/current-system/sw/lib -lumka -Wl,-Bdynamic

#include "umka_api.h"

extern void externFuncShim(UmkaStackSlot *params, UmkaStackSlot *result);
*/
import "C"

type UmkaExternFunc func(params []any, result any)

func (u Umka) AddFunc(name string, fn UmkaExternFunc) *UmkaError {
	success := bool(C.umkaAddFunc(
		u.alloc,
		C.CString(name),
		C.externFuncShim,
	))
	if !success { return u.GetError() }
	return nil
}

//export externFuncWrapper
func externFuncShim(params, result *C.UmkaStackSlot) {
	// how do we get current_function?
	if current_function != nil {
		// need to convert params and result to correct values
		// current_function(/*?, ?*/)
	}
}

I have no way to determine what function I'm supposed to be calling at the moment, even if I store it somewhere, what information do I have to discern it?

Readf0x avatar Dec 03 '25 01:12 Readf0x

@Readf0x So your idea is to register the same C function externFuncShim() under all possible Umka names. At this point, you, fairly enough, lose the information about what Umka function it actually was. Then you want to recover this information from within this C function by passing some reflection metadata there.

It's quite a convoluted use case I never had in mind. I'll come back when I have a fix or a reasonable workaround.

vtereshkov avatar Dec 03 '25 14:12 vtereshkov