Skip to content

Proposal: Managed Source Generator Slim Bindings #1300

Open
@jpobst

Description

@jpobst

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#?

Note

A very quick prototype and sample is available here.

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
[JavaBindingType ("com.facebook.FacebookSdk")]
public partial class FacebookSdk : Java.Lang.Object
{
    [JavaBindingMethod ("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:

[JavaBindingType ("com.mapsui.Map")]
public partial class Map : Java.Lang.Object
{
    [JavaBindingMethod ("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:

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

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

    [JavaBindingMethod ("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:

[JavaBindingType ("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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    generatorIssues binding a Java library (generator, class-parse, etc.)proposalIssue raised for discussion, we do not even know if the change would be desirable yet

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions