From 8c83f64fe60ea9539a22633a42abbd788aadf8d5 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 25 Apr 2014 17:33:49 -0400 Subject: [PATCH] [Java.Interop] "Java Activation": API Declaration Next can-o'-worms to start implementing: Java Activation: http://docs.xamarin.com/guides/android/under_the_hood/architecture/#Java_Activation There are logically two ways to create a "managed peer"/"native peer" instance pair: 1. By creating the managed peer, which will automagically create the native peer instance (see the JavaObject default constructor); or 2. By creating the native peer. (1) is implemented. (2) has not been, meaning at present if Java code were to create an instance of a "native peer", no corresponding managed peer instance would be created, and thus any `native` methods wouldn't be registered, and things would blow up rather quickly. How should this be exposed? Xamarin.Android has two related methods: * mono.android.Runtime.register(): registers native methods for a Java class. * mono.android.TypeManager.Activate(): invoked by the Java constructor, and responsible for associating the native peer with a managed peer, creating a new managed peer if necessary. The pattern for TypeManager.Activate() invocation is as follows: // Xamarin.Android constructor style public JavaPeerType(Args... args) { super (args...); if (getClass() == JavaPeerType.class) TypeManager.Activate ( AssemblyQualifiedTypeName, ConstructorSignature, this, new java.lang.Object[]{ args } ); } The need for the getClass() check is so that TypeManager.Activate() is only executed once, on the most derived native peer type, and not once per type in the inheritance chain. My feeling is that Runtime.register() and TypeManager.Activate() should be more closely related, because they both operate on peer types. (Runtime.register() is also a semantically poor name, and the it's only in Runtime for "where else should we put it?" reasons.) The alternative Java.Interop API? ManagedPeer.runConstructor() (and later, ManagedPeer.registerNativeMethods()), which also alters parameter ordering and now takes the Class for the declaring class: // Java.Interop constructor style: public JavaPeerType(Args... args) { super (args...); ManagedPeer.runConstructor ( JavaPeerType.class, this, AssemblyQualifiedTypeName, ConstructorSignature, args... ); } This "moves" the getClass() check into ManagedPeer.runConstructor(), allowing ManagedPeer methods to consistently take a Class instance, and also provides possible future flexibility on behavior. ManagedPeer.runConstructor() doesn't do anything at this time; this is just stubbing out the API, adding the appropriate calls to our Java "native peer" test types, and providing the initial C# implementation. --- src/Java.Interop/Java.Interop.csproj | 2 + src/Java.Interop/Java.Interop/JavaVM.cs | 2 + src/Java.Interop/Java.Interop/ManagedPeer.cs | 50 +++++++++++++++++++ .../Tests/Java.Interop-Tests.csproj | 2 +- .../xamarin/interop/CallNonvirtualBase.java | 9 ++++ .../interop/CallNonvirtualDerived.java | 9 ++++ .../interop/CallNonvirtualDerived2.java | 9 ++++ .../CallVirtualFromConstructorBase.java | 7 +++ .../CallVirtualFromConstructorDerived.java | 7 +++ .../java/com/xamarin/interop/TestType.java | 9 ++++ .../java/com/xamarin/android/ManagedPeer.java | 26 ++++++++++ 11 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/Java.Interop/Java.Interop/ManagedPeer.cs create mode 100644 src/Java.Interop/java/com/xamarin/android/ManagedPeer.java diff --git a/src/Java.Interop/Java.Interop.csproj b/src/Java.Interop/Java.Interop.csproj index 201808bbc..c5584ee68 100644 --- a/src/Java.Interop/Java.Interop.csproj +++ b/src/Java.Interop/Java.Interop.csproj @@ -91,10 +91,12 @@ + + diff --git a/src/Java.Interop/Java.Interop/JavaVM.cs b/src/Java.Interop/Java.Interop/JavaVM.cs index d3d666228..44ba8a096 100644 --- a/src/Java.Interop/Java.Interop/JavaVM.cs +++ b/src/Java.Interop/Java.Interop/JavaVM.cs @@ -177,6 +177,8 @@ protected JavaVM (JavaVMOptions options) } JavaVMs.TryAdd (SafeHandle.DangerousGetHandle (), this); + + ManagedPeer.Init (); } ~JavaVM () diff --git a/src/Java.Interop/Java.Interop/ManagedPeer.cs b/src/Java.Interop/Java.Interop/ManagedPeer.cs new file mode 100644 index 000000000..de36969b8 --- /dev/null +++ b/src/Java.Interop/Java.Interop/ManagedPeer.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using System.Text; + +namespace Java.Interop { + + [JniTypeInfo (ManagedPeer.JniTypeName)] + /* static */ class ManagedPeer : JavaObject { + + internal const string JniTypeName = "com/xamarin/android/ManagedPeer"; + + + static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (ManagedPeer)); + + static ManagedPeer () + { + _members.JniPeerType.RegisterNativeMethods ( + new JniNativeMethodRegistration ("runConstructor", RunConstructorSignature, (Action) RunConstructor) + ); + } + + ManagedPeer () + { + } + + internal static void Init () + { + // Present so that JavaVM has _something_ to reference to + // prompt invocation of the static constructor & registration + } + + public override JniPeerMembers JniPeerMembers { + get {return _members;} + } + + const string RunConstructorSignature = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V"; + + static void RunConstructor ( + IntPtr jnienv, + IntPtr klass, + IntPtr n_self, + IntPtr n_assemblyQualifiedName, + IntPtr n_constructorSignature, + IntPtr n_constructorArguments) + { + } + } +} + diff --git a/src/Java.Interop/Tests/Java.Interop-Tests.csproj b/src/Java.Interop/Tests/Java.Interop-Tests.csproj index ffde6e719..4ed94097e 100644 --- a/src/Java.Interop/Tests/Java.Interop-Tests.csproj +++ b/src/Java.Interop/Tests/Java.Interop-Tests.csproj @@ -96,7 +96,7 @@ - + diff --git a/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualBase.java b/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualBase.java index 4972910fa..ccf83e314 100644 --- a/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualBase.java +++ b/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualBase.java @@ -2,6 +2,15 @@ public class CallNonvirtualBase { + public CallNonvirtualBase () { + com.xamarin.android.ManagedPeer.runConstructor ( + CallNonvirtualBase.class, + this, + "Java.InteropTests.CallNonvirtualBase, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", + "" + ); + } + boolean methodInvoked; public void method () { System.out.println ("CallNonvirtualBase.method() invoked!"); diff --git a/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualDerived.java b/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualDerived.java index 0c891a712..e3d8d1042 100644 --- a/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualDerived.java +++ b/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualDerived.java @@ -2,6 +2,15 @@ public class CallNonvirtualDerived extends CallNonvirtualBase { + public CallNonvirtualDerived () { + com.xamarin.android.ManagedPeer.runConstructor ( + CallNonvirtualDerived.class, + this, + "Java.InteropTests.CallNonvirtualDerived, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", + "" + ); + } + boolean methodInvoked; public void method () { System.out.println ("CallNonvirtualDerived.method() invoked!"); diff --git a/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualDerived2.java b/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualDerived2.java index 161429758..83968f295 100644 --- a/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualDerived2.java +++ b/src/Java.Interop/Tests/java/com/xamarin/interop/CallNonvirtualDerived2.java @@ -1,4 +1,13 @@ package com.xamarin.interop; public class CallNonvirtualDerived2 extends CallNonvirtualDerived { + + public CallNonvirtualDerived2 () { + com.xamarin.android.ManagedPeer.runConstructor ( + CallNonvirtualDerived2.class, + this, + "Java.InteropTests.CallNonvirtualDerived2, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", + "" + ); + } } diff --git a/src/Java.Interop/Tests/java/com/xamarin/interop/CallVirtualFromConstructorBase.java b/src/Java.Interop/Tests/java/com/xamarin/interop/CallVirtualFromConstructorBase.java index 6c8d8a9f8..aca80b279 100644 --- a/src/Java.Interop/Tests/java/com/xamarin/interop/CallVirtualFromConstructorBase.java +++ b/src/Java.Interop/Tests/java/com/xamarin/interop/CallVirtualFromConstructorBase.java @@ -3,6 +3,13 @@ public class CallVirtualFromConstructorBase { public CallVirtualFromConstructorBase (int value) { + com.xamarin.android.ManagedPeer.runConstructor ( + CallVirtualFromConstructorBase.class, + this, + "Java.InteropTests.CallVirtualFromConstructorBase, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", + "System.Int32", + value + ); calledFromConstructor (value); } diff --git a/src/Java.Interop/Tests/java/com/xamarin/interop/CallVirtualFromConstructorDerived.java b/src/Java.Interop/Tests/java/com/xamarin/interop/CallVirtualFromConstructorDerived.java index a7f370f69..6ae756d9b 100644 --- a/src/Java.Interop/Tests/java/com/xamarin/interop/CallVirtualFromConstructorDerived.java +++ b/src/Java.Interop/Tests/java/com/xamarin/interop/CallVirtualFromConstructorDerived.java @@ -4,6 +4,13 @@ public class CallVirtualFromConstructorDerived extends CallVirtualFromConstructo public CallVirtualFromConstructorDerived (int value) { super (value); + com.xamarin.android.ManagedPeer.runConstructor ( + CallVirtualFromConstructorDerived.class, + this, + "Java.InteropTests.CallVirtualFromConstructorDerived, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", + "System.Int32", + value + ); } public native void calledFromConstructor (int value); diff --git a/src/Java.Interop/Tests/java/com/xamarin/interop/TestType.java b/src/Java.Interop/Tests/java/com/xamarin/interop/TestType.java index d4cc22034..dc636db6c 100644 --- a/src/Java.Interop/Tests/java/com/xamarin/interop/TestType.java +++ b/src/Java.Interop/Tests/java/com/xamarin/interop/TestType.java @@ -2,6 +2,15 @@ public class TestType { + public TestType () { + com.xamarin.android.ManagedPeer.runConstructor ( + TestType.class, + this, + "Java.InteropTests.TestType, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", + "" + ); + } + public void runTests () { int n = getInt32Value (); if (getInt32Value() != 42) diff --git a/src/Java.Interop/java/com/xamarin/android/ManagedPeer.java b/src/Java.Interop/java/com/xamarin/android/ManagedPeer.java new file mode 100644 index 000000000..8c5963f6a --- /dev/null +++ b/src/Java.Interop/java/com/xamarin/android/ManagedPeer.java @@ -0,0 +1,26 @@ +package com.xamarin.android; + +public /* static */ class ManagedPeer { + private ManagedPeer () { + } + + // public static native void registerNativeMethods (java.lang.Class nativeClass, String managedType, String methods); + + public static void runConstructor ( + Class declaringClass, + Object self, + String assemblyQualifiedName, + String constructorSignature, + Object... arguments) { + if (self.getClass() != declaringClass) + return; + runConstructor (self, assemblyQualifiedName, constructorSignature, arguments); + } + + static native void runConstructor ( + Object self, + String assemblyQualifiedName, + String constructorSignature, + Object... arguments + ); +}