diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs
index c9b620ff18f..afa9ea3c3d9 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs
@@ -109,14 +109,15 @@ static JniParamKind ParseSingleType (string sig, ref int i)
/// Encodes a JNI type as its CLR equivalent for [UnmanagedCallersOnly] UCO wrapper signatures.
///
///
- /// JNI boolean (Z) maps to byte (unsigned, blittable for the JNI ABI).
+ /// JNI boolean (Z) maps to byte and JNI char (C) maps to ushort,
+ /// preserving the JNI ABI with blittable UCO parameter types.
///
public static void EncodeClrType (SignatureTypeEncoder encoder, JniParamKind kind)
{
switch (kind) {
case JniParamKind.Boolean: encoder.Byte (); break; // JNI jboolean is unsigned byte; blittable for UCO
case JniParamKind.Byte: encoder.SByte (); break;
- case JniParamKind.Char: encoder.Char (); break;
+ case JniParamKind.Char: encoder.UInt16 (); break;
case JniParamKind.Short: encoder.Int16 (); break;
case JniParamKind.Int: encoder.Int32 (); break;
case JniParamKind.Long: encoder.Int64 (); break;
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
index b9126586bf4..ecd92f53e19 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
@@ -204,6 +204,7 @@ sealed record UcoMethodData
/// JNI method signature, e.g., "(Landroid/os/Bundle;)V". Used to determine CLR parameter types.
///
public required string JniSignature { get; init; }
+
}
///
@@ -228,6 +229,16 @@ sealed record UcoConstructorData
/// JNI constructor signature, e.g., "(Landroid/content/Context;)V". Used for RegisterNatives registration.
///
public required string JniSignature { get; init; }
+
+ ///
+ /// Managed constructor parameter type names, in declaration order.
+ ///
+ public IReadOnlyList ManagedParameterTypes { get; init; } = [];
+
+ ///
+ /// True when this Java constructor has a matching managed constructor on the target type.
+ ///
+ public bool HasManagedConstructor { get; init; }
}
///
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
index 7547c5ac38f..21ce7d7d66d 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
@@ -365,6 +365,8 @@ static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy)
ManagedTypeName = peer.ManagedTypeName,
AssemblyName = peer.AssemblyName,
},
+ ManagedParameterTypes = ctor.ManagedParameterTypes,
+ HasManagedConstructor = ctor.HasManagedConstructor,
});
}
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs
index f48ebd8c72a..18fe552561d 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs
@@ -611,6 +611,12 @@ public void Throw ()
SetStack (0);
}
+ public void PopValue ()
+ {
+ Encoder.OpCode (ILOpCode.Pop);
+ Pop (1);
+ }
+
public void OpCode (ILOpCode code)
{
Encoder.OpCode (code);
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
index 67761e0d617..1eb16ffbf8b 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
@@ -87,7 +87,9 @@ sealed class TypeMapAssemblyEmitter
TypeReferenceHandle _jniObjectReferenceOptionsRef;
TypeReferenceHandle _iAndroidCallableWrapperRef;
TypeReferenceHandle _jniEnvRef;
+ TypeReferenceHandle _javaLangObjectRef;
TypeReferenceHandle _systemTypeRef;
+ TypeReferenceHandle _systemArrayRef;
TypeReferenceHandle _runtimeTypeHandleRef;
TypeReferenceHandle _jniTypeRef;
TypeReferenceHandle _notSupportedExceptionRef;
@@ -100,7 +102,13 @@ sealed class TypeMapAssemblyEmitter
MemberReferenceHandle _notSupportedExceptionCtorRef;
MemberReferenceHandle _jniObjectReferenceCtorRef;
MemberReferenceHandle _jniEnvDeleteRefRef;
+ MemberReferenceHandle _jniEnvGetStringRef;
+ MemberReferenceHandle _jniEnvGetArrayRef;
+ MemberReferenceHandle _javaLangObjectGetObjectRef;
MemberReferenceHandle _shouldSkipActivationRef;
+ MemberReferenceHandle _getActivationPeerRef;
+ MemberReferenceHandle _setActivationPeerReferenceRef;
+ MemberReferenceHandle _markActivationPeerReplaceableRef;
MemberReferenceHandle _waitForBridgeProcessingRef;
MemberReferenceHandle _androidEnvironmentUnhandledExceptionRef;
MemberReferenceHandle _ucoAttrCtorRef;
@@ -228,6 +236,8 @@ void EmitTypeReferences ()
metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JniHandleOwnership"));
_jniEnvRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JNIEnv"));
+ _javaLangObjectRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
+ metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("Object"));
_jniObjectReferenceRef = metadata.AddTypeReference (_javaInteropRef,
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniObjectReference"));
_jniObjectReferenceTypeRef = metadata.AddTypeReference (_javaInteropRef,
@@ -238,6 +248,8 @@ void EmitTypeReferences ()
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("IAndroidCallableWrapper"));
_systemTypeRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Type"));
+ _systemArrayRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
+ metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Array"));
_runtimeTypeHandleRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("RuntimeTypeHandle"));
_jniTypeRef = metadata.AddTypeReference (_javaInteropRef,
@@ -357,12 +369,56 @@ void EmitMemberReferences ()
p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
}));
+ _jniEnvGetStringRef = _pe.AddMemberRef (_jniEnvRef, "GetString",
+ sig => sig.MethodSignature ().Parameters (2,
+ rt => rt.Type ().String (),
+ p => {
+ p.AddParameter ().Type ().IntPtr ();
+ p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
+ }));
+
+ _jniEnvGetArrayRef = _pe.AddMemberRef (_jniEnvRef, "GetArray",
+ sig => sig.MethodSignature ().Parameters (3,
+ rt => rt.Type ().Type (_systemArrayRef, false),
+ p => {
+ p.AddParameter ().Type ().IntPtr ();
+ p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
+ p.AddParameter ().Type ().Type (_systemTypeRef, false);
+ }));
+
+ _javaLangObjectGetObjectRef = _pe.AddMemberRef (_javaLangObjectRef, "GetObject",
+ sig => sig.MethodSignature ().Parameters (3,
+ rt => rt.Type ().Type (_iJavaPeerableRef, false),
+ p => {
+ p.AddParameter ().Type ().IntPtr ();
+ p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
+ p.AddParameter ().Type ().Type (_systemTypeRef, false);
+ }));
+
// JavaPeerProxy.ShouldSkipActivation(IntPtr) -> bool (static method)
_shouldSkipActivationRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "ShouldSkipActivation",
sig => sig.MethodSignature ().Parameters (1,
rt => rt.Type ().Boolean (),
p => { p.AddParameter ().Type ().IntPtr (); }));
+ _getActivationPeerRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "GetActivationPeer",
+ sig => sig.MethodSignature ().Parameters (1,
+ rt => rt.Type ().Type (_iJavaPeerableRef, false),
+ p => { p.AddParameter ().Type ().IntPtr (); }));
+
+ _setActivationPeerReferenceRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "SetActivationPeerReference",
+ sig => sig.MethodSignature ().Parameters (2,
+ rt => rt.Void (),
+ p => {
+ p.AddParameter ().Type ().Type (_iJavaPeerableRef, false);
+ p.AddParameter ().Type ().IntPtr ();
+ }));
+
+ _markActivationPeerReplaceableRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "MarkActivationPeerReplaceable",
+ sig => sig.MethodSignature ().Parameters (1,
+ rt => rt.Void (),
+ p => p.AddParameter ().Type ().IntPtr ()));
+
_waitForBridgeProcessingRef = _pe.AddMemberRef (_androidRuntimeInternalRef, "WaitForBridgeProcessing",
sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }));
@@ -918,6 +974,18 @@ MemberReferenceHandle AddActivationCtorRef (EntityHandle declaringTypeRef)
}));
}
+ MemberReferenceHandle AddManagedCtorRef (EntityHandle declaringTypeRef, IReadOnlyList parameterTypes, string defaultAssemblyName)
+ {
+ var blob = new BlobBuilder (32);
+ blob.WriteByte (0x20); // HASTHIS
+ blob.WriteCompressedInteger (parameterTypes.Count);
+ blob.WriteByte (0x01); // ELEMENT_TYPE_VOID
+ foreach (var parameterType in parameterTypes) {
+ WriteManagedTypeSignature (blob, parameterType, defaultAssemblyName);
+ }
+ return _pe.Metadata.AddMemberReference (declaringTypeRef, _pe.Metadata.GetOrAddString (".ctor"), _pe.Metadata.GetOrAddBlob (blob));
+ }
+
MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco, JavaPeerProxyData proxy)
{
var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature);
@@ -1011,9 +1079,8 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
$"UCO constructor wrapper requires an activation ctor for '{uco.TargetType.ManagedTypeName}'");
// UCO constructor wrappers must match the JNI native method signature exactly.
- // Only jnienv (arg 0) and self (arg 1) are used — the constructor parameters
- // are not forwarded because we create the managed peer using the
- // activation ctor (IntPtr, JniHandleOwnership), not the user-visible constructor.
+ // jnienv and self are followed by the Java constructor parameters, which are
+ // forwarded when scanner metadata can identify the matching managed constructor.
var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature);
int paramCount = 2 + jniParams.Count;
@@ -1028,19 +1095,65 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
// Open generic types can't be activated because Java construction cannot provide the type arguments.
if (proxy.IsGenericDefinition) {
- var openGenericHandle = _pe.EmitBody (uco.WrapperName,
- MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
- encodeSig,
- (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => {
+ var openGenericHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig,
+ enc => {
enc.LoadString (_pe.Metadata.GetOrAddUserString ("Constructing instances of generic types from Java is not supported, as the type parameters cannot be determined."));
enc.NewObject (_notSupportedExceptionCtorRef, parameterCount: 1);
enc.Throw ();
- }),
+ },
EncodeUcoConstructorLocals_Standard);
AddUnmanagedCallersOnlyAttribute (openGenericHandle);
return openGenericHandle;
}
+ if (proxy.InvokerType != null) {
+ var invokerTypeRef = _pe.ResolveTypeRef (proxy.InvokerType);
+ MethodDefinitionHandle invokerHandle;
+ if (proxy.InvokerActivationCtorStyle == ActivationCtorStyle.JavaInterop) {
+ var ctorRef = AddJavaInteropActivationCtorRef (invokerTypeRef);
+ invokerHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig,
+ enc => {
+ enc.LoadLocalAddress (3); // jniRef
+ enc.LoadArgument (1); // self
+ enc.LoadConstantI4 (0); // JniObjectReferenceType.Invalid
+ enc.Call (_jniObjectReferenceCtorRef, parameterCount: 2, isInstance: true);
+
+ enc.LoadLocalAddress (3); // ref jniRef
+ enc.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
+ enc.NewObject (ctorRef, parameterCount: 2);
+ enc.PopValue ();
+
+ enc.LoadArgument (1); // self
+ enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1);
+ },
+ EncodeUcoConstructorLocals_JavaInterop);
+ } else {
+ var ctorRef = AddActivationCtorRef (invokerTypeRef);
+ invokerHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig,
+ enc => {
+ enc.LoadArgument (1); // self
+ enc.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
+ enc.NewObject (ctorRef, parameterCount: 2);
+ enc.PopValue ();
+
+ enc.LoadArgument (1); // self
+ enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1);
+ },
+ EncodeUcoConstructorLocals_Standard);
+ }
+ AddUnmanagedCallersOnlyAttribute (invokerHandle);
+ return invokerHandle;
+ }
+
+ if (uco.HasManagedConstructor && uco.ManagedParameterTypes.Count == jniParams.Count) {
+ var ctorRef = AddManagedCtorRef (targetTypeRef, uco.ManagedParameterTypes, uco.TargetType.AssemblyName);
+ var managedCtorHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig,
+ enc => EmitManagedConstructorActivation (enc, targetTypeRef, ctorRef, uco.ManagedParameterTypes, jniParams, uco.TargetType.AssemblyName),
+ blob => EncodeUcoConstructorLocals_DefaultConstructor (blob, targetTypeRef));
+ AddUnmanagedCallersOnlyAttribute (managedCtorHandle);
+ return managedCtorHandle;
+ }
+
MethodDefinitionHandle handle;
if (activationCtor.Style == ActivationCtorStyle.JavaInterop) {
var ctorRef = AddJavaInteropActivationCtorRef (
@@ -1051,10 +1164,8 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
// 1: JniRuntime? (runtime) — out-parameter for BeginMarshalMethod
// 2: Exception (e) — catch variable
// 3: JniObjectReference (jniRef) — needed for JavaInterop-style activation
- handle = _pe.EmitBody (uco.WrapperName,
- MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
- encodeSig,
- (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => {
+ handle = EmitUcoConstructorBody (uco.WrapperName, encodeSig,
+ enc => {
if (!activationCtor.IsOnLeafType) {
enc.LoadToken (targetTypeRef);
enc.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
@@ -1071,13 +1182,15 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
enc.LoadLocalAddress (3); // ref jniRef
enc.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
enc.NewObject (ctorRef, parameterCount: 2);
- enc.OpCode (ILOpCode.Pop);
+ enc.PopValue ();
} else {
enc.LoadLocalAddress (3); // ref jniRef
enc.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
enc.Call (ctorRef, parameterCount: 2, isInstance: true);
}
- }),
+ enc.LoadArgument (1); // self
+ enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1);
+ },
EncodeUcoConstructorLocals_JavaInterop);
} else {
var ctorRef = AddActivationCtorRef (
@@ -1087,15 +1200,13 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
// 0: JniTransition (envp) — out-parameter for BeginMarshalMethod
// 1: JniRuntime? (runtime) — out-parameter for BeginMarshalMethod
// 2: Exception (e) — catch variable
- handle = _pe.EmitBody (uco.WrapperName,
- MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
- encodeSig,
- (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => {
+ handle = EmitUcoConstructorBody (uco.WrapperName, encodeSig,
+ enc => {
if (activationCtor.IsOnLeafType) {
enc.LoadArgument (1); // self
enc.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
enc.NewObject (ctorRef, parameterCount: 2);
- enc.OpCode (ILOpCode.Pop);
+ enc.PopValue ();
} else {
enc.LoadToken (targetTypeRef);
enc.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
@@ -1106,13 +1217,67 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
enc.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
enc.Call (ctorRef, parameterCount: 2, isInstance: true);
}
- }),
+ enc.LoadArgument (1); // self
+ enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1);
+ },
EncodeUcoConstructorLocals_Standard);
}
AddUnmanagedCallersOnlyAttribute (handle);
return handle;
}
+ MethodDefinitionHandle EmitUcoConstructorBody (
+ string wrapperName,
+ Action encodeSig,
+ Action emitActivation,
+ Action encodeLocals)
+ {
+ return _pe.EmitBody (wrapperName,
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
+ encodeSig,
+ (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, emitActivation),
+ encodeLocals);
+ }
+
+ void EmitManagedConstructorActivation (
+ TrackedInstructionEncoder enc,
+ EntityHandle targetTypeRef,
+ MemberReferenceHandle ctorRef,
+ IReadOnlyList managedParameterTypes,
+ IReadOnlyList jniParams,
+ string defaultAssemblyName)
+ {
+ var havePeer = enc.DefineLabel ();
+
+ enc.LoadArgument (1);
+ enc.Call (_getActivationPeerRef, parameterCount: 1, returnsValue: true);
+ enc.CastClass (targetTypeRef);
+ enc.StoreLocal (4);
+
+ enc.LoadLocal (4);
+ enc.Branch (ILOpCode.Brtrue, havePeer);
+
+ enc.LoadToken (targetTypeRef);
+ enc.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
+ enc.Call (_getUninitializedObjectRef, parameterCount: 1, returnsValue: true);
+ enc.CastClass (targetTypeRef);
+ enc.StoreLocal (4);
+
+ enc.LoadLocal (4);
+ enc.LoadArgument (1); // self
+ enc.Call (_setActivationPeerReferenceRef, parameterCount: 2);
+
+ enc.MarkLabel (havePeer);
+ enc.LoadLocal (4);
+ for (int i = 0; i < managedParameterTypes.Count; i++) {
+ EmitManagedConstructorArgument (enc, managedParameterTypes [i], jniParams [i], i + 2, defaultAssemblyName);
+ }
+ enc.Call (ctorRef, managedParameterTypes.Count, isInstance: true);
+
+ enc.LoadArgument (1); // self
+ enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1);
+ }
+
///
/// Emits the common try/catch/finally marshal-method wrapper pattern used by all
/// non-generic UCO constructor bodies:
@@ -1185,6 +1350,111 @@ void EmitUcoConstructorBodyWithMarshal (TrackedInstructionEncoder encoder, Contr
cfb.AddFinallyRegion (tryStart, finallyStart, finallyStart, afterAll);
}
+ void EmitManagedConstructorArgument (TrackedInstructionEncoder encoder, string managedType, JniParamKind jniKind, int argumentIndex, string defaultAssemblyName)
+ {
+ if (jniKind != JniParamKind.Object) {
+ encoder.LoadArgument (argumentIndex);
+ return;
+ }
+
+ if (managedType == "System.String") {
+ encoder.LoadArgument (argumentIndex);
+ encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
+ encoder.Call (_jniEnvGetStringRef, parameterCount: 2, returnsValue: true);
+ return;
+ }
+
+ if (TryGetSzArrayElementType (managedType, out var elementType)) {
+ var arrayType = ResolveManagedTypeHandle (managedType, defaultAssemblyName);
+ var elementTypeHandle = ResolveManagedTypeHandle (elementType, defaultAssemblyName);
+
+ encoder.LoadArgument (argumentIndex);
+ encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
+ encoder.LoadToken (elementTypeHandle);
+ encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
+ encoder.Call (_jniEnvGetArrayRef, parameterCount: 3, returnsValue: true);
+ encoder.CastClass (arrayType);
+ return;
+ }
+
+ var managedTypeHandle = ResolveManagedTypeHandle (managedType, defaultAssemblyName);
+ encoder.LoadArgument (argumentIndex);
+ encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
+ encoder.LoadToken (managedTypeHandle);
+ encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
+ encoder.Call (_javaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true);
+ encoder.CastClass (managedTypeHandle);
+ }
+
+ EntityHandle ResolveManagedTypeHandle (string managedType, string defaultAssemblyName)
+ {
+ if (TryGetSzArrayElementType (managedType, out var elementType)) {
+ var blob = new BlobBuilder (32);
+ blob.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY
+ WriteManagedTypeSignature (blob, elementType, defaultAssemblyName);
+ return _pe.Metadata.AddTypeSpecification (_pe.Metadata.GetOrAddBlob (blob));
+ }
+
+ return _pe.ResolveTypeRef (new TypeRefData {
+ ManagedTypeName = managedType,
+ AssemblyName = GetAssemblyNameForManagedType (managedType, defaultAssemblyName),
+ });
+ }
+
+ static bool TryGetSzArrayElementType (string managedType, out string elementType)
+ {
+ if (managedType.EndsWith ("[]", StringComparison.Ordinal)) {
+ elementType = managedType.Substring (0, managedType.Length - 2);
+ return true;
+ }
+
+ elementType = "";
+ return false;
+ }
+
+ void WriteManagedTypeSignature (BlobBuilder blob, string managedType, string defaultAssemblyName)
+ {
+ if (TryGetSzArrayElementType (managedType, out var elementType)) {
+ blob.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY
+ WriteManagedTypeSignature (blob, elementType, defaultAssemblyName);
+ return;
+ }
+
+ switch (managedType) {
+ case "System.Boolean": blob.WriteByte (0x02); return;
+ case "System.Char": blob.WriteByte (0x03); return;
+ case "System.SByte": blob.WriteByte (0x04); return;
+ case "System.Byte": blob.WriteByte (0x05); return;
+ case "System.Int16": blob.WriteByte (0x06); return;
+ case "System.UInt16": blob.WriteByte (0x07); return;
+ case "System.Int32": blob.WriteByte (0x08); return;
+ case "System.UInt32": blob.WriteByte (0x09); return;
+ case "System.Int64": blob.WriteByte (0x0A); return;
+ case "System.UInt64": blob.WriteByte (0x0B); return;
+ case "System.Single": blob.WriteByte (0x0C); return;
+ case "System.Double": blob.WriteByte (0x0D); return;
+ case "System.String": blob.WriteByte (0x0E); return;
+ case "System.Object": blob.WriteByte (0x1C); return;
+ }
+
+ var typeHandle = ResolveManagedTypeHandle (managedType, defaultAssemblyName);
+ blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (typeHandle));
+ }
+
+ static string GetAssemblyNameForManagedType (string managedType, string defaultAssemblyName)
+ {
+ if (managedType.StartsWith ("System.", StringComparison.Ordinal)) {
+ return "System.Runtime";
+ }
+ if (managedType.StartsWith ("Android.", StringComparison.Ordinal) ||
+ managedType.StartsWith ("Java.", StringComparison.Ordinal) ||
+ managedType.StartsWith ("Javax.", StringComparison.Ordinal)) {
+ return "Mono.Android";
+ }
+ return defaultAssemblyName;
+ }
+
///
/// LOCAL_SIG for UCO constructors without JavaInterop-style activation.
/// Locals: 0=JniTransition, 1=JniRuntime, 2=Exception.
@@ -1226,6 +1496,31 @@ void EncodeUcoConstructorLocals_JavaInterop (BlobBuilder blob)
blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniObjectReferenceRef));
}
+ ///
+ /// LOCAL_SIG for UCO constructors that invoke a no-arg managed constructor.
+ /// Locals: 0=JniTransition, 1=JniRuntime, 2=Exception, 3=JniObjectReference, 4=target type.
+ ///
+ void EncodeUcoConstructorLocals_DefaultConstructor (BlobBuilder blob, EntityHandle targetTypeRef)
+ {
+ blob.WriteByte (0x07); // LOCAL_SIG
+ blob.WriteCompressedInteger (5);
+ // local 0: JniTransition (valuetype)
+ blob.WriteByte (0x11); // ELEMENT_TYPE_VALUETYPE
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniTransitionRef));
+ // local 1: JniRuntime (class)
+ blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniRuntimeRef));
+ // local 2: Exception (class)
+ blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_exceptionRef));
+ // local 3: JniObjectReference (valuetype)
+ blob.WriteByte (0x11); // ELEMENT_TYPE_VALUETYPE
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniObjectReferenceRef));
+ // local 4: target type (class)
+ blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (targetTypeRef));
+ }
+
void EmitRegisterNatives (JavaPeerProxyData proxy,
Dictionary wrapperHandles)
{
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
index ee285b42798..97606a4b5af 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
@@ -224,6 +224,17 @@ public sealed record MarshalMethodInfo
///
public string? SuperArgumentsString { get; init; }
+ ///
+ /// Managed method parameter type names, in declaration order.
+ ///
+ public IReadOnlyList ManagedParameterTypes { get; init; } = [];
+
+ ///
+ /// True when this constructor registration maps to an actual managed constructor on the target type.
+ /// False for constructors inherited from Java base types that only exist to generate Java source.
+ ///
+ public bool HasManagedConstructor { get; init; }
+
///
/// True if this method was collected from an implemented interface
/// (Pass 4: CollectInterfaceMethodImplementations), not from the type itself.
@@ -267,6 +278,16 @@ public sealed record JavaConstructorInfo
/// Null for [Register] constructors.
///
public string? SuperArgumentsString { get; init; }
+
+ ///
+ /// Managed constructor parameter type names, in declaration order.
+ ///
+ public IReadOnlyList ManagedParameterTypes { get; init; } = [];
+
+ ///
+ /// True when this Java constructor has a matching managed constructor on the target type.
+ ///
+ public bool HasManagedConstructor { get; init; }
}
///
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
index dda7460271c..40959a5de2c 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
@@ -556,6 +556,7 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index,
continue;
}
if (!alreadyRegisteredSignatures.Contains (signature)) {
+ var managedParameterTypes = TryGetMatchingPublicConstructorParameterTypes (typeDef, index, baseCtor.Method);
methods.Add (new MarshalMethodInfo {
JniName = baseCtor.RegisterInfo.JniName,
JniSignature = signature,
@@ -563,6 +564,8 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index,
ManagedMethodName = ".ctor",
NativeCallbackName = "n_ctor",
IsConstructor = true,
+ ManagedParameterTypes = managedParameterTypes ?? [],
+ HasManagedConstructor = managedParameterTypes != null,
});
alreadyRegisteredSignatures.Add (signature);
}
@@ -614,6 +617,8 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index,
NativeCallbackName = "n_ctor",
IsConstructor = true,
SuperArgumentsString = "",
+ ManagedParameterTypes = sig.ParameterTypes,
+ HasManagedConstructor = true,
});
alreadyRegisteredSignatures.Add (jniSignature);
}
@@ -621,6 +626,27 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index,
}
}
+ IReadOnlyList? TryGetMatchingPublicConstructorParameterTypes (TypeDefinition typeDef, AssemblyIndex index, MethodDefinition baseCtor)
+ {
+ foreach (var methodHandle in typeDef.GetMethods ()) {
+ var methodDef = index.Reader.GetMethodDefinition (methodHandle);
+ if (index.Reader.GetString (methodDef.Name) != ".ctor") {
+ continue;
+ }
+ if ((methodDef.Attributes & MethodAttributes.MemberAccessMask) != MethodAttributes.Public) {
+ continue;
+ }
+ if (!HaveIdenticalParameterTypes (methodDef, baseCtor)) {
+ continue;
+ }
+
+ var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
+ return sig.ParameterTypes;
+ }
+
+ return null;
+ }
+
string? BuildJniCtorSignature (MethodSignature sig)
{
var sb = new System.Text.StringBuilder ();
@@ -892,6 +918,7 @@ static void AddMarshalMethod (List methods, RegisterInfo regi
bool isExport = exportInfo is not null;
string managedName = index.Reader.GetString (methodDef.Name);
string jniSignature = registerInfo.Signature ?? "()V";
+ var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
string declaringTypeName = "";
string declaringAssemblyName = "";
@@ -911,6 +938,8 @@ static void AddMarshalMethod (List methods, RegisterInfo regi
JavaAccess = isExport ? GetJavaAccess (methodDef.Attributes & MethodAttributes.MemberAccessMask) : null,
ThrownNames = exportInfo?.ThrownNames,
SuperArgumentsString = exportInfo?.SuperArgumentsString,
+ ManagedParameterTypes = sig.ParameterTypes,
+ HasManagedConstructor = isConstructor,
});
}
@@ -1553,6 +1582,8 @@ static List BuildJavaConstructors (List
JniSignature = mm.JniSignature,
ConstructorIndex = ctorIndex,
SuperArgumentsString = mm.SuperArgumentsString,
+ ManagedParameterTypes = mm.ManagedParameterTypes,
+ HasManagedConstructor = mm.HasManagedConstructor,
});
ctorIndex++;
}
diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs
index 88f7fa32244..fec5337d347 100644
--- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs
+++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs
@@ -93,15 +93,45 @@ protected JavaPeerProxy (
/// already registered the peer).
///
public static bool ShouldSkipActivation (IntPtr jniSelf)
+ {
+ var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid);
+ var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (reference);
+ if (peer != null && !IsActivationPeer (peer)) {
+ return true;
+ }
+ return JniEnvironment.WithinNewObjectScope;
+ }
+
+ public static IJavaPeerable? GetActivationPeer (IntPtr jniSelf)
+ {
+ var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid);
+ var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (reference);
+ return peer != null && IsActivationPeer (peer) ? peer : null;
+ }
+
+ public static void SetActivationPeerReference (IJavaPeerable peer, IntPtr jniSelf)
+ {
+ var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid);
+ peer.SetPeerReference (reference);
+ peer.SetJniIdentityHashCode (JniEnvironment.References.GetIdentityHashCode (reference));
+ }
+
+ public static void MarkActivationPeerReplaceable (IntPtr jniSelf)
{
var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid);
var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (reference);
if (peer == null) {
- return false;
+ return;
}
+
+ peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable);
+ }
+
+ static bool IsActivationPeer (IJavaPeerable peer)
+ {
var state = peer.JniManagedPeerState;
- return (state & JniManagedPeerStates.Activatable) != JniManagedPeerStates.Activatable
- && (state & JniManagedPeerStates.Replaceable) != JniManagedPeerStates.Replaceable;
+ return (state & JniManagedPeerStates.Activatable) == JniManagedPeerStates.Activatable
+ || (state & JniManagedPeerStates.Replaceable) == JniManagedPeerStates.Replaceable;
}
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs
index 44b5bff4f85..2355555e9ac 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs
@@ -91,7 +91,7 @@ private protected static JavaPeerInfo MakeAcwPeer (string jniName, string manage
return MakePeerWithActivation (jniName, managedName, asmName) with {
DoNotGenerateAcw = false,
JavaConstructors = new List {
- new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V" },
+ new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V", HasManagedConstructor = true },
},
MarshalMethods = new List {
new MarshalMethodInfo {
@@ -100,6 +100,7 @@ private protected static JavaPeerInfo MakeAcwPeer (string jniName, string manage
JniSignature = "()V",
ManagedMethodName = ".ctor",
IsConstructor = true,
+ HasManagedConstructor = true,
},
},
};
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
index e84d8bcf392..63e166b11b3 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
@@ -288,7 +288,7 @@ public void Generate_LeafCtor_DoesNotUseCreateManagedPeer ()
}
[Fact]
- public void Generate_InheritedCtor_ReferencesGuardAndActivationCtor ()
+ public void Generate_InheritedCtor_NctorUcoCallsDefaultConstructor ()
{
var peers = ScanFixtures ();
var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity");
@@ -302,18 +302,24 @@ public void Generate_InheritedCtor_ReferencesGuardAndActivationCtor ()
Assert.Contains ("ShouldSkipActivation", memberNames);
Assert.Contains ("GetUninitializedObject", memberNames);
+ Assert.Contains ("SetActivationPeerReference", memberNames);
+ Assert.Contains ("MarkActivationPeerReplaceable", memberNames);
Assert.DoesNotContain ("Invoke", memberNames);
Assert.DoesNotContain ("ActivateInstance", memberNames);
Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames);
- Assert.NotEmpty (FindCtorMemberRefs (reader, "Android.App", "Activity",
+ // The new no-arg nctor codegen calls the target type's parameterless .ctor()
+ // directly, not the legacy (IntPtr, JniHandleOwnership) activation ctor on the base.
+ Assert.NotEmpty (FindCtorMemberRefs (reader, "MyApp", "SimpleActivity"));
+ Assert.Empty (FindCtorMemberRefs (reader, "Android.App", "Activity",
"System.IntPtr", "Android.Runtime.JniHandleOwnership"));
+
var nctorMethodHandle = FindNctorUcoMethod (reader);
Assert.False (nctorMethodHandle.IsNil, "SimpleActivity should have a nctor_*_uco method");
}
[Fact]
- public void Generate_InheritedJavaInteropCtor_ReferencesActivationCtor ()
+ public void Generate_InheritedJavaInteropCtor_NctorUcoCallsDefaultConstructor ()
{
var peer = MakeAcwPeer ("test/JiInheritedTarget", "Test.JiInheritedTarget", "TestAsm") with {
ActivationCtor = new ActivationCtorInfo {
@@ -332,10 +338,17 @@ public void Generate_InheritedJavaInteropCtor_ReferencesActivationCtor ()
var memberNames = GetMemberRefNames (reader);
Assert.Contains ("GetUninitializedObject", memberNames);
+ Assert.Contains ("SetActivationPeerReference", memberNames);
+ Assert.Contains ("MarkActivationPeerReplaceable", memberNames);
Assert.DoesNotContain ("Invoke", memberNames);
- Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "JiInheritedBase",
+ // The new no-arg nctor codegen calls the target type's parameterless .ctor()
+ // directly, not the JI-style (JniObjectReference&, JniObjectReferenceOptions)
+ // activation ctor on the inherited base.
+ Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "JiInheritedTarget"));
+ Assert.Empty (FindCtorMemberRefs (reader, "Test", "JiInheritedBase",
"Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions"));
+
var nctorMethodHandle = FindNctorUcoMethod (reader);
Assert.False (nctorMethodHandle.IsNil, "The ACW peer should have a nctor_*_uco method");
}
@@ -539,6 +552,41 @@ sig.ParameterTypes [0].Contains ("JniObjectReference")) {
Assert.True (foundByRefCtor, "Expected to find a JI-style invoker .ctor with byref JniObjectReference parameter");
}
+ [Fact]
+ public void Generate_UcoConstructor_InvokerUsesXamarinAndroidActivationCtor ()
+ {
+ var peer = MakeAcwPeer ("test/AbstractCtorTarget", "Test.AbstractCtorTarget", "TestAsm") with {
+ InvokerTypeName = "Test.AbstractCtorInvoker",
+ };
+
+ using var stream = GenerateAssembly (new [] { peer }, "InvokerCtorUcoTest");
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "AbstractCtorInvoker",
+ "System.IntPtr", "Android.Runtime.JniHandleOwnership"));
+ Assert.Empty (FindCtorMemberRefs (reader, "Test", "AbstractCtorTarget",
+ "System.IntPtr", "Android.Runtime.JniHandleOwnership"));
+ }
+
+ [Fact]
+ public void Generate_UcoConstructor_InvokerUsesJavaInteropActivationCtor ()
+ {
+ var peer = MakeAcwPeer ("test/AbstractJiCtorTarget", "Test.AbstractJiCtorTarget", "TestAsm") with {
+ InvokerTypeName = "Test.AbstractJiCtorInvoker",
+ InvokerActivationCtorStyle = ActivationCtorStyle.JavaInterop,
+ };
+
+ using var stream = GenerateAssembly (new [] { peer }, "JiInvokerCtorUcoTest");
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "AbstractJiCtorInvoker",
+ "Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions"));
+ Assert.Empty (FindCtorMemberRefs (reader, "Test", "AbstractJiCtorTarget",
+ "Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions"));
+ }
+
[Fact]
public void Generate_JiStyleCtor_EmitsDeleteRefCall ()
{
@@ -672,7 +720,7 @@ public void Generate_AcwProxy_HasUnmanagedCallersOnlyAttribute ()
[Theory]
[InlineData (1, 0x05)] // Boolean → byte (unsigned) for JNI ABI
[InlineData (2, 0x04)] // Byte → sbyte
- [InlineData (3, 0x03)] // Char → char
+ [InlineData (3, 0x07)] // Char → uint16 (blittable JNI jchar)
[InlineData (4, 0x06)] // Short → int16
[InlineData (5, 0x08)] // Int → int32
[InlineData (6, 0x0A)] // Long → int64
@@ -739,7 +787,6 @@ public void EncodeClrTypeForCallback_Void_Throws ()
[Theory]
[InlineData (2)] // Byte
- [InlineData (3)] // Char
[InlineData (4)] // Short
[InlineData (5)] // Int
[InlineData (6)] // Long
@@ -1160,6 +1207,98 @@ public void Generate_UcoConstructor_HasMarshalMethodMetadataAndExceptionRegions
Assert.Contains (regions, r => r.Kind == ExceptionRegionKind.Finally);
}
+ [Fact]
+ public void Generate_UcoConstructor_ParameterizedPrimitiveCtorCallsManagedConstructor ()
+ {
+ string jniSignature = "(ZBCSIJFD)V";
+ var managedTypes = new [] {
+ "System.Boolean",
+ "System.SByte",
+ "System.Char",
+ "System.Int16",
+ "System.Int32",
+ "System.Int64",
+ "System.Single",
+ "System.Double",
+ };
+ var peer = MakeAcwPeer ("test/PrimitiveCtorArgs", "Test.PrimitiveCtorArgs", "TestAsm") with {
+ JavaConstructors = new List {
+ new JavaConstructorInfo {
+ ConstructorIndex = 0,
+ JniSignature = jniSignature,
+ ManagedParameterTypes = managedTypes,
+ HasManagedConstructor = true,
+ },
+ },
+ MarshalMethods = new List {
+ new MarshalMethodInfo {
+ JniName = "",
+ NativeCallbackName = "n_ctor",
+ JniSignature = jniSignature,
+ ManagedMethodName = ".ctor",
+ IsConstructor = true,
+ ManagedParameterTypes = managedTypes,
+ },
+ },
+ };
+
+ using var stream = GenerateAssembly (new [] { peer }, "ParameterizedPrimitiveCtorTest");
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "PrimitiveCtorArgs", managedTypes));
+
+ var nctorMethod = reader.GetMethodDefinition (FindNctorUcoMethod (reader));
+ var nctorSignature = nctorMethod.DecodeSignature (SignatureTypeProvider.Instance, null);
+ Assert.Equal (
+ new [] { "System.IntPtr", "System.IntPtr", "System.Byte", "System.SByte", "System.UInt16", "System.Int16", "System.Int32", "System.Int64", "System.Single", "System.Double" },
+ nctorSignature.ParameterTypes);
+ }
+
+ [Fact]
+ public void Generate_UcoConstructor_ParameterizedObjectCtorUsesExplicitMarshalHelpers ()
+ {
+ string jniSignature = "(Ljava/lang/String;[I[Ljava/lang/String;Landroid/content/Context;)V";
+ var managedTypes = new [] {
+ "System.String",
+ "System.Int32[]",
+ "System.String[]",
+ "Android.Content.Context",
+ };
+ var peer = MakeAcwPeer ("test/ObjectCtorArgs", "Test.ObjectCtorArgs", "TestAsm") with {
+ JavaConstructors = new List {
+ new JavaConstructorInfo {
+ ConstructorIndex = 0,
+ JniSignature = jniSignature,
+ ManagedParameterTypes = managedTypes,
+ HasManagedConstructor = true,
+ },
+ },
+ MarshalMethods = new List {
+ new MarshalMethodInfo {
+ JniName = "",
+ NativeCallbackName = "n_ctor",
+ JniSignature = jniSignature,
+ ManagedMethodName = ".ctor",
+ IsConstructor = true,
+ ManagedParameterTypes = managedTypes,
+ },
+ },
+ };
+
+ using var stream = GenerateAssembly (new [] { peer }, "ParameterizedObjectCtorTest");
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "ObjectCtorArgs", managedTypes));
+
+ var memberNames = GetMemberRefNames (reader);
+ Assert.Contains ("GetString", memberNames);
+ Assert.Contains ("GetArray", memberNames);
+ Assert.Contains ("GetObject", memberNames);
+ Assert.DoesNotContain ("FromJniHandle", memberNames);
+ }
+
[Fact]
public void Generate_UcoConstructor_JiStyle_HasExceptionRegions ()
{
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs
new file mode 100644
index 00000000000..1fd51eff2e1
--- /dev/null
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs
@@ -0,0 +1,956 @@
+using System;
+
+using Android.App;
+using Android.Content;
+using Android.Runtime;
+using Android.Util;
+using Android.Views;
+
+using Java.Interop;
+
+using NUnit.Framework;
+
+namespace Java.InteropTests
+{
+ [TestFixture]
+ public class ConstructorActivationTests
+ {
+ [Test]
+ public void JavaSideDefaultConstructorRunsOnceAndRegistersPeer ()
+ {
+ ConstructorActivationDefault.Reset ();
+
+ using (var instance = CreateFromJava ("()V")) {
+ Assert.IsNotNull (instance);
+ Assert.AreEqual (1, ConstructorActivationDefault.ConstructorInvocations);
+ Assert.AreEqual (1, instance.ConstructorOrdinal);
+ AssertRegisteredSame (instance);
+ }
+ }
+
+ [Test]
+ public void ManagedConstructionDoesNotReenterThroughJavaConstructorActivation ()
+ {
+ ConstructorActivationDefault.Reset ();
+
+ using (var instance = new ConstructorActivationDefault ()) {
+ Assert.IsNotNull (instance);
+ Assert.AreEqual (1, ConstructorActivationDefault.ConstructorInvocations);
+ Assert.AreEqual (1, instance.ConstructorOrdinal);
+ AssertRegisteredSame (instance);
+ }
+ }
+
+ [Test]
+ public void CreateInstanceDoesNotActivateManagedConstructorInsideNewObjectScope ()
+ {
+ ConstructorActivationDefault.Reset ();
+
+ IntPtr handle = JNIEnv.CreateInstance (typeof (ConstructorActivationDefault), "()V");
+ try {
+ var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (new JniObjectReference (handle));
+
+ Assert.AreEqual (0, ConstructorActivationDefault.ConstructorInvocations);
+ Assert.IsNull (peer);
+ } finally {
+ JNIEnv.DeleteLocalRef (handle);
+ }
+ }
+
+ [Test]
+ public void JavaSideConstructorReinvokesExistingActivatablePeer ()
+ {
+ ConstructorActivationDefault.Reset ();
+
+ using (var instance = new ConstructorActivationDefault ()) {
+ var peer = (IJavaPeerable) instance;
+ peer.SetJniManagedPeerState (instance.JniManagedPeerState | JniManagedPeerStates.Activatable | JniManagedPeerStates.Replaceable);
+
+ JNIEnv.FinishCreateInstance (instance.Handle, "()V");
+
+ Assert.AreEqual (2, ConstructorActivationDefault.ConstructorInvocations);
+ Assert.AreEqual (2, instance.ConstructorOrdinal);
+ AssertRegisteredSame (instance);
+ }
+ }
+
+ [Test]
+ public void JavaSideThrowingConstructorPropagatesException ()
+ {
+ ConstructorActivationThrowing.Reset ();
+
+ var exception = Assert.Catch (() => CreateFromJavaExpectingConstructorException ("()V"));
+ Assert.IsNotNull (exception);
+ Assert.AreEqual (1, ConstructorActivationThrowing.ConstructorInvocations);
+ Assert.IsTrue (exception.ToString ().Contains (ConstructorActivationThrowing.ExceptionMessage));
+ }
+
+ [Test]
+ public void JavaSideContextConstructorForwardsArgument ()
+ {
+ ConstructorActivationContextView.Reset ();
+
+ using (var instance = CreateFromJava (
+ "(Landroid/content/Context;)V",
+ new JValue (Application.Context))) {
+ Assert.AreEqual (1, ConstructorActivationContextView.ConstructorInvocations);
+ Assert.IsNotNull (instance.ContextValue);
+ Assert.AreEqual (Application.Context.Handle, instance.ContextValue.Handle);
+ }
+ }
+
+ [Test]
+ public void JavaSideContextAttributeSetConstructorForwardsArguments ()
+ {
+ ConstructorActivationAttributeSetView.Reset ();
+
+ using (var instance = CreateFromJava (
+ "(Landroid/content/Context;Landroid/util/AttributeSet;)V",
+ new JValue (Application.Context),
+ JValue.Zero)) {
+ Assert.AreEqual (1, ConstructorActivationAttributeSetView.ConstructorInvocations);
+ Assert.IsNotNull (instance.ContextValue);
+ Assert.AreEqual (Application.Context.Handle, instance.ContextValue.Handle);
+ Assert.IsNull (instance.AttributeSetValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideContextAttributeSetStyleConstructorForwardsArguments ()
+ {
+ ConstructorActivationStyledView.Reset ();
+
+ using (var instance = CreateFromJava (
+ "(Landroid/content/Context;Landroid/util/AttributeSet;I)V",
+ new JValue (Application.Context),
+ JValue.Zero,
+ new JValue (42))) {
+ Assert.AreEqual (1, ConstructorActivationStyledView.ConstructorInvocations);
+ Assert.IsNotNull (instance.ContextValue);
+ Assert.AreEqual (Application.Context.Handle, instance.ContextValue.Handle);
+ Assert.IsNull (instance.AttributeSetValue);
+ Assert.AreEqual (42, instance.DefStyleAttrValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideBooleanConstructorForwardsTrue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+
+ using (var instance = CreateFromJava ("(Z)V", new JValue (true))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (true, instance.BooleanValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideBooleanConstructorForwardsFalse ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+
+ using (var instance = CreateFromJava ("(Z)V", new JValue (false))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (false, instance.BooleanValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideByteConstructorForwardsSignedValue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+
+ using (var instance = CreateFromJava ("(B)V", new JValue ((sbyte) -12))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual ((sbyte) -12, instance.ByteValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideCharConstructorForwardsValue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+
+ using (var instance = CreateFromJava ("(C)V", new JValue ('Q'))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual ('Q', instance.CharValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideShortConstructorForwardsValue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJava ("(S)V", new JValue ((short) -1234))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual ((short) -1234, instance.ShortValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideIntConstructorForwardsValue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+
+ using (var instance = CreateFromJava ("(I)V", new JValue (0x1234567))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (0x1234567, instance.IntValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideLongConstructorForwardsValue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+
+ using (var instance = CreateFromJava ("(J)V", new JValue (0x123456789L))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (0x123456789L, instance.LongValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideFloatConstructorForwardsValue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+
+ using (var instance = CreateFromJava ("(F)V", new JValue (12.5f))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (12.5f, instance.FloatValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideDoubleConstructorForwardsValue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+
+ using (var instance = CreateFromJava ("(D)V", new JValue (-42.25))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (-42.25, instance.DoubleValue);
+ }
+ }
+
+ [Test]
+ public void JavaSidePrimitiveMixedConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+
+ using (var instance = CreateFromJava (
+ "(IZJ)V",
+ new JValue (12345),
+ new JValue (true),
+ new JValue (9876543210L))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (12345, instance.MultiIntValue);
+ Assert.AreEqual (true, instance.MultiBooleanValue);
+ Assert.AreEqual (9876543210L, instance.MultiLongValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideStringConstructorForwardsValue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var value = new Java.Lang.String ("hello constructor"))
+ using (var instance = CreateFromJava ("(Ljava/lang/String;)V", new JValue (value))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual ("hello constructor", instance.StringValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideStringConstructorForwardsNull ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJava ("(Ljava/lang/String;)V", JValue.Zero)) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.IsNull (instance.StringValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideTwoStringConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var first = new Java.Lang.String ("first"))
+ using (var second = new Java.Lang.String ("second"))
+ using (var instance = CreateFromJava (
+ "(Ljava/lang/String;Ljava/lang/String;)V",
+ new JValue (first),
+ new JValue (second))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual ("first", instance.FirstStringValue);
+ Assert.AreEqual ("second", instance.SecondStringValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideTwoStringConstructorForwardsNullSecondValue ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var first = new Java.Lang.String ("first"))
+ using (var instance = CreateFromJava (
+ "(Ljava/lang/String;Ljava/lang/String;)V",
+ new JValue (first),
+ JValue.Zero)) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual ("first", instance.FirstStringValue);
+ Assert.IsNull (instance.SecondStringValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideStringIntConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var text = new Java.Lang.String ("string-int"))
+ using (var instance = CreateFromJava (
+ "(Ljava/lang/String;I)V",
+ new JValue (text),
+ new JValue (17))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual ("string-int", instance.StringIntStringValue);
+ Assert.AreEqual (17, instance.StringIntValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideIntArrayConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJavaWithLocalArray (
+ "([I)V",
+ JNIEnv.NewArray (new [] { 1, 2, 3, 5 }))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (new [] { 1, 2, 3, 5 }, instance.IntArrayValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideIntArrayConstructorForwardsEmptyArray ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJavaWithLocalArray (
+ "([I)V",
+ JNIEnv.NewArray (Array.Empty ()))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.IsNotNull (instance.IntArrayValue);
+ Assert.AreEqual (0, instance.IntArrayValue.Length);
+ }
+ }
+
+ [Test]
+ public void JavaSideIntArrayConstructorForwardsNull ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJava ("([I)V", JValue.Zero)) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.IsNull (instance.IntArrayValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideStringIntArrayConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var label = new Java.Lang.String ("string-array"))
+ {
+ IntPtr array = JNIEnv.NewArray (new [] { 8, 13, 21 });
+ try {
+ using (var instance = CreateFromJava (
+ "(Ljava/lang/String;[I)V",
+ new JValue (label),
+ new JValue (array))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual ("string-array", instance.StringArrayLabel);
+ Assert.AreEqual (new [] { 8, 13, 21 }, instance.StringArrayValues);
+ }
+ } finally {
+ JNIEnv.DeleteLocalRef (array);
+ }
+ }
+ }
+
+ [Test]
+ public void JavaSideBooleanArrayConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJavaWithLocalArray (
+ "([Z)V",
+ JNIEnv.NewArray (new [] { true, false, true }))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (new [] { true, false, true }, instance.BooleanArrayValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideByteArrayConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJavaWithLocalArray (
+ "([B)V",
+ JNIEnv.NewArray (new byte [] { 1, 127, 255 }))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (new byte [] { 1, 127, 255 }, instance.ByteArrayValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideStringArrayConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJavaWithLocalArray (
+ "([Ljava/lang/String;)V",
+ JNIEnv.NewArray (new [] { "red", "green", "blue" }))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (new [] { "red", "green", "blue" }, instance.StringArrayValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideStringArrayConstructorForwardsNullElement ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJavaWithLocalArray (
+ "([Ljava/lang/String;)V",
+ JNIEnv.NewArray (new [] { "red", null, "blue" }))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.AreEqual (new [] { "red", null, "blue" }, instance.StringArrayValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideStringArrayConstructorForwardsNull ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJava ("([Ljava/lang/String;)V", JValue.Zero)) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.IsNull (instance.StringArrayValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideObjectArrayConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var first = new Java.Lang.String ("object-array-value"))
+ using (var instance = CreateFromJavaWithLocalArray (
+ "([Ljava/lang/Object;)V",
+ JNIEnv.NewArray (new Java.Lang.Object [] { first, Application.Context }))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.IsNotNull (instance.ObjectArrayValue);
+ Assert.AreEqual (2, instance.ObjectArrayValue.Length);
+ Assert.AreEqual ("object-array-value", instance.ObjectArrayValue [0].ToString ());
+ Assert.AreEqual (Application.Context.Handle, instance.ObjectArrayValue [1].Handle);
+ }
+ }
+
+ [Test]
+ public void JavaSideObjectArrayConstructorForwardsNull ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJava ("([Ljava/lang/Object;)V", JValue.Zero)) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.IsNull (instance.ObjectArrayValue);
+ }
+ }
+
+ [Test]
+ public void JavaSideNestedIntArrayConstructorForwardsValues ()
+ {
+ ConstructorActivationMarshalObject.Reset ();
+ AssumeTrimmableConstructorParameterMarshalling ();
+
+ using (var instance = CreateFromJavaWithLocalArray (
+ "([[I)V",
+ JNIEnv.NewArray (new [] {
+ new [] { 1, 2 },
+ new [] { 3, 4, 5 },
+ }))) {
+ Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations);
+ Assert.IsNotNull (instance.NestedIntArrayValue);
+ Assert.AreEqual (new [] { 1, 2 }, instance.NestedIntArrayValue [0]);
+ Assert.AreEqual (new [] { 3, 4, 5 }, instance.NestedIntArrayValue [1]);
+ }
+ }
+
+ static void AssumeTrimmableConstructorParameterMarshalling ()
+ {
+ if (!Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) {
+ Assert.Ignore ("Legacy TypeManager.n_Activate does not marshal string, short, or array constructor parameters; this case validates trimmable constructor UCO parameter marshalling.");
+ }
+ }
+
+ static T CreateFromJava (string constructorSignature, params JValue [] arguments)
+ where T : Java.Lang.Object
+ {
+ var instance = JNIEnv.StartCreateInstance (typeof (T), constructorSignature, arguments);
+ JNIEnv.FinishCreateInstance (instance, constructorSignature, arguments);
+ var result = Java.Lang.Object.GetObject (instance, JniHandleOwnership.TransferLocalRef);
+ Assert.IsNotNull (result);
+ return result;
+ }
+
+ static T CreateFromJavaWithLocalArray (string constructorSignature, IntPtr array)
+ where T : Java.Lang.Object
+ {
+ try {
+ return CreateFromJava (constructorSignature, new JValue (array));
+ } finally {
+ JNIEnv.DeleteLocalRef (array);
+ }
+ }
+
+ static void CreateFromJavaExpectingConstructorException (string constructorSignature, params JValue [] arguments)
+ where T : Java.Lang.Object
+ {
+ IntPtr instance = IntPtr.Zero;
+ try {
+ instance = JNIEnv.StartCreateInstance (typeof (T), constructorSignature, arguments);
+ JNIEnv.FinishCreateInstance (instance, constructorSignature, arguments);
+ } finally {
+ if (instance != IntPtr.Zero) {
+ JNIEnv.DeleteLocalRef (instance);
+ }
+ }
+ }
+
+ static void AssertRegisteredSame (T instance)
+ where T : Java.Lang.Object
+ {
+ var registered = Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer);
+ try {
+ Assert.AreSame (instance, registered);
+ if (Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) {
+ Assert.AreEqual (Java.Lang.JavaSystem.IdentityHashCode (instance), instance.JniIdentityHashCode);
+ }
+ } finally {
+ if (registered != null && !object.ReferenceEquals (instance, registered))
+ registered.Dispose ();
+ }
+ }
+ }
+
+ [Register ("net/dot/android/test/ConstructorActivationThrowing")]
+ public class ConstructorActivationThrowing : Java.Lang.Object
+ {
+ public const string ExceptionMessage = "constructor activation throw";
+
+ public static int ConstructorInvocations;
+
+ public ConstructorActivationThrowing ()
+ {
+ ConstructorInvocations++;
+ throw new InvalidOperationException (ExceptionMessage);
+ }
+
+ public static void Reset ()
+ {
+ ConstructorInvocations = 0;
+ }
+ }
+
+ [Register ("net/dot/android/test/ConstructorActivationBase", DoNotGenerateAcw = true)]
+ public class ConstructorActivationBase : Java.Lang.Object
+ {
+ public ConstructorActivationBase ()
+ {
+ }
+
+ public ConstructorActivationBase (bool value)
+ {
+ }
+
+ public ConstructorActivationBase (sbyte value)
+ {
+ }
+
+ public ConstructorActivationBase (char value)
+ {
+ }
+
+ public ConstructorActivationBase (short value)
+ {
+ }
+
+ public ConstructorActivationBase (int value)
+ {
+ }
+
+ public ConstructorActivationBase (long value)
+ {
+ }
+
+ public ConstructorActivationBase (float value)
+ {
+ }
+
+ public ConstructorActivationBase (double value)
+ {
+ }
+
+ public ConstructorActivationBase (int number, bool flag, long longValue)
+ {
+ }
+
+ public ConstructorActivationBase (string value)
+ {
+ }
+
+ public ConstructorActivationBase (string first, string second)
+ {
+ }
+
+ public ConstructorActivationBase (string text, int number)
+ {
+ }
+
+ public ConstructorActivationBase (int[] value)
+ {
+ }
+
+ public ConstructorActivationBase (string label, int[] value)
+ {
+ }
+
+ public ConstructorActivationBase (bool[] value)
+ {
+ }
+
+ public ConstructorActivationBase (byte[] value)
+ {
+ }
+
+ public ConstructorActivationBase (string[] value)
+ {
+ }
+
+ public ConstructorActivationBase (Java.Lang.Object[] value)
+ {
+ }
+
+ public ConstructorActivationBase (int[][] value)
+ {
+ }
+
+ public ConstructorActivationBase (IntPtr handle, JniHandleOwnership transfer)
+ : base (handle, transfer)
+ {
+ }
+ }
+
+ [Register ("net/dot/android/test/ConstructorActivationDefault")]
+ public class ConstructorActivationDefault : Java.Lang.Object
+ {
+ public static int ConstructorInvocations;
+
+ public int ConstructorOrdinal;
+
+ public ConstructorActivationDefault ()
+ {
+ ConstructorOrdinal = ++ConstructorInvocations;
+ }
+
+ public static void Reset ()
+ {
+ ConstructorInvocations = 0;
+ }
+ }
+
+ [Register ("net/dot/android/test/ConstructorActivationMarshalObject")]
+ public class ConstructorActivationMarshalObject : ConstructorActivationBase
+ {
+ public static int ConstructorInvocations;
+
+ public bool BooleanValue;
+ public sbyte ByteValue;
+ public char CharValue;
+ public short ShortValue;
+ public int IntValue;
+ public long LongValue;
+ public float FloatValue;
+ public double DoubleValue;
+ public int MultiIntValue;
+ public bool MultiBooleanValue;
+ public long MultiLongValue;
+ public string StringValue;
+ public string FirstStringValue;
+ public string SecondStringValue;
+ public string StringIntStringValue;
+ public int StringIntValue;
+ public int[] IntArrayValue;
+ public string StringArrayLabel;
+ public int[] StringArrayValues;
+ public bool[] BooleanArrayValue;
+ public byte[] ByteArrayValue;
+ public string[] StringArrayValue;
+ public Java.Lang.Object[] ObjectArrayValue;
+ public int[][] NestedIntArrayValue;
+
+ [Register (".ctor", "(Z)V", "")]
+ public ConstructorActivationMarshalObject (bool value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ BooleanValue = value;
+ }
+
+ [Register (".ctor", "(B)V", "")]
+ public ConstructorActivationMarshalObject (sbyte value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ ByteValue = value;
+ }
+
+ [Register (".ctor", "(C)V", "")]
+ public ConstructorActivationMarshalObject (char value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ CharValue = value;
+ }
+
+ [Register (".ctor", "(S)V", "")]
+ public ConstructorActivationMarshalObject (short value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ ShortValue = value;
+ }
+
+ [Register (".ctor", "(I)V", "")]
+ public ConstructorActivationMarshalObject (int value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ IntValue = value;
+ }
+
+ [Register (".ctor", "(J)V", "")]
+ public ConstructorActivationMarshalObject (long value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ LongValue = value;
+ }
+
+ [Register (".ctor", "(F)V", "")]
+ public ConstructorActivationMarshalObject (float value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ FloatValue = value;
+ }
+
+ [Register (".ctor", "(D)V", "")]
+ public ConstructorActivationMarshalObject (double value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ DoubleValue = value;
+ }
+
+ [Register (".ctor", "(IZJ)V", "")]
+ public ConstructorActivationMarshalObject (int number, bool flag, long longValue)
+ : base (number, flag, longValue)
+ {
+ ConstructorInvocations++;
+ MultiIntValue = number;
+ MultiBooleanValue = flag;
+ MultiLongValue = longValue;
+ }
+
+ [Register (".ctor", "(Ljava/lang/String;)V", "")]
+ public ConstructorActivationMarshalObject (string value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ StringValue = value;
+ }
+
+ [Register (".ctor", "(Ljava/lang/String;Ljava/lang/String;)V", "")]
+ public ConstructorActivationMarshalObject (string first, string second)
+ : base (first, second)
+ {
+ ConstructorInvocations++;
+ FirstStringValue = first;
+ SecondStringValue = second;
+ }
+
+ [Register (".ctor", "(Ljava/lang/String;I)V", "")]
+ public ConstructorActivationMarshalObject (string text, int number)
+ : base (text, number)
+ {
+ ConstructorInvocations++;
+ StringIntStringValue = text;
+ StringIntValue = number;
+ }
+
+ [Register (".ctor", "([I)V", "")]
+ public ConstructorActivationMarshalObject (int[] value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ IntArrayValue = value;
+ }
+
+ [Register (".ctor", "(Ljava/lang/String;[I)V", "")]
+ public ConstructorActivationMarshalObject (string label, int[] value)
+ : base (label, value)
+ {
+ ConstructorInvocations++;
+ StringArrayLabel = label;
+ StringArrayValues = value;
+ }
+
+ [Register (".ctor", "([Z)V", "")]
+ public ConstructorActivationMarshalObject (bool[] value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ BooleanArrayValue = value;
+ }
+
+ [Register (".ctor", "([B)V", "")]
+ public ConstructorActivationMarshalObject (byte[] value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ ByteArrayValue = value;
+ }
+
+ [Register (".ctor", "([Ljava/lang/String;)V", "")]
+ public ConstructorActivationMarshalObject (string[] value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ StringArrayValue = value;
+ }
+
+ [Register (".ctor", "([Ljava/lang/Object;)V", "")]
+ public ConstructorActivationMarshalObject (Java.Lang.Object[] value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ ObjectArrayValue = value;
+ }
+
+ [Register (".ctor", "([[I)V", "")]
+ public ConstructorActivationMarshalObject (int[][] value)
+ : base (value)
+ {
+ ConstructorInvocations++;
+ NestedIntArrayValue = value;
+ }
+
+ public static void Reset ()
+ {
+ ConstructorInvocations = 0;
+ }
+ }
+
+ [Register ("net/dot/android/test/ConstructorActivationContextView")]
+ public class ConstructorActivationContextView : View
+ {
+ public static int ConstructorInvocations;
+
+ public Context ContextValue;
+
+ [Register (".ctor", "(Landroid/content/Context;)V", "")]
+ public ConstructorActivationContextView (Context context)
+ : base (context)
+ {
+ ConstructorInvocations++;
+ ContextValue = context;
+ }
+
+ public static void Reset ()
+ {
+ ConstructorInvocations = 0;
+ }
+ }
+
+ [Register ("net/dot/android/test/ConstructorActivationAttributeSetView")]
+ public class ConstructorActivationAttributeSetView : View
+ {
+ public static int ConstructorInvocations;
+
+ public Context ContextValue;
+ public IAttributeSet AttributeSetValue;
+
+ [Register (".ctor", "(Landroid/content/Context;Landroid/util/AttributeSet;)V", "")]
+ public ConstructorActivationAttributeSetView (Context context, IAttributeSet attrs)
+ : base (context, attrs)
+ {
+ ConstructorInvocations++;
+ ContextValue = context;
+ AttributeSetValue = attrs;
+ }
+
+ public static void Reset ()
+ {
+ ConstructorInvocations = 0;
+ }
+ }
+
+ [Register ("net/dot/android/test/ConstructorActivationStyledView")]
+ public class ConstructorActivationStyledView : View
+ {
+ public static int ConstructorInvocations;
+
+ public Context ContextValue;
+ public IAttributeSet AttributeSetValue;
+ public int DefStyleAttrValue;
+
+ [Register (".ctor", "(Landroid/content/Context;Landroid/util/AttributeSet;I)V", "")]
+ public ConstructorActivationStyledView (Context context, IAttributeSet attrs, int defStyleAttr)
+ : base (context, attrs, defStyleAttr)
+ {
+ ConstructorInvocations++;
+ ContextValue = context;
+ AttributeSetValue = attrs;
+ DefStyleAttrValue = defStyleAttr;
+ }
+
+ public static void Reset ()
+ {
+ ConstructorInvocations = 0;
+ }
+ }
+}
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs
index 0eacc4eddad..13fa0962b58 100644
--- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs
@@ -301,14 +301,11 @@ public void ActivatedDirectObjectSubclassesShouldBeRegistered ()
}
}
- // TODO: https://github.com/dotnet/android/issues/11170 — throwable subclass not registered under trimmable typemap
[Test]
public void ActivatedDirectThrowableSubclassesShouldBeRegistered ()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.GingerbreadMr1)
Assert.Ignore ("Skipping test due to Bug #34141");
-
- Console.Error.WriteLine ($"# jonp: BEGIN ActivatedDirectThrowableSubclassesShouldBeRegistered!!!");
using (var ThrowableActivatedFromJava_class = Java.Lang.Class.FromType (typeof (ThrowableActivatedFromJava))) {
var ThrowableActivatedFromJava_init = JNIEnv.GetMethodID (ThrowableActivatedFromJava_class.Handle, "", "()V");
@@ -324,7 +321,6 @@ public void ActivatedDirectThrowableSubclassesShouldBeRegistered ()
Assert.IsTrue (v.Constructed);
v.Dispose ();
}
- Console.Error.WriteLine ($"# jonp: END ActivatedDirectThrowableSubclassesShouldBeRegistered!!!");
}
[Test]
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs
index b4d921acd17..764fc416379 100644
--- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs
@@ -19,7 +19,6 @@ namespace Java.LangTests
[TestFixture]
public class ObjectTest
{
- // TODO: https://github.com/dotnet/android/issues/11170 — trimmable typemap doesn't resolve most-derived managed type
[Test]
public void GetObject_ReturnsMostDerivedType ()
{
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
index f1575d1e45d..6831b511fa6 100644
--- a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
@@ -102,6 +102,7 @@
+
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs
index 966903726aa..b03e62bb551 100644
--- a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs
@@ -34,12 +34,6 @@ protected NUnitInstrumentation(IntPtr handle, JniHandleOwnership transfer)
// net.dot.jni.test.CallVirtualFromConstructorDerived Java class not in APK
"Java.InteropTests.InvokeVirtualFromConstructorTests",
- // Throwable subclass registration
- "Java.InteropTests.JnienvTest.ActivatedDirectThrowableSubclassesShouldBeRegistered",
-
- // Instance identity after JNI round-trip
- "Java.LangTests.ObjectTest.JnienvCreateInstance_RegistersMultipleInstances",
-
// Global ref leak when inflating custom views
"Xamarin.Android.RuntimeTests.CustomWidgetTests.InflateCustomView_ShouldNotLeakGlobalRefs",
};
diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java
new file mode 100644
index 00000000000..20283efcfc9
--- /dev/null
+++ b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java
@@ -0,0 +1,24 @@
+package net.dot.android.test;
+
+public class ConstructorActivationBase {
+ public ConstructorActivationBase() {}
+ public ConstructorActivationBase(boolean value) {}
+ public ConstructorActivationBase(byte value) {}
+ public ConstructorActivationBase(char value) {}
+ public ConstructorActivationBase(short value) {}
+ public ConstructorActivationBase(int value) {}
+ public ConstructorActivationBase(long value) {}
+ public ConstructorActivationBase(float value) {}
+ public ConstructorActivationBase(double value) {}
+ public ConstructorActivationBase(int number, boolean flag, long longValue) {}
+ public ConstructorActivationBase(String value) {}
+ public ConstructorActivationBase(String first, String second) {}
+ public ConstructorActivationBase(String text, int number) {}
+ public ConstructorActivationBase(int[] value) {}
+ public ConstructorActivationBase(String label, int[] value) {}
+ public ConstructorActivationBase(boolean[] value) {}
+ public ConstructorActivationBase(byte[] value) {}
+ public ConstructorActivationBase(String[] value) {}
+ public ConstructorActivationBase(Object[] value) {}
+ public ConstructorActivationBase(int[][] value) {}
+}