-
Notifications
You must be signed in to change notification settings - Fork 52
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
[generator] Use proper syntax for nested classes for default interface method invokers. #662
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…e method invokers.
Squash-and-merge: Summary:
Body:
|
jonpryor
pushed a commit
that referenced
this pull request
Jun 11, 2020
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.
Release Notes
|
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #661
When we
[Register]
the invoker for adefault
method in a nested interface, we are using.
to denote a nested interface, when it should be a/
.For example, in:
Android.App.Application.IActivityLifecycleCallbacks
should beAndroid.App.Application/IActivityLifecycleCallbacks
.This is causing the nested type to not be found.
Fixed by using
AssemblyQualifiedName
instead ofFullName
, which is explicitly defined to support the correct notation.