From 5d88e2397498ca96ceac8adad4df8184ce7074e1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 6 May 2026 08:58:25 +0000
Subject: [PATCH 1/3] Initial plan
From 612be1dc8dc92c2d598dd44f2dc675159a75fd91 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 6 May 2026 09:47:11 +0000
Subject: [PATCH 2/3] Fix trimmable constructor UCO generation
Agent-Logs-Url: https://github.com/dotnet/android/sessions/2a0c90c9-484f-417c-875c-bea6afda1c98
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
---
.../Generator/MetadataHelper.cs | 15 ++
.../Generator/Model/TypeMapAssemblyData.cs | 12 ++
.../Generator/ModelBuilder.cs | 5 +
.../Generator/TypeMapAssemblyEmitter.cs | 139 ++++++++++++++++++
.../Scanner/JavaPeerInfo.cs | 44 ++++++
.../Scanner/JavaPeerScanner.cs | 37 ++++-
.../TypeMapAssemblyGeneratorTests.cs | 54 +++++++
7 files changed, 301 insertions(+), 5 deletions(-)
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs
index 93f8ce5c5fc..c01a04dec06 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs
@@ -42,6 +42,21 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data)
writer.Write (proxy.TargetType.AssemblyName);
writer.Write ((byte)(proxy.ActivationCtor?.Style ?? 0));
writer.Write ((byte)(proxy.InvokerActivationCtorStyle ?? 0));
+ foreach (var ctor in proxy.UcoConstructors) {
+ writer.Write (ctor.WrapperName);
+ writer.Write (ctor.JniSignature);
+ writer.Write (ctor.ConstructorDeclaringType?.ManagedTypeName ?? "");
+ writer.Write (ctor.ConstructorDeclaringType?.AssemblyName ?? "");
+ if (ctor.ManagedParameterTypes is null) {
+ writer.Write (0);
+ } else {
+ writer.Write (ctor.ManagedParameterTypes.Count);
+ foreach (var parameter in ctor.ManagedParameterTypes) {
+ writer.Write (parameter.ManagedTypeName);
+ writer.Write (parameter.AssemblyName);
+ }
+ }
+ }
}
foreach (var assoc in data.Associations) {
writer.Write (assoc.SourceTypeReference);
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
index b9126586bf4..18b5e919223 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
@@ -228,6 +228,18 @@ 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 types. Null when this UCO constructor should
+ /// fall back to activation-only behavior because no concrete managed constructor
+ /// was identified by the scanner.
+ ///
+ public IReadOnlyList? ManagedParameterTypes { get; init; }
+
+ ///
+ /// Managed type that declares the constructor to call.
+ ///
+ public TypeRefData? ConstructorDeclaringType { get; init; }
}
///
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
index 7547c5ac38f..f0737eaff31 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
@@ -365,6 +365,11 @@ static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy)
ManagedTypeName = peer.ManagedTypeName,
AssemblyName = peer.AssemblyName,
},
+ ManagedParameterTypes = ctor.ManagedParameterTypes,
+ ConstructorDeclaringType = ctor.ManagedParameterTypes is null ? null : new TypeRefData {
+ ManagedTypeName = !ctor.ConstructorDeclaringTypeName.IsNullOrEmpty () ? ctor.ConstructorDeclaringTypeName : peer.ManagedTypeName,
+ AssemblyName = !ctor.ConstructorDeclaringAssemblyName.IsNullOrEmpty () ? ctor.ConstructorDeclaringAssemblyName : peer.AssemblyName,
+ },
});
}
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
index af36173550f..af0a79e7fb6 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
@@ -78,6 +78,7 @@ sealed class TypeMapAssemblyEmitter
TypeReferenceHandle _javaPeerProxyRef;
TypeReferenceHandle _javaPeerProxyNonGenericRef;
+ TypeReferenceHandle _javaConvertRef;
TypeReferenceHandle _iJavaPeerableRef;
TypeReferenceHandle _jniHandleOwnershipRef;
TypeReferenceHandle _jniObjectReferenceRef;
@@ -97,6 +98,8 @@ sealed class TypeMapAssemblyEmitter
MemberReferenceHandle _getUninitializedObjectRef;
MemberReferenceHandle _notSupportedExceptionCtorRef;
MemberReferenceHandle _jniObjectReferenceCtorRef;
+ MemberReferenceHandle _iJavaPeerableSetPeerReferenceRef;
+ MemberReferenceHandle _javaConvertFromJniHandleRef;
MemberReferenceHandle _jniEnvDeleteRefRef;
MemberReferenceHandle _shouldSkipActivationRef;
MemberReferenceHandle _waitForBridgeProcessingRef;
@@ -220,6 +223,8 @@ void EmitTypeReferences ()
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy`1"));
_javaPeerProxyNonGenericRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy"));
+ _javaConvertRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
+ metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaConvert"));
_iJavaPeerableRef = metadata.AddTypeReference (_javaInteropRef,
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("IJavaPeerable"));
_jniHandleOwnershipRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
@@ -340,6 +345,19 @@ void EmitMemberReferences ()
p.AddParameter ().Type ().Type (_jniObjectReferenceTypeRef, true);
}));
+ _iJavaPeerableSetPeerReferenceRef = _pe.AddMemberRef (_iJavaPeerableRef, "SetPeerReference",
+ sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1,
+ rt => rt.Void (),
+ p => p.AddParameter ().Type ().Type (_jniObjectReferenceRef, true)));
+
+ _javaConvertFromJniHandleRef = _pe.AddMemberRef (_javaConvertRef, "FromJniHandle",
+ sig => sig.MethodSignature (genericParameterCount: 1).Parameters (2,
+ rt => rt.Type ().GenericMethodTypeParameter (0),
+ p => {
+ p.AddParameter ().Type ().IntPtr ();
+ p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
+ }));
+
// JNIEnv.DeleteRef(IntPtr, JniHandleOwnership) — static, internal
// Used by JI-style activation to clean up the original handle after constructing the peer.
// Matches the legacy TypeManager.CreateProxy behavior.
@@ -921,6 +939,18 @@ MemberReferenceHandle AddActivationCtorRef (EntityHandle declaringTypeRef)
}));
}
+ MemberReferenceHandle AddManagedConstructorRef (EntityHandle declaringTypeRef, IReadOnlyList parameterTypes)
+ {
+ return _pe.AddMemberRef (declaringTypeRef, ".ctor",
+ sig => sig.MethodSignature (isInstanceMethod: true).Parameters (parameterTypes.Count,
+ rt => rt.Void (),
+ p => {
+ foreach (var parameterType in parameterTypes) {
+ EncodeManagedType (p.AddParameter ().Type (), parameterType);
+ }
+ }));
+ }
+
MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco, JavaPeerProxyData proxy)
{
var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature);
@@ -1045,6 +1075,20 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
return openGenericHandle;
}
+ if (uco.ManagedParameterTypes is not null && uco.ConstructorDeclaringType is not null) {
+ var ctorDeclaringTypeRef = _pe.ResolveTypeRef (uco.ConstructorDeclaringType);
+ var ctorRef = AddManagedConstructorRef (ctorDeclaringTypeRef, uco.ManagedParameterTypes);
+ var directHandle = _pe.EmitBody (uco.WrapperName,
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
+ encodeSig,
+ (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => {
+ EmitManagedConstructorActivation (enc, targetTypeRef, ctorRef, uco.ManagedParameterTypes, jniParams);
+ }),
+ EncodeUcoConstructorLocals_Standard);
+ AddUnmanagedCallersOnlyAttribute (directHandle);
+ return directHandle;
+ }
+
MethodDefinitionHandle handle;
if (activationCtor.Style == ActivationCtorStyle.JavaInterop) {
var ctorRef = AddJavaInteropActivationCtorRef (
@@ -1123,6 +1167,37 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
return handle;
}
+ void EmitManagedConstructorActivation (InstructionEncoder encoder, EntityHandle targetTypeRef,
+ MemberReferenceHandle ctorRef, IReadOnlyList managedParameterTypes,
+ IReadOnlyList jniParameterTypes)
+ {
+ encoder.OpCode (ILOpCode.Ldtoken);
+ encoder.Token (targetTypeRef);
+ encoder.Call (_getTypeFromHandleRef);
+ encoder.Call (_getUninitializedObjectRef);
+ encoder.OpCode (ILOpCode.Castclass);
+ encoder.Token (targetTypeRef);
+ encoder.OpCode (ILOpCode.Dup);
+
+ encoder.LoadArgument (1); // self
+ encoder.LoadConstantI4 (0); // JniObjectReferenceType.Invalid
+ encoder.OpCode (ILOpCode.Newobj);
+ encoder.Token (_jniObjectReferenceCtorRef);
+ encoder.OpCode (ILOpCode.Callvirt);
+ encoder.Token (_iJavaPeerableSetPeerReferenceRef);
+
+ for (int i = 0; i < managedParameterTypes.Count; i++) {
+ encoder.LoadArgument (i + 2);
+ if (jniParameterTypes [i] == JniParamKind.Object) {
+ encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
+ encoder.OpCode (ILOpCode.Call);
+ encoder.Token (MakeJavaConvertFromJniHandleSpec (managedParameterTypes [i]));
+ }
+ }
+
+ encoder.Call (ctorRef);
+ }
+
///
/// Emits the common try/catch/finally marshal-method wrapper pattern used by all
/// non-generic UCO constructor bodies:
@@ -1425,6 +1500,70 @@ TypeSpecificationHandle MakeGenericTypeSpec_ValueType (EntityHandle openType, En
return _pe.Metadata.AddTypeSpecification (_pe.Metadata.GetOrAddBlob (sigBlob));
}
+ MethodSpecificationHandle MakeJavaConvertFromJniHandleSpec (ManagedParameterInfo type)
+ {
+ var blob = new BlobBuilder (32);
+ blob.WriteByte (0x0A); // GENMETHOD_INST
+ blob.WriteCompressedInteger (1);
+ EncodeManagedType (blob, type);
+ return _pe.Metadata.AddMethodSpecification (_javaConvertFromJniHandleRef, _pe.Metadata.GetOrAddBlob (blob));
+ }
+
+ void EncodeManagedType (SignatureTypeEncoder encoder, ManagedParameterInfo type)
+ {
+ EncodeManagedType (encoder.Builder, type);
+ }
+
+ void EncodeManagedType (BlobBuilder builder, ManagedParameterInfo type)
+ {
+ string managedType = type.ManagedTypeName;
+ if (managedType.EndsWith ("[]", StringComparison.Ordinal)) {
+ builder.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY
+ EncodeManagedType (builder, new ManagedParameterInfo {
+ ManagedTypeName = managedType.Substring (0, managedType.Length - 2),
+ AssemblyName = type.AssemblyName,
+ });
+ return;
+ }
+
+ if (TryEncodePrimitiveManagedType (builder, managedType)) {
+ return;
+ }
+
+ var typeRef = _pe.ResolveTypeRef (new TypeRefData {
+ ManagedTypeName = managedType,
+ AssemblyName = type.AssemblyName,
+ });
+ builder.WriteByte (0x12); // ELEMENT_TYPE_CLASS
+ builder.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (typeRef));
+ }
+
+ static bool TryEncodePrimitiveManagedType (BlobBuilder builder, string managedType)
+ {
+ byte typeCode = managedType switch {
+ "System.Boolean" => 0x02,
+ "System.Char" => 0x03,
+ "System.SByte" => 0x04,
+ "System.Byte" => 0x05,
+ "System.Int16" => 0x06,
+ "System.UInt16" => 0x07,
+ "System.Int32" => 0x08,
+ "System.UInt32" => 0x09,
+ "System.Int64" => 0x0A,
+ "System.UInt64" => 0x0B,
+ "System.Single" => 0x0C,
+ "System.Double" => 0x0D,
+ "System.String" => 0x0E,
+ "System.IntPtr" => 0x18,
+ _ => 0,
+ };
+ if (typeCode == 0) {
+ return false;
+ }
+ builder.WriteByte (typeCode);
+ return true;
+ }
+
///
/// Encodes ReadOnlySpan<JniNativeMethod> directly into a signature type encoder.
/// Required because doesn't accept TypeSpec handles.
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
index ee285b42798..af11ed0dba5 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
@@ -224,6 +224,12 @@ public sealed record MarshalMethodInfo
///
public string? SuperArgumentsString { get; init; }
+ ///
+ /// Managed constructor parameter types, when this marshal entry represents a
+ /// concrete managed constructor. Null for inherited seed constructors.
+ ///
+ public IReadOnlyList? ManagedParameterTypes { get; init; }
+
///
/// True if this method was collected from an implemented interface
/// (Pass 4: CollectInterfaceMethodImplementations), not from the type itself.
@@ -267,6 +273,44 @@ public sealed record JavaConstructorInfo
/// Null for [Register] constructors.
///
public string? SuperArgumentsString { get; init; }
+
+ ///
+ /// Managed constructor parameter types, when this Java constructor corresponds to
+ /// a concrete managed constructor. Null for inherited seed constructors that do
+ /// not identify a constructor on the target type.
+ ///
+ public IReadOnlyList? ManagedParameterTypes { get; init; }
+
+ ///
+ /// Managed type that declares the concrete constructor to call.
+ /// Null when is null.
+ ///
+ public string? ConstructorDeclaringTypeName { get; init; }
+
+ ///
+ /// Assembly containing .
+ /// Null when is null.
+ ///
+ public string? ConstructorDeclaringAssemblyName { get; init; }
+}
+
+///
+/// Describes a managed parameter type for generated direct constructor calls.
+///
+public sealed record ManagedParameterInfo
+{
+ ///
+ /// Full managed type name, e.g. "System.String" or "Android.Content.Context".
+ /// Array types use C#-style "[]" suffixes.
+ ///
+ public required string ManagedTypeName { get; init; }
+
+ ///
+ /// Assembly containing the parameter type, when a TypeRef is needed.
+ /// Primitive types and System.String leave this empty because they are encoded
+ /// directly in metadata signatures.
+ ///
+ public string AssemblyName { get; init; } = "";
}
///
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
index dda7460271c..0fb65eb5c4d 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
@@ -610,11 +610,12 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index,
JniName = ".ctor",
JniSignature = jniSignature,
Connector = "",
- ManagedMethodName = ".ctor",
- NativeCallbackName = "n_ctor",
- IsConstructor = true,
- SuperArgumentsString = "",
- });
+ ManagedMethodName = ".ctor",
+ NativeCallbackName = "n_ctor",
+ IsConstructor = true,
+ SuperArgumentsString = "",
+ ManagedParameterTypes = ToManagedParameterInfos (sig.ParameterTypes, index.AssemblyName),
+ });
alreadyRegisteredSignatures.Add (jniSignature);
}
}
@@ -896,6 +897,7 @@ static void AddMarshalMethod (List methods, RegisterInfo regi
string declaringTypeName = "";
string declaringAssemblyName = "";
ParseConnectorDeclaringType (registerInfo.Connector, out declaringTypeName, out declaringAssemblyName);
+ var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
methods.Add (new MarshalMethodInfo {
JniName = registerInfo.JniName,
@@ -911,9 +913,31 @@ static void AddMarshalMethod (List methods, RegisterInfo regi
JavaAccess = isExport ? GetJavaAccess (methodDef.Attributes & MethodAttributes.MemberAccessMask) : null,
ThrownNames = exportInfo?.ThrownNames,
SuperArgumentsString = exportInfo?.SuperArgumentsString,
+ ManagedParameterTypes = isConstructor ? ToManagedParameterInfos (sig.ParameterTypes, index.AssemblyName) : null,
});
}
+ static ManagedParameterInfo [] ToManagedParameterInfos (IReadOnlyList parameterTypes, string defaultAssemblyName)
+ {
+ var result = new ManagedParameterInfo [parameterTypes.Count];
+ for (int i = 0; i < parameterTypes.Count; i++) {
+ string parameterType = parameterTypes [i];
+ result [i] = new ManagedParameterInfo {
+ ManagedTypeName = parameterType,
+ AssemblyName = NeedsTypeReference (parameterType) ? defaultAssemblyName : "",
+ };
+ }
+ return result;
+ }
+
+ static bool NeedsTypeReference (string managedType)
+ {
+ while (managedType.EndsWith ("[]", StringComparison.Ordinal)) {
+ managedType = managedType.Substring (0, managedType.Length - 2);
+ }
+ return TryGetPrimitiveJniDescriptor (managedType) is null;
+ }
+
static string GetJavaAccess (MethodAttributes access)
{
return access switch {
@@ -1553,6 +1577,9 @@ static List BuildJavaConstructors (List
JniSignature = mm.JniSignature,
ConstructorIndex = ctorIndex,
SuperArgumentsString = mm.SuperArgumentsString,
+ ManagedParameterTypes = mm.ManagedParameterTypes,
+ ConstructorDeclaringTypeName = mm.ManagedParameterTypes is null ? null : mm.DeclaringTypeName,
+ ConstructorDeclaringAssemblyName = mm.ManagedParameterTypes is null ? null : mm.DeclaringAssemblyName,
});
ctorIndex++;
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
index ec37cbff9af..b81e475c95d 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
@@ -1109,6 +1109,60 @@ public void Generate_AliasHolder_HasDeserializableAliasKeys ()
}
}
+ [Fact]
+ public void Generate_UcoConstructor_WithManagedParameters_CallsMatchingConstructor ()
+ {
+ var peer = FindFixtureByJavaName ("my/app/ActivityWithCustomCtor");
+ Assert.Contains (peer.JavaConstructors, c => c.JniSignature == "(Ljava/lang/String;)V");
+
+ using var stream = GenerateAssembly (new [] { peer }, "UcoCtorManagedString");
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ var stringCtor = FindCtorMemberRef (reader, "MyApp", "ActivityWithCustomCtor", "System.String");
+ var activationCtors = FindCtorMemberRefs (reader, "MyApp", "ActivityWithCustomCtor",
+ "System.IntPtr", "Android.Runtime.JniHandleOwnership");
+
+ var nctorMethod = reader.GetMethodDefinition (FindMethodDefinition (reader, "nctor_1_uco"));
+ var body = pe.GetMethodBody (nctorMethod.RelativeVirtualAddress);
+ var ilBytes = body.GetILBytes ();
+ Assert.NotNull (ilBytes);
+ Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (stringCtor)),
+ "nctor_1_uco should call ActivityWithCustomCtor(string), not the activation constructor.");
+ foreach (var activationCtor in activationCtors) {
+ Assert.False (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (activationCtor)),
+ "nctor_1_uco should not call the activation constructor.");
+ }
+ }
+
+ [Fact]
+ public void Generate_UcoConstructor_ObjectParameter_ConvertsJniHandleBeforeConstructorCall ()
+ {
+ var peer = FindFixtureByJavaName ("my/app/CustomView");
+ Assert.Contains (peer.JavaConstructors, c => c.JniSignature == "(Landroid/content/Context;)V");
+
+ using var stream = GenerateAssembly (new [] { peer }, "UcoCtorManagedObject");
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ var contextCtor = FindCtorMemberRef (reader, "MyApp", "CustomView", "Android.Content.Context");
+ var javaConvertFromJniHandle = Enumerable.Range (1, reader.GetTableRowCount (TableIndex.MemberRef))
+ .Select (i => MetadataTokens.MemberReferenceHandle (i))
+ .Single (h => reader.GetString (reader.GetMemberReference (h).Name) == "FromJniHandle");
+ var javaConvertFromJniHandleSpec = Enumerable.Range (1, reader.GetTableRowCount (TableIndex.MethodSpec))
+ .Select (i => MetadataTokens.MethodSpecificationHandle (i))
+ .Single (h => reader.GetMethodSpecification (h).Method == javaConvertFromJniHandle);
+ var nctorMethod = reader.GetMethodDefinition (FindMethodDefinition (reader, "nctor_1_uco"));
+ var body = pe.GetMethodBody (nctorMethod.RelativeVirtualAddress);
+ var ilBytes = body.GetILBytes ();
+ Assert.NotNull (ilBytes);
+
+ Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (javaConvertFromJniHandleSpec)),
+ "nctor_1_uco should convert the JNI Context handle to the managed Context parameter.");
+ Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (contextCtor)),
+ "nctor_1_uco should call CustomView(Context), not the activation constructor.");
+ }
+
[Fact]
public void Generate_UcoConstructor_HasMarshalMethodMetadataAndExceptionRegions ()
{
From d3d3f514cfd414bf9f15cd9646a60d6ee6ddac2d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 6 May 2026 09:51:28 +0000
Subject: [PATCH 3/3] Address constructor UCO review feedback
Agent-Logs-Url: https://github.com/dotnet/android/sessions/2a0c90c9-484f-417c-875c-bea6afda1c98
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
---
.../Generator/TypeMapAssemblyEmitter.cs | 10 ++++------
.../Scanner/JavaPeerScanner.cs | 18 +++++++++++++-----
2 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
index af0a79e7fb6..375bea78501 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
@@ -1518,12 +1518,10 @@ void EncodeManagedType (BlobBuilder builder, ManagedParameterInfo type)
{
string managedType = type.ManagedTypeName;
if (managedType.EndsWith ("[]", StringComparison.Ordinal)) {
- builder.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY
- EncodeManagedType (builder, new ManagedParameterInfo {
- ManagedTypeName = managedType.Substring (0, managedType.Length - 2),
- AssemblyName = type.AssemblyName,
- });
- return;
+ do {
+ builder.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY
+ managedType = managedType.Substring (0, managedType.Length - 2);
+ } while (managedType.EndsWith ("[]", StringComparison.Ordinal));
}
if (TryEncodePrimitiveManagedType (builder, managedType)) {
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
index 0fb65eb5c4d..801357a321f 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
@@ -882,7 +882,7 @@ static bool HaveIdenticalParameterTypes (MethodDefinition method1, MethodDefinit
return true;
}
- static void AddMarshalMethod (List methods, RegisterInfo registerInfo, MethodDefinition methodDef, AssemblyIndex index, ExportInfo? exportInfo = null, bool isInterfaceImplementation = false)
+ void AddMarshalMethod (List methods, RegisterInfo registerInfo, MethodDefinition methodDef, AssemblyIndex index, ExportInfo? exportInfo = null, bool isInterfaceImplementation = false)
{
// Skip methods that are just the JNI name (type-level [Register])
if (registerInfo.Signature is null && registerInfo.Connector is null) {
@@ -917,25 +917,33 @@ static void AddMarshalMethod (List methods, RegisterInfo regi
});
}
- static ManagedParameterInfo [] ToManagedParameterInfos (IReadOnlyList parameterTypes, string defaultAssemblyName)
+ ManagedParameterInfo [] ToManagedParameterInfos (IReadOnlyList parameterTypes, string defaultAssemblyName)
{
var result = new ManagedParameterInfo [parameterTypes.Count];
for (int i = 0; i < parameterTypes.Count; i++) {
string parameterType = parameterTypes [i];
result [i] = new ManagedParameterInfo {
ManagedTypeName = parameterType,
- AssemblyName = NeedsTypeReference (parameterType) ? defaultAssemblyName : "",
+ AssemblyName = ResolveManagedParameterAssembly (parameterType, defaultAssemblyName),
};
}
return result;
}
- static bool NeedsTypeReference (string managedType)
+ string ResolveManagedParameterAssembly (string managedType, string defaultAssemblyName)
{
while (managedType.EndsWith ("[]", StringComparison.Ordinal)) {
managedType = managedType.Substring (0, managedType.Length - 2);
}
- return TryGetPrimitiveJniDescriptor (managedType) is null;
+ if (TryGetPrimitiveJniDescriptor (managedType) is not null) {
+ return "";
+ }
+ foreach (var assembly in assemblyCache.Values) {
+ if (assembly.TypesByFullName.ContainsKey (managedType)) {
+ return assembly.AssemblyName;
+ }
+ }
+ return defaultAssemblyName;
}
static string GetJavaAccess (MethodAttributes access)