diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
index 56484f7a99a..e2868a3b2c5 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
@@ -182,7 +182,7 @@ sealed record UcoMethodData
/// An [UnmanagedCallersOnly] static wrapper for a constructor callback.
/// Signature must match the full JNI native method signature (jnienv + self + ctor params)
/// so the ABI is correct when JNI dispatches the call.
-/// Body: TrimmableNativeRegistration.ActivateInstance(self, typeof(TargetType)).
+/// Body: directly activates the target type using its generated activation ctor.
///
sealed record UcoConstructorData
{
@@ -192,7 +192,7 @@ sealed record UcoConstructorData
public required string WrapperName { get; init; }
///
- /// Target type to pass to ActivateInstance.
+ /// Target type to activate in the generated wrapper.
///
public required TypeRefData TargetType { get; init; }
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs
index 56ae75638b2..ddec657a085 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs
@@ -239,7 +239,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size)
int typeMethodStart = Metadata.GetRowCount (TableIndex.MethodDef) + 1;
var handle = Metadata.AddTypeDefinition (
- TypeAttributes.NestedPrivate | TypeAttributes.ExplicitLayout | TypeAttributes.Sealed | TypeAttributes.AnsiClass,
+ TypeAttributes.NestedAssembly | TypeAttributes.ExplicitLayout | TypeAttributes.Sealed | TypeAttributes.AnsiClass,
default,
Metadata.GetOrAddString ($"__utf8_{size}"),
Metadata.AddTypeReference (SystemRuntimeRef,
@@ -259,7 +259,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size)
///
public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
Action encodeSig, Action emitIL)
- => EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null);
+ => EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null, useBranches: false);
///
/// Emits a method body and definition with optional local variable declarations.
@@ -269,9 +269,19 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
/// and must write the full LOCAL_SIG blob (header 0x07,
/// compressed count, then each variable type).
///
+ ///
+ /// If true, creates a so the emitted IL can use
+ /// , ,
+ /// and .
+ ///
public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
Action encodeSig, Action emitIL,
Action? encodeLocals)
+ => EmitBody (name, attrs, encodeSig, emitIL, encodeLocals, useBranches: false);
+
+ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
+ Action encodeSig, Action emitIL,
+ Action? encodeLocals, bool useBranches)
{
_sigBlob.Clear ();
encodeSig (new BlobEncoder (_sigBlob));
@@ -287,7 +297,8 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
}
_codeBlob.Clear ();
- var encoder = new InstructionEncoder (_codeBlob);
+ ControlFlowBuilder? cfb = useBranches ? new ControlFlowBuilder () : null;
+ var encoder = new InstructionEncoder (_codeBlob, cfb);
emitIL (encoder);
while (ILBuilder.Count % 4 != 0) {
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
index 34bfd15a9b6..a74f4c4955f 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
@@ -43,7 +43,9 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
/// [UnmanagedCallersOnly]
/// public static void nctor_0_uco(IntPtr jnienv, IntPtr self)
-/// => TrimmableNativeRegistration.ActivateInstance(self, typeof(Activity));
+/// => new Activity(self, JniHandleOwnership.DoNotTransfer);
+/// // or: var obj = (Activity)RuntimeHelpers.GetUninitializedObject(typeof(Activity));
+/// // obj.BaseCtor(self, JniHandleOwnership.DoNotTransfer);
///
/// // Registers JNI native methods (ACWs only):
/// public void RegisterNatives(JniType jniType)
@@ -77,7 +79,6 @@ sealed class TypeMapAssemblyEmitter
TypeReferenceHandle _systemTypeRef;
TypeReferenceHandle _runtimeTypeHandleRef;
TypeReferenceHandle _jniTypeRef;
- TypeReferenceHandle _trimmableNativeRegistrationRef;
TypeReferenceHandle _notSupportedExceptionRef;
TypeReferenceHandle _runtimeHelpersRef;
@@ -87,7 +88,7 @@ sealed class TypeMapAssemblyEmitter
MemberReferenceHandle _notSupportedExceptionCtorRef;
MemberReferenceHandle _jniObjectReferenceCtorRef;
MemberReferenceHandle _jniEnvDeleteRefRef;
- MemberReferenceHandle _activateInstanceRef;
+ MemberReferenceHandle _withinNewObjectScopeRef;
MemberReferenceHandle _ucoAttrCtorRef;
BlobHandle _ucoAttrBlobHandle;
MemberReferenceHandle _typeMapAttrCtorRef2Arg;
@@ -184,8 +185,6 @@ void EmitTypeReferences ()
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("RuntimeTypeHandle"));
_jniTypeRef = metadata.AddTypeReference (_javaInteropRef,
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniType"));
- _trimmableNativeRegistrationRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
- metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("TrimmableNativeRegistration"));
_notSupportedExceptionRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("NotSupportedException"));
_runtimeHelpersRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
@@ -240,13 +239,11 @@ void EmitMemberReferences ()
p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
}));
- _activateInstanceRef = _pe.AddMemberRef (_trimmableNativeRegistrationRef, "ActivateInstance",
- sig => sig.MethodSignature ().Parameters (2,
- rt => rt.Void (),
- p => {
- p.AddParameter ().Type ().IntPtr ();
- p.AddParameter ().Type ().Type (_systemTypeRef, false);
- }));
+ // JniEnvironment.get_WithinNewObjectScope() -> bool (static property)
+ _withinNewObjectScopeRef = _pe.AddMemberRef (_jniEnvironmentRef, "get_WithinNewObjectScope",
+ sig => sig.MethodSignature ().Parameters (0,
+ rt => rt.Type ().Boolean (),
+ p => { }));
// JniNativeMethod..ctor(byte*, byte*, IntPtr)
_jniNativeMethodCtorRef = _pe.AddMemberRef (_jniNativeMethodRef, ".ctor",
@@ -347,6 +344,16 @@ void EmitTypeMapAssociationAttributeCtorRef ()
void EmitProxyType (JavaPeerProxyData proxy, Dictionary wrapperHandles)
{
+ if (proxy.IsAcw) {
+ // RegisterNatives uses RVA-backed UTF-8 fields under .
+ // Materialize those helper types before adding the proxy TypeDef, otherwise the
+ // later RegisterNatives method can be attached to the helper type instead.
+ foreach (var reg in proxy.NativeRegistrations) {
+ _pe.GetOrAddUtf8Field (reg.JniMethodName);
+ _pe.GetOrAddUtf8Field (reg.JniSignature);
+ }
+ }
+
var metadata = _pe.Metadata;
var typeDefHandle = metadata.AddTypeDefinition (
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class,
@@ -390,7 +397,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary),
- // so the wrapper signature must include all parameters to match the ABI.
// Only jnienv (arg 0) and self (arg 1) are used — the constructor parameters
- // are not forwarded because ActivateInstance creates the managed peer using the
+ // are not forwarded because we create the managed peer using the
// activation ctor (IntPtr, JniHandleOwnership), not the user-visible constructor.
var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature);
int paramCount = 2 + jniParams.Count;
- var handle = _pe.EmitBody (uco.WrapperName,
- MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
- sig => sig.MethodSignature ().Parameters (paramCount,
- rt => rt.Void (),
- p => {
- p.AddParameter ().Type ().IntPtr (); // jnienv
- p.AddParameter ().Type ().IntPtr (); // self
- for (int j = 0; j < jniParams.Count; j++)
- JniSignatureHelper.EncodeClrType (p.AddParameter ().Type (), jniParams [j]);
- }),
- encoder => {
- encoder.LoadArgument (1); // self
- encoder.OpCode (ILOpCode.Ldtoken);
- encoder.Token (userTypeRef);
- encoder.Call (_getTypeFromHandleRef);
- encoder.Call (_activateInstanceRef);
- encoder.OpCode (ILOpCode.Ret);
+ Action encodeSig = sig => sig.MethodSignature ().Parameters (paramCount,
+ rt => rt.Void (),
+ p => {
+ p.AddParameter ().Type ().IntPtr (); // jnienv
+ p.AddParameter ().Type ().IntPtr (); // self
+ for (int j = 0; j < jniParams.Count; j++)
+ JniSignatureHelper.EncodeClrType (p.AddParameter ().Type (), jniParams [j]);
});
+ // Open generic types can't be activated — emit a no-op UCO.
+ if (proxy.IsGenericDefinition) {
+ var noopHandle = _pe.EmitBody (uco.WrapperName,
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
+ encodeSig,
+ encoder => {
+ encoder.OpCode (ILOpCode.Ret);
+ });
+ AddUnmanagedCallersOnlyAttribute (noopHandle);
+ return noopHandle;
+ }
+
+ MethodDefinitionHandle handle;
+ if (activationCtor.Style == ActivationCtorStyle.JavaInterop) {
+ var ctorRef = AddJavaInteropActivationCtorRef (
+ activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType));
+
+ handle = _pe.EmitBody (uco.WrapperName,
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
+ encodeSig,
+ encoder => {
+ // Skip activation if the object is being created from managed code
+ // (e.g., JNIEnv.StartCreateInstance / JNIEnv.NewObject).
+ var skipLabel = encoder.DefineLabel ();
+ encoder.Call (_withinNewObjectScopeRef);
+ encoder.Branch (ILOpCode.Brtrue, skipLabel);
+
+ if (!activationCtor.IsOnLeafType) {
+ encoder.OpCode (ILOpCode.Ldtoken);
+ encoder.Token (targetTypeRef);
+ encoder.Call (_getTypeFromHandleRef);
+ encoder.Call (_getUninitializedObjectRef);
+ encoder.OpCode (ILOpCode.Castclass);
+ encoder.Token (targetTypeRef);
+ }
+
+ encoder.LoadLocalAddress (0);
+ encoder.LoadArgument (1); // self
+ encoder.Call (_jniObjectReferenceCtorRef);
+
+ if (activationCtor.IsOnLeafType) {
+ encoder.LoadLocalAddress (0);
+ encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
+ encoder.OpCode (ILOpCode.Newobj);
+ encoder.Token (ctorRef);
+ encoder.OpCode (ILOpCode.Pop);
+ } else {
+ encoder.LoadLocalAddress (0);
+ encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
+ encoder.Call (ctorRef);
+ }
+
+ encoder.MarkLabel (skipLabel);
+ encoder.OpCode (ILOpCode.Ret);
+ },
+ EncodeJniObjectReferenceLocal,
+ useBranches: true);
+ } else {
+ var ctorRef = AddActivationCtorRef (
+ activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType));
+
+ handle = _pe.EmitBody (uco.WrapperName,
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
+ encodeSig,
+ encoder => {
+ // Skip activation if the object is being created from managed code
+ var skipLabel = encoder.DefineLabel ();
+ encoder.Call (_withinNewObjectScopeRef);
+ encoder.Branch (ILOpCode.Brtrue, skipLabel);
+
+ if (activationCtor.IsOnLeafType) {
+ encoder.LoadArgument (1); // self
+ encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
+ encoder.OpCode (ILOpCode.Newobj);
+ encoder.Token (ctorRef);
+ encoder.OpCode (ILOpCode.Pop);
+ } else {
+ encoder.OpCode (ILOpCode.Ldtoken);
+ encoder.Token (targetTypeRef);
+ encoder.Call (_getTypeFromHandleRef);
+ encoder.Call (_getUninitializedObjectRef);
+ encoder.OpCode (ILOpCode.Castclass);
+ encoder.Token (targetTypeRef);
+
+ encoder.LoadArgument (1); // self
+ encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
+ encoder.Call (ctorRef);
+ }
+
+ encoder.MarkLabel (skipLabel);
+ encoder.OpCode (ILOpCode.Ret);
+ },
+ encodeLocals: null,
+ useBranches: true);
+ }
AddUnmanagedCallersOnlyAttribute (handle);
return handle;
}
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
index f2c535dd8c7..4b46fee009c 100644
--- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
+++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
@@ -147,39 +147,6 @@ internal bool TryCreatePeer (Type type, IntPtr handle, JniHandleOwnership transf
return GetProxyForManagedType (type)?.GetContainerFactory ();
}
- ///
- /// Creates a managed peer instance for a Java object being constructed.
- /// Called from generated UCO constructor wrappers (nctor_*_uco).
- ///
- internal static void ActivateInstance (IntPtr self, Type targetType)
- {
- var instance = s_instance;
- if (instance is null) {
- throw new InvalidOperationException ("TrimmableTypeMap has not been initialized.");
- }
-
- // Look up the proxy via JNI class name → TypeMap dictionary.
- // We can't use targetType.GetCustomAttribute() because the
- // self-application attribute is on the proxy type, not the target type.
- var selfRef = new JniObjectReference (self);
- var jniClass = JniEnvironment.Types.GetObjectClass (selfRef);
- var className = JniEnvironment.Types.GetJniTypeNameFromClass (jniClass);
- JniObjectReference.Dispose (ref jniClass);
-
- if (className is null || !instance._typeMap.TryGetValue (className, out var proxyType)) {
- throw new InvalidOperationException (
- $"Failed to create peer for type '{targetType.FullName}' (jniClass='{className}'). " +
- "Ensure the type has a generated proxy in the TypeMap assembly.");
- }
-
- var proxy = proxyType.GetCustomAttribute (inherit: false);
- if (proxy is null || proxy.CreateInstance (self, JniHandleOwnership.DoNotTransfer) is null) {
- throw new InvalidOperationException (
- $"Failed to create peer for type '{targetType.FullName}'. " +
- "Ensure the type has a generated proxy in the TypeMap assembly.");
- }
- }
-
[UnmanagedCallersOnly]
static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHandle)
{
@@ -208,4 +175,5 @@ static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHa
Environment.FailFast ($"TrimmableTypeMap: Failed to register natives for class '{className}'.", ex);
}
}
+
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
index d892b0161da..3141793ba84 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
@@ -188,6 +188,25 @@ public void Generate_LeafCtor_DoesNotUseCreateManagedPeer ()
Assert.True (ctorRefs.Count >= 2, "Should have ctor refs for proxy base + target type");
}
+ [Fact]
+ public void Generate_InheritedCtor_UcoUsesGuardAndInlinedActivation ()
+ {
+ var peers = ScanFixtures ();
+ var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity");
+ Assert.NotNull (simpleActivity.ActivationCtor);
+ Assert.NotEqual (simpleActivity.ManagedTypeName, simpleActivity.ActivationCtor.DeclaringTypeName);
+
+ using var stream = GenerateAssembly (new [] { simpleActivity }, "InheritedCtorUcoTest");
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+ var memberNames = GetMemberRefNames (reader);
+
+ Assert.Contains ("get_WithinNewObjectScope", memberNames);
+ Assert.Contains ("GetUninitializedObject", memberNames);
+ Assert.DoesNotContain ("ActivateInstance", memberNames);
+ Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames);
+ }
+
[Fact]
public void Generate_GenericType_ThrowsNotSupportedException ()
{
@@ -422,14 +441,26 @@ public void Generate_AcwProxy_HasRegisterNativesAndUcoMethods ()
using var pe = new PEReader (stream);
var reader = pe.GetMetadataReader ();
- var memberNames = GetMemberRefNames (reader);
+ var proxyType = reader.TypeDefinitions
+ .Select (h => reader.GetTypeDefinition (h))
+ .Single (t =>
+ reader.GetString (t.Namespace) == "_TypeMap.Proxies" &&
+ reader.GetString (t.Name) == "MyApp_MainActivity_Proxy");
+ var proxyMethodNames = proxyType.GetMethods ()
+ .Select (h => reader.GetMethodDefinition (h))
+ .Select (m => reader.GetString (m.Name))
+ .ToList ();
+ Assert.Contains ("RegisterNatives", proxyMethodNames);
+ Assert.Contains (proxyMethodNames, name => name.Contains ("_uco_"));
- // RegisterNatives is a method definition on the proxy type, not a member reference
- var methodDefs = reader.MethodDefinitions
+ var privateImplDetailsType = reader.TypeDefinitions
+ .Select (h => reader.GetTypeDefinition (h))
+ .Single (t => reader.GetString (t.Name) == "");
+ var privateImplMethodNames = privateImplDetailsType.GetMethods ()
.Select (h => reader.GetMethodDefinition (h))
.Select (m => reader.GetString (m.Name))
.ToList ();
- Assert.Contains ("RegisterNatives", methodDefs);
+ Assert.DoesNotContain ("RegisterNatives", privateImplMethodNames);
}
[Fact]