Skip to content

Commit 83a50be

Browse files
committed
Fix UCO constructor wrappers to include full JNI parameter list
The UCO constructor wrapper signature must match the JNI native method signature (jnienv + self + constructor params) for correct ABI. Previously it only accepted (IntPtr, IntPtr), causing a calling convention mismatch when JNI dispatches constructors with parameters. The body still calls ActivateInstance(self, typeof(T)) — the constructor params are consumed but not forwarded, since peer activation uses the (IntPtr, JniHandleOwnership) activation ctor, not the user constructor.
1 parent 0c5365e commit 83a50be

File tree

3 files changed

+29
-11
lines changed

3 files changed

+29
-11
lines changed

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ sealed class UcoMethodData
132132

133133
/// <summary>
134134
/// An [UnmanagedCallersOnly] static wrapper for a constructor callback.
135+
/// Signature must match the full JNI native method signature (jnienv + self + ctor params)
136+
/// so the ABI is correct when JNI dispatches the call.
135137
/// Body: TrimmableNativeRegistration.ActivateInstance(self, typeof(TargetType)).
136138
/// </summary>
137139
sealed class UcoConstructorData

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -493,19 +493,25 @@ MethodDefinitionHandle EmitUcoConstructor (MetadataBuilder metadata, BlobBuilder
493493
{
494494
var userTypeRef = ResolveTypeRef (metadata, uco.TargetType);
495495

496-
// UCO constructor wrappers always take exactly (IntPtr jnienv, IntPtr self) regardless
497-
// of the actual JNI constructor signature. The JNI parameters are not forwarded —
498-
// ActivateInstance only needs the jobject handle to create the managed peer.
499-
// The correct JNI signature is still used in RegisterNatives so the JNI runtime
500-
// dispatches to this wrapper for the right constructor overload.
496+
// UCO constructor wrappers must match the JNI native method signature exactly.
497+
// The Java JCW declares e.g. "private native void nctor_0(Context p0)" and calls
498+
// it with arguments. JNI dispatches with (JNIEnv*, jobject, <ctor params...>),
499+
// so the wrapper signature must include all parameters to match the ABI.
500+
// Only jnienv (arg 0) and self (arg 1) are used — the constructor parameters
501+
// are not forwarded because ActivateInstance creates the managed peer using the
502+
// activation ctor (IntPtr, JniHandleOwnership), not the user-visible constructor.
503+
var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature);
504+
int paramCount = 2 + jniParams.Count;
501505

502506
var handle = EmitBody (metadata, ilBuilder, uco.WrapperName,
503507
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
504-
sig => sig.MethodSignature ().Parameters (2,
508+
sig => sig.MethodSignature ().Parameters (paramCount,
505509
rt => rt.Void (),
506510
p => {
507-
p.AddParameter ().Type ().IntPtr ();
508-
p.AddParameter ().Type ().IntPtr ();
511+
p.AddParameter ().Type ().IntPtr (); // jnienv
512+
p.AddParameter ().Type ().IntPtr (); // self
513+
for (int j = 0; j < jniParams.Count; j++)
514+
JniSignatureHelper.EncodeClrType (p.AddParameter ().Type (), jniParams [j]);
509515
}),
510516
encoder => {
511517
encoder.LoadArgument (1); // self

tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,7 +1550,7 @@ public void FullPipeline_CustomView_HasConstructorAndMethodWrappers ()
15501550
}
15511551

15521552
[Fact]
1553-
public void FullPipeline_CustomView_UcoConstructorHasExactlyTwoParams ()
1553+
public void FullPipeline_CustomView_UcoConstructorMatchesJniSignature ()
15541554
{
15551555
var peer = FindFixtureByJavaName ("my/app/CustomView");
15561556
var model = BuildModel (new [] { peer }, "CtorSigTest");
@@ -1574,12 +1574,22 @@ public void FullPipeline_CustomView_UcoConstructorHasExactlyTwoParams ()
15741574
.ToList ();
15751575

15761576
Assert.NotEmpty (ucoCtors);
1577+
1578+
// Match each UCO constructor to its model data to verify param count
15771579
foreach (var uco in ucoCtors) {
1578-
// UCO constructor wrappers always take exactly 2 params (IntPtr jnienv, IntPtr self)
1580+
var name = reader.GetString (uco.Name);
1581+
var modelUco = model.ProxyTypes
1582+
.SelectMany (p => p.UcoConstructors)
1583+
.First (u => u.WrapperName == name);
1584+
1585+
// UCO constructor signature must include jnienv + self + JNI params
1586+
int expectedJniParams = JniSignatureHelper.ParseParameterTypes (modelUco.JniSignature).Count;
1587+
int expectedTotal = 2 + expectedJniParams;
1588+
15791589
var sig = reader.GetBlobReader (uco.Signature);
15801590
var header = sig.ReadSignatureHeader ();
15811591
int paramCount = sig.ReadCompressedInteger ();
1582-
Assert.Equal (2, paramCount);
1592+
Assert.Equal (expectedTotal, paramCount);
15831593
}
15841594
} finally {
15851595
CleanUpDir (outputPath);

0 commit comments

Comments
 (0)