Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd3d3a1b

Browse files
authored
[Java.Interop] JNIEnv::NewObject and Replaceable instances (#1323)
Context:3043d89Context:dec35f5Context:dotnet/android#9862Context:dotnet/android#9862 (comment)Context:dotnet/android#10004Context:https://github.com/xamarin/monodroid/commit/326509e56d4e582c53bbe5dfe6d5c741a27f1af5Context:https://github.com/xamarin/monodroid/commit/940136ebf1318a7c57a855e2728ce2703c0240afEver get the feeling that everything is inextricably related?JNI has two pattens for create an instance of a Java type: 1. [`JNIEnv::NewObject(jclass clazz, jmethodID methodID, const jvalue* args)`][0] 2. [`JNIEnv::AllocObject(jclass clazz)`][1] + [`JNIEnv::CallNonvirtualVoidMethod(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args)`][2]In both patterns: * `clazz` is the `java.lang.Class` of the type to create. * `methodID` is the constructor to execute * `args` are the constructor arguments.In .NET terms: * `JNIEnv::NewObject()` is equivalent to using `System.Reflection.ConstructorInfo.Invoke(object?[]?)`, while * `JNIEnv::AllocObject()` + `JNIEnv::CallNonvirtualVoidMethod()` is equivalent to using `System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(Type)` + `System.Reflection.MethodBase.Invoke(object?, object?[]?)`.Why prefer one over the other?When hand-writing your JNI code, `JNIEnv::NewObject()` is easier.This is less of a concern when a code generator is used.The *real* reason to *avoid* `JNIEnv::NewObject()` whenever possibleis the [Java Activation][3] scenario, summarized as the "are you sureyou want to do this?" [^1] scenario of invoking a virtual method from theconstructor:class Base { public Base() { VirtualMethod(); } public virtual void VirtualMethod() {}}class Derived : Base { public override void VirtualMethod() {}}Java and C# are identical here: when a constructor invokes a virtualmethod, the most derived method implementation is used, which willoccur *before* the constructor of the derived type has *started*execution. (With lots of quibbling about field initializers…)Thus, assume you have a Java `CallVirtualFromConstructorBase` type,which has its constructor Do The Wrong Thing™ and invoke a virtualmethod from the constructor, and that method is overridden in C#?// Javapublic class CallVirtualFromConstructorBase { public CallVirtualFromConstructorBase(int value) { calledFromConstructor(value); } public void calledFromConstructor(int value) { }}// C#public class CallVirtualFromConstructorDerived : CallVirtualFromConstructorBase { public CallVirtualFromConstructorDerived(int value) : base (value) { } public override void CalledFromConstructor(int value) { }}What happens with:var p = new CallVirtualFromConstructorDerived(42);The answer depends on whether or not `JNIEnv::NewObject()` is used.If `JNIEnv::NewObject()` is *not* used (the default!) 1. `CallVirtualFromConstructorDerived(int)` constructor begins execution, immediately calls `base(value)`. 2. `CallVirtualFromConstructorBase(int)` constructor runs, uses `JNIEnv::AllocObject()` to *create* (but not construct!) Java `CallVirtualFromConstructorDerived` instance. 3. `JavaObject.Construct(ref JniObjectReference, JniObjectReferenceOptions)` invoked, creating a mapping between the C# instance created in (1) and the Java instance created in (2). 4. `CallVirtualFromConstructorBase(int)` C# constructor calls `JniPeerMembers.InstanceMethods.FinishGenericCreateInstance()`, which eventually invokes `JNIEnv::CallNonvirtualVoidMethod()` with the Java `CallVirtualFromConstructorDerived(int)` ctor. 5. Java `CallVirtualFromConstructorDerived(int)` constructor invokes Java `CallVirtualFromConstructorBase(int)` constructor, which invokes `CallVirtualFromConstructorDerived.calledFromConstructor()`. 6. Marshal method (356485e) for `CallVirtualFromConstructorBase.CalledFromConstructor()` invoked, *immediately* calls `JniRuntime.JniValueManager.GetPeer()` (e288589) to obtain an instance upon which to invoke `.CalledFromConstructor()`, finds the instance mapping from (3), invokes `CallVirtualFromConstructorDerived.CalledFromConstructor()` override. 7. Marshal Method for `CalledFromConstructor()` returns, Java `CallVirtualFromConstructorBase(int)` constructor finishes, Java `CallVirtualFromConstructorDerived(int)` constructor finishes, `JNIEnv::CallNonvirtualVoidMethod()` finishes. 8. `CallVirtualFromConstructorDerived` instance finishes construction.If `JNIEnv::NewObject()` is used: 1. `CallVirtualFromConstructorDerived(int)` constructor begins execution, immediately calls `base(value)`. Note that this is the first created `CallVirtualFromConstructorDerived` instance, but it hasn't been registered yet. 2. `CallVirtualFromConstructorBase(int)` constructor runs, uses `JNIEnv::NewObject()` to construct Java `CallVirtualFromConstructorDerived` instance. 3. `JNIEnv::NewObject()` invokes Java `CallVirtualFromConstructorDerived(int)` constructor, which invokes `CallVirtualFromConstructorBase(int)` constructor, which invokes `CallVirtualFromConstructorDerived.calledFromConstructor()`. 4. Marshal method (356485e) for `CallVirtualFromConstructorBase.CalledFromConstructor()` invoked, *immediately* calls `JniRuntime.JniValueManager.GetPeer()` (e288589) to obtain an instance upon which to invoke `.CalledFromConstructor()`. Here is where things go "off the rails" compared to the `JNIEnv::AllocObject()` code path: There is no such instance -- we're still in the middle of constructing it! -- so we look for an "activation constructor". 5. `CallVirtualFromConstructorDerived(ref JniObjectReference, JniObjectReferenceOptions)` activation constructor executed. This is the *second* `CallVirtualFromConstructorDerived` instance created, and registers a mapping from the Java instance that we started constructing in (3) to what we'll call the "activation intermediary". The activation intermediary instance is marked as "Replaceable". 6. `CallVirtualFromConstructorDerived.CalledFromConstructor()` method override invoked on the activation intermediary. 7. Marshal Method for `CalledFromConstructor()` returns, Java `CallVirtualFromConstructorBase(int)` constructor finishes, Java `CallVirtualFromConstructorDerived(int)` constructor finishes, `JNIEnv::NewObject()` returns instance. 8. C# `CallVirtualFromConstructorBase(int)` constructor calls `JavaObject.Construct(ref JniObjectReference, JniObjectReferenceOptions)`, to create a mapping between (3) and (1). In .NET for Android, this causes the C# instance created in (1) to *replace* the C# instance created in (5), which allows "Replaceable" instance to be replaced. In dotnet/java-interop, this replacement *didn't* happen, which meant that `ValueManager.PeekPeer(p.PeerReference)` would return the activation intermediary, *not* `p`, which confuses everyone. 9. `CallVirtualFromConstructorDerived` instance finishes construction.For awhile, dotnet/java-interop did not fully support this scenarioaround `JNIEnv::NewObject()`. Additionally, support for using`JNIEnv::NewObject()` as part of`JniPeerMembers.JniInstanceMethods.StartCreateInstance()` was*removed* indec35f5.Which brings us todotnet/android#9862: where there is an observed"race condition" around `Android.App.Application` subclass creation.*Two* instances of `AndroidApp` were created, one from the "normal"app startup:at crc647fae2f69c19dcd0d.AndroidApp.n_onCreate(Native Method)at crc647fae2f69c19dcd0d.AndroidApp.onCreate(AndroidApp.java:25)at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)and another from an `androidx.work.WorkerFactory`:at mono.android.TypeManager.n_activate(Native Method)at mono.android.TypeManager.Activate(TypeManager.java:7)at crc647fae2f69c19dcd0d.SyncWorker.<init>(SyncWorker.java:23)at java.lang.reflect.Constructor.newInstance0(Native Method)at java.lang.reflect.Constructor.newInstance(Constructor.java:343)at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:95)However, what was odd about this "race condition" was that the*second* instance created would reliably win!Further investigation suggested that this was less of a"race condition" and more a bug in `AndroidValueManager`, wherein when"Replaceable" instances were created, an existing instance would*always* be replaced, even if the new instance was also Replaceable!This feels bananas; yes, Replaceable should be replaceable, but theexpectation was that it would be replaced by *non*-Replaceableinstances, not just any instance that came along later.Update `JniRuntimeJniValueManagerContract` to add a new`CreatePeer_ReplaceableDoesNotReplace()` test to codify the desiredsemantic that Replaceable instances do not replace Replaceableinstances.Surprisingly, this new test did not fail on java-interop, as`ManagedValueManager.AddPeer()` bails early when `PeekPeer()` findsa value, while `AndroidValueManager.AddPeer()` does not bail early.An obvious fix for `CreatePeer_ReplaceableDoesNotReplace()` withindotnet/android would be to adopt the "`AddPeer()` calls `PeekPeer()`"logic from java-interop. The problem is that doing so breaks[`ObjectTest.JnienvCreateInstance_RegistersMultipleInstances()`][5],as seen indotnet/android#10004!`JnienvCreateInstance_RegistersMultipleInstances()` in turn failswhen `PeekPeer()` is used because follows the `JNIEnv::NewObject()`[construction codepath][6]!public CreateInstance_OverrideAbsListView_Adapter (Context context) : base ( JNIEnv.CreateInstance ( JcwType, "(Landroid/content/Context;)V", new JValue (context)), JniHandleOwnership.TransferLocalRef){ AdapterValue = new ArrayAdapter (context, 0);}as `JNIEnv.CreateInstance()` uses `JNIEnv.NewObject()`.We thus have a conundrum: how do we fix *both*`CreatePeer_ReplaceableDoesNotReplace()` *and*`JnienvCreateInstance_RegistersMultipleInstances()`?The answer is to add proper support for the `JNIEnv::NewObject()`construction scenario to dotnet/java-interop, which in turn requires"lowering" the setting of `.Replaceable`. Previously, we would set`.Replaceable` *after* the activation constructor was invoked:// dotnet/android TypeManager.CreateInstance(), paraphrasingvar result = CreateProxy (type, handle, transfer);result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable);return result;This is *too late*, as during execution of the activation constructor,the instance thinks it *isn't* replaceable, and thus creation of a newinstance via the activation constructor will replace an alreadyexisting replaceable instance; it's not until *after* the constructorfinished executing that we'd set `.Replaceable`.To fix this, update `JniRuntime.JniValueManager.TryCreatePeerInstance()`to first create an *uninitialized* instance, set `.Replaceable`, and*then* invoke the activation constructor. This allows`JniRuntime.JniValueManager.AddPeer()` to check to see if the newvalue is also replaceable, and ignore the replacement if appropriate.This in turn requires replacing:partial class /* JniRuntime. */ JniValueManager { protected virtual IJavaPeerable? TryCreatePeer () ref JniObjectReference reference, JniObjectReferenceOptions options, Type type);}with:partial class /* JniRuntime. */ JniValueManager { protected virtual bool TryConstructPeer () IJavaPeerable self, ref JniObjectReference reference, JniObjectReferenceOptions options, Type type);}This is fine because we haven't shipped `TryCreatePeer()` in a stablerelease yet.[^1]: See also [Framework Design Guidelines > Constructor Design][4]: > ❌ AVOID calling virtual members on an object inside its constructor. > Calling a virtual member will cause the most derived override to be > called, even if the constructor of the most derived type has not > been fully run yet.[0]:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#NewObject[1]:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#AllocObject[2]:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#CallNonvirtual_type_Method_routines[3]:https://learn.microsoft.com/en-us/previous-versions/xamarin/android/internals/architecture#java-activation[4]:https://learn.microsoft.com/dotnet/standard/design-guidelines/constructor[5]:https://github.com/dotnet/android/blob/9ad492a42b384519a8b1f1987adae82335536d9c/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs#L68-L79[6]:https://github.com/dotnet/android/blob/9ad492a42b384519a8b1f1987adae82335536d9c/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs#L151-L160
1 parent8221b7d commitd3d3a1b

File tree

7 files changed

+270
-30
lines changed

7 files changed

+270
-30
lines changed

‎src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -401,15 +401,36 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
401401
Typetype)
402402
{
403403
type=Runtime.TypeManager.GetInvokerType(type)??type;
404-
returnTryCreatePeer(refreference,options,type);
404+
405+
varself=GetUninitializedObject(type);
406+
varconstructed=false;
407+
try{
408+
constructed=TryConstructPeer(self,refreference,options,type);
409+
}finally{
410+
if(!constructed){
411+
GC.SuppressFinalize(self);
412+
self=null;
413+
}
414+
}
415+
returnself;
416+
417+
staticIJavaPeerableGetUninitializedObject(
418+
[DynamicallyAccessedMembers(Constructors)]
419+
Typetype)
420+
{
421+
varv=(IJavaPeerable)System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(type);
422+
v.SetJniManagedPeerState(JniManagedPeerStates.Replaceable|JniManagedPeerStates.Activatable);
423+
returnv;
424+
}
405425
}
406426

407427
constBindingFlagsActivationConstructorBindingFlags=BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance;
408428
staticreadonlyTypeByRefJniObjectReference=typeof(JniObjectReference).MakeByRefType();
409429
staticreadonlyType[]JIConstructorSignature=newType[]{ByRefJniObjectReference,typeof(JniObjectReferenceOptions)};
410430

411431

412-
protectedvirtualIJavaPeerable?TryCreatePeer(
432+
protectedvirtualboolTryConstructPeer(
433+
IJavaPeerableself,
413434
refJniObjectReferencereference,
414435
JniObjectReferenceOptionsoptions,
415436
[DynamicallyAccessedMembers(Constructors)]
@@ -421,11 +442,11 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
421442
reference,
422443
options,
423444
};
424-
varp=(IJavaPeerable)c.Invoke(args);
445+
c.Invoke(self,args);
425446
reference=(JniObjectReference)args[0];
426-
returnp;
447+
returntrue;
427448
}
428-
returnnull;
449+
returnfalse;
429450
}
430451

431452
publicobject?CreateValue(

‎src/Java.Interop/PublicAPI.Unshipped.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ static Java.Interop.JniEnvironment.EndMarshalMethod(ref Java.Interop.JniTransiti
44
virtual Java.Interop.JniRuntime.OnEnterMarshalMethod() -> void
55
virtual Java.Interop.JniRuntime.OnUserUnhandledException(ref Java.Interop.JniTransition transition, System.Exception! e) -> void
66
virtual Java.Interop.JniRuntime.JniTypeManager.GetInvokerTypeCore(System.Type! type) -> System.Type?
7-
virtual Java.Interop.JniRuntime.JniValueManager.TryCreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) ->Java.Interop.IJavaPeerable?
7+
virtual Java.Interop.JniRuntime.JniValueManager.TryConstructPeer(Java.Interop.IJavaPeerable! self,ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) ->bool
88
Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void
99
Java.Interop.JavaException.SetJavaStackTrace(Java.Interop.JniObjectReference peerReferenceOverride = default(Java.Interop.JniObjectReference)) -> void
1010
Java.Interop.JniRuntime.JniTypeManager.GetInvokerType(System.Type! type) -> System.Type?

‎src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ public override void AddPeer (IJavaPeerable value)
5757
varr=value.PeerReference;
5858
if(!r.IsValid)
5959
thrownewObjectDisposedException(value.GetType().FullName);
60-
varo=PeekPeer(value.PeerReference);
61-
if(o!=null)
62-
return;
6360

6461
if(r.Type!=JniObjectReferenceType.Global){
6562
value.SetPeerReference(r.NewGlobalRef());
@@ -80,7 +77,7 @@ public override void AddPeer (IJavaPeerable value)
8077
varp=peers[i];
8178
if(!JniEnvironment.Types.IsSameObject(p.PeerReference,value.PeerReference))
8279
continue;
83-
if(Replaceable(p)){
80+
if(Replaceable(p,value)){
8481
peers[i]=value;
8582
}else{
8683
WarnNotReplacing(key,value,p);
@@ -91,11 +88,12 @@ public override void AddPeer (IJavaPeerable value)
9188
}
9289
}
9390

94-
staticboolReplaceable(IJavaPeerablepeer)
91+
staticboolReplaceable(IJavaPeerablepeer,IJavaPeerablevalue)
9592
{
9693
if(peer==null)
9794
returntrue;
98-
return(peer.JniManagedPeerState&JniManagedPeerStates.Replaceable)==JniManagedPeerStates.Replaceable;
95+
returnpeer.JniManagedPeerState.HasFlag(JniManagedPeerStates.Replaceable)&&
96+
!value.JniManagedPeerState.HasFlag(JniManagedPeerStates.Replaceable);
9997
}
10098

10199
voidWarnNotReplacing(intkey,IJavaPeerableignoreValue,IJavaPeerablekeepValue)

‎tests/Java.Interop-Tests/Java.Interop/CallVirtualFromConstructorBase.cs‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
usingSystem;
2+
usingSystem.Runtime.CompilerServices;
23

34
usingJava.Interop;
45
usingJava.Interop.GenericMarshaler;
@@ -16,14 +17,31 @@ public override JniPeerMembers JniPeerMembers {
1617
}
1718

1819
publicunsafeCallVirtualFromConstructorBase(intvalue)
20+
:this(value,useNewObject:false)
21+
{
22+
}
23+
24+
publicunsafeCallVirtualFromConstructorBase(intvalue,booluseNewObject)
1925
:this(ref*InvalidJniObjectReference,JniObjectReferenceOptions.None)
2026
{
2127
if(PeerReference.IsValid)
2228
return;
2329

24-
varpeer=JniPeerMembers.InstanceMethods.StartGenericCreateInstance("(I)V",GetType(),value);
30+
conststring__id="(I)V";
31+
32+
if(useNewObject){
33+
varctors=JniPeerMembers.InstanceMethods.GetConstructorsForType(GetType());
34+
varinit=ctors.GetConstructor(__id);
35+
36+
JniArgumentValue*__args=stackallocJniArgumentValue[1];
37+
__args[0]=newJniArgumentValue(value);
38+
varlref=JniEnvironment.Object.NewObject(ctors.JniPeerType.PeerReference,init,__args);
39+
Construct(reflref,JniObjectReferenceOptions.CopyAndDispose);
40+
return;
41+
}
42+
varpeer=JniPeerMembers.InstanceMethods.StartGenericCreateInstance(__id,GetType(),value);
2543
Construct(refpeer,JniObjectReferenceOptions.CopyAndDispose);
26-
JniPeerMembers.InstanceMethods.FinishGenericCreateInstance("(I)V",this,value);
44+
JniPeerMembers.InstanceMethods.FinishGenericCreateInstance(__id,this,value);
2745
}
2846

2947
publicCallVirtualFromConstructorBase(refJniObjectReferencereference,JniObjectReferenceOptionsoptions)

‎tests/Java.Interop-Tests/Java.Interop/CallVirtualFromConstructorDerived.cs‎

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
usingSystem;
1+
#nullable enable
2+
3+
usingSystem;
4+
usingSystem.Runtime.CompilerServices;
25

36
usingJava.Interop;
47

@@ -24,21 +27,38 @@ public override JniPeerMembers JniPeerMembers {
2427
publicboolInvokedConstructor;
2528

2629
publicCallVirtualFromConstructorDerived(intvalue)
27-
:base(value)
30+
:this(value,useNewObject:false)
31+
{
32+
}
33+
34+
publicCallVirtualFromConstructorDerived(intvalue,booluseNewObject)
35+
:base(value,useNewObject)
2836
{
2937
InvokedConstructor=true;
30-
if(value!=calledValue)
38+
39+
if(useNewObject&&calledValue!=0){
40+
// calledValue was set on a *different* instance! So it's 0 here.
41+
thrownewArgumentException(
42+
string.Format("value '{0}' doesn't match expected value '{1}'.",value,0),
43+
"value");
44+
}
45+
if(!useNewObject&&value!=calledValue)
3146
thrownewArgumentException(
3247
string.Format("value '{0}' doesn't match expected value '{1}'.",value,calledValue),
3348
"value");
3449
}
3550

3651
publicboolInvokedActivationConstructor;
3752

53+
publicstaticCallVirtualFromConstructorDerived?Intermediate_FromCalledFromConstructor;
54+
publicstaticCallVirtualFromConstructorDerived?Intermediate_FromActivationConstructor;
55+
3856
publicCallVirtualFromConstructorDerived(refJniObjectReferencereference,JniObjectReferenceOptionsoptions)
3957
:base(refreference,options)
4058
{
4159
InvokedActivationConstructor=true;
60+
61+
Intermediate_FromActivationConstructor=this;
4262
}
4363

4464
publicboolCalled;
@@ -47,14 +67,16 @@ public override void CalledFromConstructor (int value)
4767
{
4868
Called=true;
4969
calledValue=value;
70+
71+
Intermediate_FromCalledFromConstructor=this;
5072
}
5173

5274
publicstaticunsafeCallVirtualFromConstructorDerivedNewInstance(intvalue)
5375
{
5476
JniArgumentValue*args=stackallocJniArgumentValue[1];
5577
args[0]=newJniArgumentValue(value);
5678
varo=_members.StaticMethods.InvokeObjectMethod("newInstance.(I)Lnet/dot/jni/test/CallVirtualFromConstructorDerived;",args);
57-
returnJniEnvironment.Runtime.ValueManager.GetValue<CallVirtualFromConstructorDerived>(refo,JniObjectReferenceOptions.CopyAndDispose);
79+
returnJniEnvironment.Runtime.ValueManager.GetValue<CallVirtualFromConstructorDerived>(refo,JniObjectReferenceOptions.CopyAndDispose)!;
5880
}
5981

6082
delegatevoidCalledFromConstructorMarshalMethod(IntPtrjnienv,IntPtrn_self,intvalue);
@@ -63,7 +85,7 @@ static void CalledFromConstructorHandler (IntPtr jnienv, IntPtr n_self, int valu
6385
varenvp=newJniTransition(jnienv);
6486
try{
6587
varr_self=newJniObjectReference(n_self);
66-
varself=JniEnvironment.Runtime.ValueManager.GetValue<CallVirtualFromConstructorDerived>(refr_self,JniObjectReferenceOptions.Copy);
88+
varself=JniEnvironment.Runtime.ValueManager.GetValue<CallVirtualFromConstructorDerived>(refr_self,JniObjectReferenceOptions.Copy)!;
6789
self.CalledFromConstructor(value);
6890
self.DisposeUnlessReferenced();
6991
}

‎tests/Java.Interop-Tests/Java.Interop/InvokeVirtualFromConstructorTests.cs‎

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
usingSystem;
2+
usingSystem.Runtime.CompilerServices;
23

34
usingJava.Interop;
45

@@ -7,25 +8,145 @@
78
namespaceJava.InteropTests{
89

910
[TestFixture]
11+
#if!__ANDROID__
12+
// We want stability around the CallVirtualFromConstructorDerived static fields
13+
[NonParallelizable]
14+
#endif// !__ANDROID__
1015
publicclassInvokeVirtualFromConstructorTests:JavaVMFixture
1116
{
1217
[Test]
13-
publicvoidInvokeVirtualFromConstructor()
18+
publicvoidCreateManagedInstanceFirst_WithAllocObject()
1419
{
15-
using(vart=newCallVirtualFromConstructorDerived(42)){
16-
Assert.IsTrue(t.Called);
17-
Assert.IsNotNull(JniRuntime.CurrentRuntime.ValueManager.PeekValue(t.PeerReference));
18-
}
20+
CallVirtualFromConstructorDerived.Intermediate_FromActivationConstructor=null;
21+
CallVirtualFromConstructorDerived.Intermediate_FromCalledFromConstructor=null;
22+
23+
usingvart=newCallVirtualFromConstructorDerived(42);
24+
Assert.IsTrue(
25+
t.Called,
26+
"CalledFromConstructor method override should have been called.");
27+
Assert.IsFalse(
28+
t.InvokedActivationConstructor,
29+
"Activation Constructor should have been called, as calledFromConstructor() is invoked before ManagedPeer.construct().");
30+
Assert.IsTrue(
31+
t.InvokedConstructor,
32+
"(int) constructor should have been called, via ManagedPeer.construct().");
33+
34+
varregistered=JniRuntime.CurrentRuntime.ValueManager.PeekValue(t.PeerReference);
35+
varacIntermediate=CallVirtualFromConstructorDerived.Intermediate_FromActivationConstructor;
36+
varcfIntermediate=CallVirtualFromConstructorDerived.Intermediate_FromCalledFromConstructor;
37+
38+
Assert.AreSame(t,registered,
39+
"Expected t and registered to be the same instance; "+
40+
$"t={RuntimeHelpers.GetHashCode(t).ToString("x")}, "+
41+
$"registered={RuntimeHelpers.GetHashCode(registered).ToString("x")}");
42+
Assert.IsNull(acIntermediate,
43+
"Activation Constructor should not have been called, because of AllocObject semantics");
44+
Assert.AreSame(t,cfIntermediate,
45+
"Expected t and cfIntermediate to be the same instance; "+
46+
$"t={RuntimeHelpers.GetHashCode(t).ToString("x")}, "+
47+
$"cfIntermediate={RuntimeHelpers.GetHashCode(cfIntermediate).ToString("x")}");
48+
49+
Dispose(refCallVirtualFromConstructorDerived.Intermediate_FromActivationConstructor);
50+
Dispose(refCallVirtualFromConstructorDerived.Intermediate_FromCalledFromConstructor);
51+
}
52+
53+
staticvoidDispose<T>(refTpeer)
54+
whereT:class,IJavaPeerable
55+
{
56+
if(peer==null)
57+
return;
58+
59+
peer.Dispose();
60+
peer=null;
1961
}
2062

2163
[Test]
22-
publicvoidActivationConstructor()
64+
publicvoidCreateManagedInstanceFirst_WithNewObject()
2365
{
24-
vart=CallVirtualFromConstructorDerived.NewInstance(42);
25-
using(t){
26-
Assert.IsTrue(t.InvokedActivationConstructor);
27-
Assert.IsTrue(t.InvokedConstructor);
28-
}
66+
CallVirtualFromConstructorDerived.Intermediate_FromActivationConstructor=null;
67+
CallVirtualFromConstructorDerived.Intermediate_FromCalledFromConstructor=null;
68+
69+
usingvart=newCallVirtualFromConstructorDerived(42,useNewObject:true);
70+
Assert.IsFalse(
71+
t.Called,
72+
"CalledFromConstructor method override was called on a different instance.");
73+
Assert.IsFalse(
74+
t.InvokedActivationConstructor,
75+
"Activation Constructor should not have been called, as calledFromConstructor() is invoked before ManagedPeer.construct().");
76+
Assert.IsTrue(
77+
t.InvokedConstructor,
78+
"(int) constructor should have been called, via ManagedPeer.construct().");
79+
80+
varregistered=JniRuntime.CurrentRuntime.ValueManager.PeekValue(t.PeerReference);
81+
varacIntermediate=CallVirtualFromConstructorDerived.Intermediate_FromActivationConstructor;
82+
varcfIntermediate=CallVirtualFromConstructorDerived.Intermediate_FromCalledFromConstructor;
83+
84+
Assert.AreSame(t,registered,
85+
"Expected t and registered to be the same instance; "+
86+
$"t={RuntimeHelpers.GetHashCode(t).ToString("x")}, "+
87+
$"registered={RuntimeHelpers.GetHashCode(registered).ToString("x")}");
88+
Assert.IsNotNull(acIntermediate,
89+
"Activation Constructor should have been called, because of NewObject");
90+
Assert.IsTrue(
91+
acIntermediate.Called,
92+
"CalledFromConstructor method override should have been called on acIntermediate.");
93+
Assert.IsTrue(
94+
acIntermediate.InvokedActivationConstructor,
95+
"Activation Constructor should have been called on intermediate instance, as calledFromConstructor() is invoked before ManagedPeer.construct().");
96+
Assert.AreNotSame(t,acIntermediate,
97+
"Expected t and registered to be different instances; "+
98+
$"t={RuntimeHelpers.GetHashCode(t).ToString("x")}, "+
99+
$"acIntermediate={RuntimeHelpers.GetHashCode(acIntermediate).ToString("x")}");
100+
Assert.AreNotSame(t,cfIntermediate,
101+
"Expected t and cfIntermediate to be different instances; "+
102+
$"t={RuntimeHelpers.GetHashCode(t).ToString("x")}, "+
103+
$"cfIntermediate={RuntimeHelpers.GetHashCode(cfIntermediate).ToString("x")}");
104+
Assert.AreSame(acIntermediate,cfIntermediate,
105+
"Expected acIntermediate and cfIntermediate to be the same instance; "+
106+
$"acIntermediate={RuntimeHelpers.GetHashCode(acIntermediate).ToString("x")}, "+
107+
$"cfIntermediate={RuntimeHelpers.GetHashCode(cfIntermediate).ToString("x")}");
108+
109+
Dispose(refCallVirtualFromConstructorDerived.Intermediate_FromActivationConstructor);
110+
Dispose(refCallVirtualFromConstructorDerived.Intermediate_FromCalledFromConstructor);
111+
}
112+
113+
[Test]
114+
publicvoidCreateJavaInstanceFirst()
115+
{
116+
CallVirtualFromConstructorDerived.Intermediate_FromActivationConstructor=null;
117+
CallVirtualFromConstructorDerived.Intermediate_FromCalledFromConstructor=null;
118+
119+
usingvart=CallVirtualFromConstructorDerived.NewInstance(42);
120+
121+
Assert.IsTrue(
122+
t.Called,
123+
"CalledFromConstructor method override should have been called.");
124+
Assert.IsTrue(
125+
t.InvokedActivationConstructor,
126+
"Activation Constructor should have been called, as calledFromConstructor() is invoked before ManagedPeer.construct().");
127+
Assert.IsTrue(
128+
t.InvokedConstructor,
129+
"(int) constructor should have been called, via ManagedPeer.construct().");
130+
131+
varregistered=JniRuntime.CurrentRuntime.ValueManager.PeekValue(t.PeerReference);
132+
varacIntermediate=CallVirtualFromConstructorDerived.Intermediate_FromActivationConstructor;
133+
varcfIntermediate=CallVirtualFromConstructorDerived.Intermediate_FromCalledFromConstructor;
134+
135+
Assert.AreSame(t,registered,
136+
"Expected t and registered to be the same instance; "+
137+
$"t={RuntimeHelpers.GetHashCode(t).ToString("x")}, "+
138+
$"registered={RuntimeHelpers.GetHashCode(registered).ToString("x")}");
139+
Assert.AreSame(t,acIntermediate,
140+
"Expected t and registered to be the same instance; "+
141+
$"t={RuntimeHelpers.GetHashCode(t).ToString("x")}, "+
142+
$"acIntermediate={RuntimeHelpers.GetHashCode(acIntermediate).ToString("x")}");
143+
Assert.AreSame(t,cfIntermediate,
144+
"Expected t and cfIntermediate to be the same instance; "+
145+
$"t={RuntimeHelpers.GetHashCode(t).ToString("x")}, "+
146+
$"cfIntermediate={RuntimeHelpers.GetHashCode(cfIntermediate).ToString("x")}");
147+
148+
Dispose(refCallVirtualFromConstructorDerived.Intermediate_FromActivationConstructor);
149+
Dispose(refCallVirtualFromConstructorDerived.Intermediate_FromCalledFromConstructor);
29150
}
30151
}
31152
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp