Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Context: dotnet/android#9747 Context: https://discord.com/channels/732297728826277939/732297837953679412/1336353039031734352 Context: https://discord.com/channels/732297728826277939/732297837953679412/1336358257769316372 Context: #1302 Context: dotnet/android#9750 The `[Register]` attribute provides "connector method" names, and for interface methods this will also include the name of the type which declares the method, which itself may be in a nested type: namespace Android.App { public partial class Application { public partial interface IActivityLifecycleCallbacks : IJavaObject, IJavaPeerable { [Register ( name: "onActivityCreated", signature: "(Landroid/app/Activity;Landroid/os/Bundle;)V", connector: "GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")] void OnActivityCreated (Android.App.Activity activity, Android.OS.Bundle? savedInstanceState); // … } } } The `connector` value is copied as-is into Java Callable Wrappers, as part of the `__md_methods` value and `Runtime.register()` call. Given the C# type: partial class MauiApplication : Application { partial class ActivityLifecycleCallbacks : Java.Lang.Object, Application.IActivityLifecycleCallbacks { public void OnActivityCreated (Activity activity, Bundle? savedInstanceState) => … } } then `Java.Interop.Tools.JavaCallableWrappers` will produce: // Java Callable Wrapper /* partial */ class MauiApplication_ActivityLifecycleCallbacks { public static final String __md_methods; static { __md_methods = // … "n_onActivityCreated:(Landroid/app/Activity;Landroid/os/Bundle;)V:GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" + // … ""; mono.android.Runtime.register ("Microsoft.Maui.MauiApplication+ActivityLifecycleCallbacks, Microsoft.Maui", MauiApplication_ActivityLifecycleCallbacks.class, __md_methods); } } The `signature` and `connector` values from the `[Register(…)]` on the method declaration are copied as-is into `__md_methods`. As the `connector` value contains a `/`, `__md_methods` does as well. This has worked fine for nearly 15+ years…on Mono/MonoVM. This *fails* on NativeAOT and CoreCLR, as: Type.GetType ("Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, …", throwOnError:true) fails with: TypeLoadException: Could not resolve type 'Android.App.Application/IActivityLifecycleCallbacksInvoker' in assembly 'Mono.Android, …'. The reason for the failure is that when using Reflection APIs such as `Type.GetType()`, the [`Type.AssemblyQualifiedName` grammar][0] must be followed, and that grammar uses `+` to "Precede a nested class", *not* `/`. (`/` isn't even in the Reflection grammar!) (Aside: where does `/` come from then? It's the *IL* separator for nested types!) For eventual CoreCLR and NativeAOT support, then, we need to replace `/` with `+` *somewhere* before it hits `Type.GetType()`. There are (at least?) three places to do so: 1. Within `JniRuntime.JniTypeManager.RegisterNativeMembers()` or equivalent override. 2. Within `generator`, updating the contents of `[Register]`. 3. Within `Java.Interop.Tools.JavaCallableWrappers`. (1) is rejected out of hand as it would be additional work done on- device at runtime. Why do that if we don't need to? (2) was attempted in #1302 & dotnet/android#9750. It turned into a bit of a boondoggle, because there are lots of linker steps which interpret the `connector` value on `[Register]`, and it "just worked" that the `connector` value contained IL names, as the linker steps deal in IL! Trying to update `connector` to instead contain Reflection syntax required finding all the places in the linker that used `connector` values, which was tedious & brittle. "Just" (2) is also inadequate, as it would require new binding assemblies to take advantage of, so (2) *also* needed (3). Which brings us to (3), the current approach: *don't* alter the semantics of the `connect` value within `[Register]`, and instead require that `Java.Interop.Tools.JavaCallableWrappers` replace all instances of `/` with `+` within `__md_methods`. This is needed *anyway*, for compatibility with existing bindings, and also avoids lots of pain that (2) encountered. With this approach, `generator` output is unchanged, and `jcw-gen` output for `MauiApplication.ActivityLifecycleCallbacks` becomes: // Java Callable Wrapper /* partial */ class MauiApplication_ActivityLifecycleCallbacks { public static final String __md_methods; static { __md_methods = // … "n_onActivityCreated:(Landroid/app/Activity;Landroid/os/Bundle;)V:GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application+IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" + // … ""; mono.android.Runtime.register ("Microsoft.Maui.MauiApplication+ActivityLifecycleCallbacks, Microsoft.Maui", MauiApplication_ActivityLifecycleCallbacks.class, __md_methods); } } [0]: https://learn.microsoft.com/en-us/dotnet/api/system.type.assemblyqualifiedname?view=net-9.0#remarks
- Loading branch information