I have created a Kotlin binding for some C library...
# multiplatform
m
I have created a Kotlin binding for some C library via the cinterop tool but I simply cannot figure out how to use this API from a Kotlin program. The problem is that the memory allocation is done on the C side and not on the Kotlin side like in all the examples. I hope somebody can give me a hint on how to set that up correctly. More Details in the tread!
The generated Kotlin API looks like this:
Copy code
fun graal_create_isolate(
	params: CValuesRef<graal_create_isolate_params_t>?,
	isolate: CValuesRef<CPointerVar<graal_isolate_t>>?,
	thread: CValuesRef<CPointerVar<graal_isolatethread_t>>?): Int
	
fun LibEnvMap_filterEnv(
	arg0: CValuesRef<graal_isolatethread_t>?,
	arg1: CValuesRef<ByteVar>?): Int
	
fun graal_tear_down_isolate(
	isolateThread: CValuesRef<graal_isolatethread_t>?): Int
Calling the original library from C is trivial:
Copy code
#include <stdio.h>
#include <stdlib.h>

#include "libenvmap.h"

int main(int argc, char **argv) {
	if (argc != 2) {
		fprintf(stderr, "Usage: %s <filter>\n", argv[0]);
		exit(1);
	}

	graal_isolate_t *isolate = NULL;
	graal_isolatethread_t *thread = NULL;

	if (graal_create_isolate(NULL, &isolate, &thread) != 0) {
		fprintf(stderr, "initialization error\n");
		return 1;
	}

	printf("Number of entries: %d\n", LibEnvMap_filterEnv(thread, argv[1]));

	graal_tear_down_isolate(thread);
}
Now, how do you have to declare and initialize these variables on the Kotlin side?
l
Any C Structs are going to need to be managed by the cinterop API. You’d use alloc methods on an Arena, nativeHeap, or memScoped depending on the scope that you need to access them.
Copy code
memScoped {
    val paramsPtr = allocPointerTo<graal_create_isolate_params_t>()
    val isolatePtr = allocPointerTo<graal_isolate_t>()
    graal_create_isolate(paramsPtr.ptr, isolatePtr.ptr, etc.)
}
Disclaimer: My memory could be a bit off. Let me know if this snippet doesn;t work.
Keep in mind that paramsPtr, isolatePtr, and anything else you directly alloc will be unusable after memScoped completes, so be careful if they leave the scope.
m
Thanks a lot. The method
allocPointerTo
was what I was missing. This is the working result:
Copy code
import kotlinx.cinterop.*
import libenvmap.*

fun main(args : Array<String>) {
	println("### Starting main ###")    
    
	if (args.size == 1) {
		memScoped {		
    		val isolate = allocPointerTo<graal_isolate_t>()
    		val isolateThread = allocPointerTo<graal_isolatethread_t>()

			if (graal_create_isolate(null, isolate.ptr, isolateThread.ptr) != 0) {
				throw RuntimeException("Could not create isolate.")
			}
		
			val entries = LibEnvMap_filterEnv(isolateThread.value, args[0].cstr)
	
			println("Number of entries: ${entries}");
	
			graal_tear_down_isolate(isolateThread.value)
		}
	} else {
    	println("Missing argument!")
	}
	
	println("### Done main ###")    
}
Just in case someone is interested what this exercise was all about. This is a proof of concept of how to compile some Kotlin/JVM code, which makes heavy use of some Java code, into a shared library via GraalVM/Native image which can then be used seamlessly in some other Kotlin native code again. The advantage is that I now do not have to rip apart all my existing Kotlin/JVM code and replace all Java usages with something else in order to go native, e.g. to iOS. The nice thing is that all this library and Kotlin API creation is fully automatic so far without any manual intervention.
l
That’s interesting.