Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[generator] Use proper syntax for nested classes for DIM invokers (#662)
Fixes: #661 App crashes with a `TypeLoadException` when instantiating a class which implements an interface containing default interface members: 1. Create a new app within Visual Studio 16.6 or 16.7. 2. Set the `$(TargetFrameworkVersion)` to v10.0.99. This pulls in a `Mono.Android.dll` which uses default interface methods. 3. Add the following class to the project. The `Android.App.Application.IActivityLifecycleCallbacks` interface is (1) a nested type, and (2) contains default interface methods. class ActivityLifecycleContextListener : Java.Lang.Object, Android.App.Application.IActivityLifecycleCallbacks { public void OnActivityCreated (Activity activity, Bundle savedInstanceState) => throw new NotImplementedException (); public void OnActivityDestroyed (Activity activity) => throw new NotImplementedException (); public void OnActivityPaused (Activity activity) => throw new NotImplementedException (); public void OnActivityResumed (Activity activity) => throw new NotImplementedException (); public void OnActivitySaveInstanceState (Activity activity, Bundle outState) => throw new NotImplementedException (); public void OnActivityStarted (Activity activity) => throw new NotImplementedException (); public void OnActivityStopped (Activity activity) => throw new NotImplementedException (); } 4. Instantiate `ActivityLifecycleContextListener` within the app's `OnCreate()` method: var a = new ActivityLifecycleContextListener (); Expected results: it works! Actual results: System.TypeLoadException: Could not load type 'Android.App.Application.IActivityLifecycleCallbacks' from assembly 'Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. at System.RuntimeType.GetType (System.String typeName, System.Boolean throwOnError, System.Boolean ignoreCase, System.Boolean reflectionOnly, Sys06-08 19:19:38.543 I/MonoDroid( 5187): at (wrapper managed-to-native) System.RuntimeTypeHandle.internal_from_name(string,System.Threading.StackCrawlMark&,System.Reflection.Assembly,bool,bool,bool) at System.RuntimeTypeHandle.GetTypeByName (System.String typeName, System.Boolean throwOnError, System.Boolean ignoreCase, System.Boolean reflectionOnly, System.Threading.StackCrawlMark& stackMark, System.Boolean loadTypeFromPartialName) at (wrapper managed-to-native) Java.Interop.NativeM06-08 at System.Type.GetType (System.String typeName, System.Boolean throwOnError) at Android.Runtime.AndroidTypeManager.RegisterNativeMembers (Java.Interop.JniType nativeClass, System.Type type, System.String methods) at Android.Runtime.JNIEnv.RegisterJniNatives (System.IntPtr typeName_ptr, System.Int32 typeName_len, System.IntPtr jniClass, System.IntPtr methods_ptr, System.Int32 methods_len) … The cause of the bug is that Xamarin.Android attempts to call: Type.GetType ("Android.App.Application.IActivityLifecycleCallbacks, Mono.Android"); which fails, because this is the wrong syntax for looking up nested types via Reflection. Xamarin.Android should instead be doing: Type.GetType ("Android.App.Application/IActivityLifecycleCallbacks, Mono.Android"); Note `/` instead of `.` in the type name. Why was Xamarin.Android trying to load the wrong type? Because the *Java Callable Wrapper* contained the wrong type, and Xamarin.Android used the string as-is: public /* partial */ class ActivityLifecycleContextListener extends java.lang.Object implements mono.android.IGCUserPeer, android.app.Application.ActivityLifecycleCallbacks { /** @hide */ 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" + … "n_onActivityPreStopped:(Landroid/app/Activity;)V:GetOnActivityPreStopped_Landroid_app_Activity_Handler:Android.App.Application.IActivityLifecycleCallbacks, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" + ""; … } } "Interesting" here is the difference between the first and the last entry: the first entry is for a "normal" interface method, which uses the required `/` nested type separator, while the last entry is for a Java default interface method. Aside/TODO: Why is `n_onActivityPreStopped()` being registered when `Application.IActivityLifecycleCallbacks.OnActivityPreStopped()` is not implemented in `ActivityLifecycleContextListener`? Where do the `__md_methods` entries come from? From the binding assembly; from `generator` output! public partial interface IActivityLifecycleCallbacks : IJavaObject, IJavaPeerable { // Metadata.xml XPath method reference: path="/api/package[@name='android.app']/interface[@name='Application.ActivityLifecycleCallbacks']/method[@name='onActivityPreStopped' and count(parameter)=1 and parameter[1][@type='android.app.Activity']]" [Register ("onActivityPreStopped", "(Landroid/app/Activity;)V", "GetOnActivityPreStopped_Landroid_app_Activity_Handler:Android.App.Application.IActivityLifecycleCallbacks, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", ApiSince = 29)] virtual unsafe void OnActivityPreStopped (Android.App.Activity activity) {…} } The 3rd `RegisterAttribute` parameter, which becomes the `RegisterAttribute.Connector` property, is where the offending string comes from: …:Android.App.Application.IActivityLifecycleCallbacks, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" The fix is to update `Method.GetConectorNameFull()` to use `DeclaringType.AssemblyQualifiedName`, *not* `DeclaringType.FullName`, as the former is what's needed for reflection compatibility, while the latter is a "C#" name, generally more useful for error messages.
- Loading branch information