Let’s say I want to interop with some external C/C++ libraries which has their own memory management and they expose an allocation function:
// allocates memory of the specified size
// and returns the pointer to the memory
void* externalAlloc(size_t size)
then later on, just before the library wants to free the memory, it will invoke a callback with the pointer of the allocated memory, so we can do our cleanup:
void onFinalize(void* data)
so here are some example code in plain C for such external library (modified from lua embedding examples):
int main() {
// allocate memory
DIR **d = (DIR **) externalAlloc(sizeof(DIR *));
// set the value into the memory
*d = opendir("some/path");
// ...(omitted) also somehow set up the finalize callback ...
}
void* onFinalize(void* data) {
// the library gives us back the data pointer
// we cast it back to proper types and do cleanup
DIR **d = (DIR **)data;
closedir(*d);
}
And my question is how to do the equivalent in Haxe, I tried the following (long code, may need to scroll):
@:native('externalAlloc')
// Q: not quite sure if the return type should be
// `cpp.Star` or `cpp.RawPointer` or `cpp.Pointer`
extern function externalAlloc<T>(size:cpp.SizeT):cpp.Pointer<T>;
static function main() {
allocate();
}
static function allocate() {
// prepare a haxe value
var foo = Foo();
// allocate memory externally
var ptr:cpp.Pointer<Foo> = externalAlloc();
// store the haxe value in the externally allocated memory
// Q: not sure if this is the correct way
cpp.Native.set((cast ptr:cpp.Star<Foo>), foo);
// normally the foo value will be ready for GC
// after this function scope because it is a local var
// so we somehow need to let the GC know that we want to keep it
// Q: not sure if this is the correct way
untyped __cpp__('GCAddRoot( (hx::Object **)&{0}.mPtr )', foo);
// alternatively, I think we can also store the value in some
// global/static Haxe data structure, such as a Map, to retain it
// globalMap.set(foo, true)
// ...(omitted) also somehow set up the finalize callback ...
}
// Q: again, not sure about the type of the argument
static function onFinalize(data:cpp.RawPointer<Void>) {
// Q: not sure if this the correct way to get the haxe value back
final foo = cpp.Native.get((cast data:cpp.Star<Foo>));
// reverse whatever done to retain the value:
untyped __cpp__('GCRemoveRoot( (hx::Object **)&{0}.mPtr )', foo);
// alternatively:
// globalMap.remove(foo);
}
class Foo {}
Observation:
if the allocation is called multiple times like below, it will either get stuck, or seg fault.
static function main() {
for(i in 0...100) allocate();
}
I also noticed that the following code will always show the same address for every invocation of allocate()
, not sure if that makes sense.
// inside `allocate()`
untyped __cpp__('printf("&{0}.mPtr = 0x%p\\n", &{0}.mPtr)', foo);
There are virtually no documentation for the API used here (cpp.Pointer, cpp.Native, GCAddRoot, etc). I’m struggling to wrap my head around all these. Could someone shed some light on it for me please?
References: