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 e08caa319a1..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,6 +269,11 @@ 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) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 19c32e80522..63ccba17203 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) @@ -87,7 +89,6 @@ sealed class TypeMapAssemblyEmitter MemberReferenceHandle _jniObjectReferenceCtorRef; MemberReferenceHandle _jniEnvDeleteRefRef; MemberReferenceHandle _withinNewObjectScopeRef; - MemberReferenceHandle _activateInstanceRef; MemberReferenceHandle _ucoAttrCtorRef; BlobHandle _ucoAttrBlobHandle; MemberReferenceHandle _typeMapAttrCtorRef2Arg; @@ -244,17 +245,6 @@ void EmitMemberReferences () rt => rt.Type ().Boolean (), p => { })); - // TrimmableTypeMap.ActivateInstance(IntPtr, Type) - var trimmableTypeMapRef = _pe.Metadata.AddTypeReference (_pe.MonoAndroidRef, - _pe.Metadata.GetOrAddString ("Microsoft.Android.Runtime"), _pe.Metadata.GetOrAddString ("TrimmableTypeMap")); - _activateInstanceRef = _pe.AddMemberRef (trimmableTypeMapRef, "ActivateInstance", - sig => sig.MethodSignature ().Parameters (2, - rt => rt.Void (), - p => { - p.AddParameter ().Type ().IntPtr (); - p.AddParameter ().Type ().Type (_systemTypeRef, false); - })); - // JniNativeMethod..ctor(byte*, byte*, IntPtr) _jniNativeMethodCtorRef = _pe.AddMemberRef (_jniNativeMethodRef, ".ctor", sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3, @@ -354,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, @@ -368,7 +368,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }), encoder => { @@ -376,6 +376,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary { + // 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); - encoder.LoadArgument (1); // jniSelf - encoder.OpCode (ILOpCode.Ldtoken); - encoder.Token (userTypeRef); - encoder.Call (_getTypeFromHandleRef); - encoder.Call (_activateInstanceRef); - - encoder.MarkLabel (skipLabel); - encoder.OpCode (ILOpCode.Ret); - }, - encodeLocals: null, - useBranches: true); - } else if (activationCtor.Style == ActivationCtorStyle.JavaInterop) { - var ctorRef = AddJavaInteropActivationCtorRef (userTypeRef); - - handle = _pe.EmitBody (uco.WrapperName, - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - encodeSig, - encoder => { - 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); - encoder.LoadLocalAddress (0); - encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy - encoder.OpCode (ILOpCode.Newobj); - encoder.Token (ctorRef); - encoder.OpCode (ILOpCode.Pop); + 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); @@ -783,21 +783,36 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy EncodeJniObjectReferenceLocal, useBranches: true); } else { - var ctorRef = AddActivationCtorRef (userTypeRef); + 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); - encoder.LoadArgument (1); // self - encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer - encoder.OpCode (ILOpCode.Newobj); - encoder.Token (ctorRef); - encoder.OpCode (ILOpCode.Pop); + 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); @@ -805,7 +820,6 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy 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 b227292746d..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) { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index 10cedc025ed..9853f392a56 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -189,7 +189,7 @@ public void Generate_LeafCtor_DoesNotUseCreateManagedPeer () } [Fact] - public void Generate_InheritedCtor_UcoUsesGuardWithoutReflectionFallback () + public void Generate_InheritedCtor_UcoUsesGuardAndInlinedActivation () { var peers = ScanFixtures (); var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity"); @@ -202,7 +202,8 @@ public void Generate_InheritedCtor_UcoUsesGuardWithoutReflectionFallback () var memberNames = GetMemberRefNames (reader); Assert.Contains ("get_WithinNewObjectScope", memberNames); - Assert.Contains ("ActivateInstance", memberNames); + Assert.Contains ("GetUninitializedObject", memberNames); + Assert.DoesNotContain ("ActivateInstance", memberNames); Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames); }