-
Notifications
You must be signed in to change notification settings - Fork 63
Description
Context: http://github.com/xamarin/monodroid/commit/c0ad930298013425e4797f89b0c9243c22827152
Context: #858
From xamarin/monodroid@c0ad930298013425e4797f89b0c9243c22827152 (2011-Nov-28):
Remember xamarin/monodroid@62a4766, and how the issue was an intermixing of static and
instance data on the interface Invokers?One of the related questions was why the
Invoker.class_reffield was
an instance field in the first place. We use staticclass_reffields
everywhere else, so being an instance field was certainly different,
but my efforts to figure out why via the commit messages and
git blamedidn't pan out...The reason
Invoker.class_refwas an instance field is as follows:
consider the following set of Java interfaces:package android.view; public interface ViewManager { void addView(View view, ViewGroup.LayoutParams params); void updateViewLayout(View view, ViewGroup.LayoutParams params); void removeView(View view); } public interface WindowManager extends ViewManager { Display getDefaultDisplay(); void removeViewImmediate(View view); }The generated C#
IWindowManagerInvokertype is (more or less):[global::Android.Runtime.Register ("android/view/WindowManager", DoNotGenerateAcw=true)] internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { static IntPtr class_ref = JNIEnv.FindClass ("android/view/WindowManager"); // ... static IntPtr id_addView; public void AddView (Android.Views.View view, Android.Views.ViewGroup.LayoutParams @params) { if (id_addView == IntPtr.Zero) id_addView = JNIEnv.GetMethodID (class_ref, "addView", "(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V"); JNIEnv.CallVoidMethod (Handle, id_addView, new JValue (JNIEnv.ToJniHandle (view)), new JValue (JNIEnv.ToJniHandle (@params))); } }Nice. Straightforward. So let's invoke
IViewManager.AddView()via an
IWindowManagerinstance (seeHello.cs):var die = new TextView (this) { Text = "Added!", }; WindowManager.AddView (die, new WindowManagerLayoutParams ());What could possibly go wrong™?
D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown. I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string) <0x0007c> I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams) <0x00067> I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle) <0x005ab> I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) <0x00057> I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr) <0x00033>Ouch.
What goes wrong is that the
addView()method is declared in the
android.view.ViewManagerinterface, not theandroid.view.WindowManager
interface, but we're doing aGetMethodID()onWindowManager. This
sounds like it should work --WindowManagerdoes implement the
ViewManagerinterface, after all -- butGetMethodID()doesn't like it
and bombs accordingly.This is why
Invoker.class_refwas an instance field: because we need
to callGetMethodID()against the actual runtime type of the target
object, not against the (compile-time) interface type.Consequently, in order to fix the bug fixed in xamarin/monodroid@62a4766 in addition to
the bug outlined above, we need to not only makeInvoker.class_refan
instance field (a'la pre- xamarin/monodroid@62a47668), but we also need to make all the
GetMethodIDvalues instance members as well (to fix the issue that
xamarin/monodroid@62a4766 fixed).
The structure of that fix are still with us today; consider: https://github.com/xamarin/java.interop/blob/dcdcce13220d98a848ae3d3598b9b87b22a62c84/tests/generator-Tests/expected/java.lang.Enum/Java.Lang.IComparable.cs
IComparableInvoker.class_refis an instance field.IComparableInvoker.id_compareTo_Ljava_lang_Object_is an instance field.IComparableInvoker.CompareTo()looks up thejmethodID, storing intoid_compareTo_Ljava_lang_Object_. As this is per-instance data, only subsequentIComparable.CompareTo()invocations on this instance will be cached; other instances will be separate.
Consequently, no interface *Invoker data is shared between instances; each instance must re-lookup jmethodID values.
Additionally, in the context of #858, none of the infrastructure currently used by interface *Invoker types will exist.
Thus, the proposal: optimize interface *Invoker types by using JniPeerMembers, one per implemented interface. For IWindowManager, this would be:
[global::Android.Runtime.Register ("android/view/WindowManager", DoNotGenerateAcw=true)]
internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
// One JniPeerMembers value per implemented interface; as IWindowManager implements IViewManager, we need two values.
static JniPeerMembers _members_ViewManager = new XAPeerMembers ("android/view/ViewManager", typeof (IWindowManagerInvoker), isInterface: true);
static JniPeerMembers _members_WindowManager = new XAPeerMembers ("android/view/WindowManager", typeof (IWindowManagerInvoker), isInterface: true);
public override JniPeerMembers JniPeerMembers {
get => _members_WindowManager;
}
public override Type ThresholdType {
get => _members.ManagedPeerType;
}
new IntPtr class_ref;
public override IntPtr ThresholdClass {
get => class_ref;
}
public IWindowManagerInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
{
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
JNIEnv.DeleteLocalRef (local_ref);
}
public unsafe void AddView (Android.Views.View? view, Android.Views.ViewGroup.LayoutParams? @params)
{
const string __id = "addView.(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V";
JniArgumentValue* __args = stackalloc JniArgumentValue [2];
__args [0] = new JniArgumentValue ((view == null) ? IntPtr.Zero : ((global::Java.Lang.Object) view).Handle);
__args [1] = new JniArgumentValue ((@params == null) ? IntPtr.Zero : ((global::Java.Lang.Object) @params).Handle);
// As AddView() is from `IViewManager`, use `_members_ViewManager` to invoke it.
_members_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args);
}
// …
// Note: SKIP java_class_ref, GetObject(), Validate(); those aren't needed anymore.
}In particular, all methods which implement the IViewManager interface would use _members_ViewManager, while all methods which implement the IWindowManager interface would use _members_WindowManager.
This should allow jmethodID values to be cached across instances, while also allowing appropriate JNI method lookup.