Skip to content
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

Proposal: Managed Source Generator Slim Bindings #1300

Open
jpobst opened this issue Jan 31, 2025 · 2 comments
Open

Proposal: Managed Source Generator Slim Bindings #1300

jpobst opened this issue Jan 31, 2025 · 2 comments
Labels
generator Issues binding a Java library (generator, class-parse, etc.) proposal Issue raised for discussion, we do not even know if the change would be desirable yet

Comments

@jpobst
Copy link
Contributor

jpobst commented Jan 31, 2025

Background

The MAUI Community Toolkit provides guidance on creating "slim bindings" called "Native Method Interop". There are a few downsides to this approach:

  • It requires non-trivial Java knowledge to set up Android Studio, a Java Binding project, and write a Java wrapper API.
  • Integrating the wrapped .jar and potentially its dependencies into the managed app can be tricky, getting its versions to align with .jar files pulled in through NuGet packages.

Once we're already down the path of asking users to write the APIs by hand, could we use Roslyn source generators to allow them to do the same in C#?

Example

Using the example from MAUI Community Toolkit, we could allow the user to write the C# API that matches the Java API to bind:

// Java API to bind
package com.facebook;

public class FacebookSdk {
    public final void setIsDebugEnabled (Boolean value) { ... }
}
// C# user writes
[AndroidBindingType ("com.facebook.FacebookSdk")]
public partial class FacebookSdk : Java.Lang.Object
{
    [AndroidBindingMethod ("setIsDebugEnabled")]
    public partial void SetIsDebugEnabled (bool value);
}

We could then use a Roslyn source generator to fill in the plumbing in a generated partial class:

// Additional C# that gets generated
[global::Android.Runtime.Register ("com/facebook/FacebookSdk", DoNotGenerateAcw=true)]
public partial class FacebookSdk
{
    [Register ("setIsDebugEnabled", "(Z)V", "")]
    public partial unsafe void SetIsDebugEnabled (bool value)
    {
        const string __id = "setIsDebugEnabled.(Z)V";
        try {
            JniArgumentValue* __args = stackalloc JniArgumentValue [1];
            __args [0] = new JniArgumentValue (nonRoot);
            _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args);
        } finally {
        }
    }
}

Vague Typing

This proposal could also incorporate the principles of Vaguely Typed Slim Bindings, allowing the user to bind as much or as little as they want.

Imagine the Java API:

public class Map {
  public final MapPin addPin (int x, int y) { ... }
}

If the user just wants to add the map pin and isn't interested in using the return value, they can bind it as Java.Lang.Object:

[AndroidBindingType ("com.mapsui.Map")]
public partial class Map : Java.Lang.Object
{
    [AndroidBindingMethod ("addPin")]
    public partial Java.Lang.Object AddPin (int x, int y);
}

If they want to interact with the MapPin type, they can provide as much of it as they want:

[AndroidBindingType ("com.mapsui.Map")]
public partial class Map : Java.Lang.Object
{
    [AndroidBindingMethod ("addPin")]
    public partial MapPin AddPin (int x, int y);
}

[AndroidBindingType ("com.mapsui.MapPin")]
public partial class MapPin : Java.Lang.Object
{
    [AndroidBindingMethod ("get_X")]
    public partial int GetX ();

    [AndroidBindingMethod ("get_Y")]
    public partial int GetY ();
}

Interaction with Existing Bindings

These bindings should compose well with existing bound libraries. Imagine a MapActivity that subclasses androidx.activity.Activity. Adding the Xamarin.AndroidX.Activity NuGet package would allow your bound type to easily subclass the "real" class:

[AndroidBindingType ("com.mapsui.MapActivity")]
public partial class MapActivity : Xamarin.AndroidX.Activity
{
    ...
}

Downsides

The largest downside is likely the lack of compile-time checking. If a user misspells a Java type or method name, we won't have tooling that can verify it, so they will receive a runtime error. Same if the user provides incorrect parameter or return types, or provides the wrong number of parameters.

@jpobst jpobst added generator Issues binding a Java library (generator, class-parse, etc.) proposal Issue raised for discussion, we do not even know if the change would be desirable yet labels Jan 31, 2025
@dellis1972
Copy link
Contributor

All the data is there to provide build time checking. We know the type they want and the methods they want so generating a .java source file and compiling that using the .jar files being referenced would produce an error. It can just be a check, it doesn't need to end up in the final app.

I mean it would be tricky, but doable. It would be an extra build step when using this feature.

@jonpryor
Copy link
Member

jonpryor commented Feb 3, 2025

Quick aside: we should use Java as the prefix, not Android, as this should be conceptually reusable with "desktop Java" bindings #858.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
generator Issues binding a Java library (generator, class-parse, etc.) proposal Issue raised for discussion, we do not even know if the change would be desirable yet
Projects
None yet
Development

No branches or pull requests

3 participants