Skip to content

Delegates on kotlin #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
gabriel-kozma opened this issue Mar 20, 2025 · 9 comments
Open

Delegates on kotlin #101

gabriel-kozma opened this issue Mar 20, 2025 · 9 comments
Labels
enhancement New feature or request generator

Comments

@gabriel-kozma
Copy link

Hi there, last week, I've posted this question and from what I've seen on the kotlin generated code, it doesn't generate the delegate invocation in kotlin

So this approach wouldn't work in Kotlin, is that correct or is there a workaround?

@lemonmojo
Copy link
Member

@gabriel-kozma That's correct. Delegates are currently not implemented in the Kotlin code generator. JNA does support mapping C function pointers though so should at least be possible to add.

@gabriel-kozma
Copy link
Author

sounds good, so the best course of action, would be for me to manually map them?

@lemonmojo
Copy link
Member

@gabriel-kozma Well, best would be to implement Delegates in the Kotlin code generator. But yes, manual mapping is a good start as well. 😉

Btw: Here's the issue where we track To-Do's related to Kotlin.

@lemonmojo lemonmojo added enhancement New feature or request generator labels Mar 20, 2025
@gabriel-kozma
Copy link
Author

@lemonmojo I'm trying to do exactly that hahahaha

Do you have any insights on the best way to do it,

@lemonmojo
Copy link
Member

@gabriel-kozma I recently created some manual Kotlin/JNA bindings for a C API that includes function pointers.
Here's the C definition: https://github.com/royalapplications/royalvnc/blob/17a022cc68586c802a3e4dc0937a947a54e6610c/Sources/RoyalVNCKitC/include/RoyalVNCKitC.h#L76
And here's the Kotlin binding: https://github.com/royalapplications/royalvnc/blob/17a022cc68586c802a3e4dc0937a947a54e6610c/Bindings/kotlin/RoyalVNCAndroidTest/app/src/main/java/com/royalapps/royalvnc/RoyalVNCKit.kt#L141

Hope that helps!

@gabriel-kozma
Copy link
Author

That helps!

@gabriel-kozma
Copy link
Author

gabriel-kozma commented Apr 9, 2025

hi @lemonmojo I have this branch I've created for implementing this feature

https://github.com/gabriel-kozma/beyondnet/tree/feature/android-delegates

main...gabriel-kozma:beyondnet:feature/android-delegates

I was able to generate the interfaces, but I'm still unable to use it on code, are you able to shine a light on what I'm doing wrong?

@lemonmojo
Copy link
Member

@gabriel-kozma So let me start by sharing a "trick" that is helpful during development of the generator. There's a sample project in the repository under Samples/Beyond.NET.Sample.Managed. You can build this using the publish_managed script in the same folder. Then, from your IDE create a launch/debug config that sets the working directory to Samples and the program arguments to Beyond.NET.Sample_Config.json.

Now, when you run the generator from within the IDE, the generated code for the samples will be updated in the Samples/Generated folder and since those files are checked into the repository, you can use your favorite git client to easily compare the changes in the generated code.

Image

Now for the actual new generated code...

Generally, Kotlin requires 2 separate projections of the .NET APIs:

  1. "Low-level" JNA wrapper. This is basically a direct translation from the C header; except for primitive types, we're dealing with pointers here. This API is not intended to be used by the consumer.
  2. "High-level" object-oriented Kotlin wrapper that sits above the JNA code. This is the "nice" API that's intended for use by the consumer and is very similar to the .NET class model.

The reason for this split is that unlike Swift, Kotlin cannot directly interface with C code. It requires some kind of wrapper (pretty much like DllImport in C#). This can be achieved using JNA or the more complicated alternative JNI. We're using JNA in Beyond.NET because it's easier to integrate.

So that's why you'll see KotlinSyntaxWriterConfiguration.GenerationPhases.JNA or KotlinSyntaxWriterConfiguration.GenerationPhases.KotlinBindings being used in KotlinCodeGenerator and it's descendants.

Without the low-level JNA wrapper we can't build the high-level API. So first you have to hook into the JNA generator phase and generate all the code necessary to use the low-level C APIs.

The callback interfaces that you already have are one part of this. There are two things that are not implemented correctly right now for them:

  1. The return types are high-level Kotlin types instead of the low-level JNA types (pointers).
  2. They're all missing the context parameter.

Let's use System.Action as an example.

The C typedef looks like this (you can find this in Samples/Generated/Generated_C.h):

typedef void (*System_Action_CFunction_t)(
	void* context
);

What you're currently generating though doesn't match the C API:

interface ISystem_Action : Callback {
    fun System_Action() : System_Void
}

Once you fix that, you'll have to look into generating JNA definitions for the various APIs that each delegate exposes in C:

#pragma mark - BEGIN APIs of System.Action
System_Action_t _Nonnull /* System.Action */
System_Action_Create(
	const void* context,
	System_Action_CFunction_t function,
	System_Action_CDestructorFunction_t destructorFunction
);

void
System_Action_Invoke(
	System_Action_t /* System.Action */ self, System_Exception_t* /* System.Exception */ outException
);

const void*
System_Action_Context_Get(
	System_Action_t /* System.Action */ self
);

System_Action_CFunction_t
System_Action_CFunction_Get(
	System_Action_t /* System.Action */ self
);

System_Action_CDestructorFunction_t
System_Action_CDestructorFunction_Get(
	System_Action_t /* System.Action */ self
);

System_Type_t _Nonnull /* System.Type */
System_Action_TypeOf(
	void
);

void /* System.Void */
System_Action_Destroy(
	System_Action_t _Nullable /* System.Action */ self
);

#pragma mark - END APIs of System.Action

Once you have these wrapped in JNA code, you should be able to:

  1. Write an implementation for a type that implements the callback.
  2. Pass it to System_Action_Create.
  3. Call System_Action_Invoke.
  4. Have it call into your Kotlin callback function.

Once you have all of this in place, let me know and I can guide you through the next steps like memory management (the context and destructor functions of delegates) and eventually how to wrap all of this in a nice object-oriented Kotlin API.

Good luck! 🍀

@gabriel-kozma
Copy link
Author

@lemonmojo , thank you!

I was following the signature of the methods you've sent before, but now I've realized that i was missing the context

I'll make the required changes and generate neccessary code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request generator
Projects
None yet
Development

No branches or pull requests

2 participants