You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fixes: #11
(Last seen nearly *two years ago* in commit 8c83f64...)
An IJavaPeerable implementation has two halfs: the managed peer and
the native peer (51f7862). The managed peer references the native
peer via IJavaPeerable.PeerReference, and an association from the
native peer to the managed peer is available through e.g.
JniRuntime.JniValueManager.PeekPeer(JniObjectReference).
Thus, the Very Important Question™: How is this association created?
From constructors.
Note the plural, and I don't mean constructor overloads.
*Both* managed peer and user peer constructors are (mostly [0])
responsible for setting up the assocation.
Managed peers do so via JniRuntime.JniValueManager.Construct()
(b3a2162; formerly SetObjectPeerReference() in 25de1f3):
/* 1 */ public unsafe BindingConstructor ()
/* 2 */ : base (ref *InvalidJniObjectReference, JniObjectReferenceOptions.None)
/* 3 */ {
/* 4 */ if (PeerReference.IsValid)
/* 5 */ return;
/* 6 */
/* 7 */ var peer = JniPeerMembers.InstanceMethods.StartCreateInstance ("()V", GetType (), null);
/* 8 */ JniEnvironment.Runtime.ValueManager.Construct (this, ref peer, JniObjectReferenceOptions.CopyAndDispose);
/* 9 */ JniPeerMembers.InstanceMethods.FinishCreateInstance ("()V", this, null);
/* 10 */ }
Line 7 creates the native peer, line 8 calls
JniRuntime.JniValueManager.Construct(), which calls
IJavaPeerable.SetPeerReference() with a GREF and creates the
association between `peer` and `this`, and line 9 finishes cleanup.
(What about lines 2, 4, and 5? Those are new; see below.)
Then we need "the other direction", when Java code performs the
construction. This process of calling a managed peer constructor with
a previously instantiated native peer instance is called *activation*.
In 8c83f64, activation support through the Java
ManagedPeer.runConstructor() API was stubbed and did nothing.
Implement ManagedPeer.runConstructor() so that when a Java User peer
is constructed, an instance of the associated managed peer is created.
This turns out to be rather complicated, for all manner of reasons.
First, the happy path:
public MyJavaType (Args...)
{
com.xamarin.java_interop.ManagedPeer.runConstructor (
MyJavaType.class,
this,
"AssemblyQualifiedNameOfManagedPeer",
"`:`-separated AssemblyQualified signature of AssemblyQualifiedNameOfManagedPeer constructor",
args...
}
When Java code invokes `new MyJavaType(...)`,
ManagedPeer.runConstructor() is executed (8c83f64), which will call
ManagedPeer.RunConstructor().
ManagedPeer.RunConstructor() will find the specified managed peer
type, lookup the specified constructor based on the signature, marshal
the parameters, and invoke the specified constructor.
Pause briefly to see the problem...
The problem is that in this scenario, the Java instance is created
*first*. Consequently, the "normal" managed peer constructor
implementation of JniPeerMembers.InstanceMethods.StartCreateInstance()
WON'T WORK, as that creats a NEW Java instance, not an existing one.
Further complicating matters is that we want a "natural" experience,
not a special constructor signature, so how do we provide the
*existing* Java instance to the managed peer that will be created?
The answer? By violating all manner of "invariants" by using
Serialization-related APIs.
Let's rephrase things: Given a Java type J and a Managed Peer type M,
how do we associate an instance of J to an instance of M
*before constructing M*?
With FormatterServices.GetUninitializedObject(), which returns a blob
of zero-initialized GC memory of "type" M which hasn't been
constructed yet.
There be potential dragons here.
We then call IJavaPeerable.SetPeerReferenc() on this
*unconstructed* instance, then use Reflection to invoke the
constructor on the pre-allocated instance.
Thus:
object m = FormatterServices.GetUninitializedObject(typeof(M));
((IJavaPeerable) o).SetPeerReference (J_instance);
constructorInfoOfM.Invoke(m, args);
(The stunning thing is that the above works at all!)
This allows the Java peer instance to be associated with a Managed
peer instance *before constructing the Managed peer*, which thus
allows invoking the desired Managed peer constructor.
Which brings us to the newly added lines 2, 4, and 5:
/* 1 */ public unsafe BindingConstructor ()
/* 2 */ : base (ref *InvalidJniObjectReference, JniObjectReferenceOptions.None)
/* 3 */ {
/* 4 */ if (PeerReference.IsValid)
/* 5 */ return;
All "binding" constructors -- constructors which would normally use
JniPeerMembers.InstanceMethods.StartCreateInstance() and
JniRuntime.JniValueManager.Construct() -- must now call the
JavaObject(JniObjectReference&, JniObjectReferenceOptions) or
JavaException(JniObjectReference&, JniObjectReferenceOptions)
constructors (line 2), to check for a "previously" set PeerReference
value. If there was a previously set PeerReference value -- i.e.
we're undergoing an activation-originated constructor call -- then the
constructor should STOP EXECUTING (lines 4-5).
Failure to do so will result in creating a new Java instance, and
associating the current managed peer instance to the *wrong* Java
instance. Don't Do That.
What about the less-than-happy path? That's when a virtual method is
invoked from a Java constructor and overridden in the managed peer.
This is described somewhat in 972c5bc and 3043d89.
The use of JNIEnv::AllocObject() (972c5bc) helps when the Managed
peer is constructed first. When the native peer is constructed first,
JNIEnv::AllocObject() can't help you.
In this scenario, the Managed peer instance is instead constructed
*multiple times*
1. Java: D.<init>()
2. Java: B.<init>()
3. Java: B.calledFromConstructor()
4. Java: D.n_calledFromConstructor();
5. C#: D.CalledFromConstructorHandler()
6. C#: JniRuntime.JniValueManager.CreateProxy() for (1)
This invokes the
D(JniObjectReference&, JniObjectReferenceOptions)
constructor. If not present, throws.
7. C#: D.CalledFromConstructor()
8. Java: B.<init>() finishes
9. Java: D.<init>() calls ManagedPeer.runConstructor().
10. C#: ManagedPeer.RunConstructor() finds specified
ConstructorInfo, invokes on instance created in (6).
Note the Managed peer constructor invocations in (6) and (10), both on
the same instance.
What's with this Reflection for
FormatterServices.GetUninitializedObject()?
FormatterServices.GetUninitializedObject() isn't present in any PCL
Profile (doh!), but it's the only mechanism I know of that allows me
to not require a special activation constructor.
Additionally, FormatterServices.GetUninitializedObject() IS supported
in CoreCL, so this should work on all interesting runtimes.
[0]: One exception being "framework peers", which don't do anything
special in their constructors to cause managed peers to be
created.
0 commit comments