Skip to content

Commit ba936c9

Browse files
committed
Implement remaining [Export] marshaling gaps
- Support arrays in export marshal wrappers using JNIEnv.GetArray<T>/NewArray<T>/CopyArray<T> including copy-back cleanup for array parameters - Support enum signatures and marshaling as JNI int while calling managed enum methods - Map Java.Lang.ICharSequence to Ljava/lang/CharSequence; in export signature generation - Resolve JNI object descriptors from [Register] metadata for managed object types (e.g. View[] -> [Landroid/view/View; instead of Object[]) - Improve ManagedTypeToAssemblyQualifiedName to resolve non-BCL types and array element types - Generate proxies for export-only ACW types (no activation ctor / no [Register] methods) Add fixture coverage and tests for export-only proxies plus array/enum/charsequence signatures and registration. All 312 tests pass.
1 parent ad06f1c commit ba936c9

File tree

7 files changed

+670
-69
lines changed

7 files changed

+670
-69
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName,
117117
var peer = peersForName [i];
118118
string entryJniName = i == 0 ? jniName : $"{jniName}[{i}]";
119119

120-
bool hasProxy = peer.ActivationCtor != null || peer.InvokerTypeName != null;
121120
bool isAcw = !peer.DoNotGenerateAcw && !peer.IsInterface && peer.MarshalMethods.Count > 0;
121+
bool hasProxy = peer.ActivationCtor != null || peer.InvokerTypeName != null || isAcw;
122122

123123
JavaPeerProxyData? proxy = null;
124124
if (hasProxy) {

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

Lines changed: 327 additions & 47 deletions
Large diffs are not rendered by default.

src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs

Lines changed: 149 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary<string, JavaPeerInfo> results
308308
return (methods, exportFields);
309309
}
310310

311-
static void AddMarshalMethod (List<MarshalMethodInfo> methods, RegisterInfo registerInfo, MethodDefinition methodDef, AssemblyIndex index)
311+
void AddMarshalMethod (List<MarshalMethodInfo> methods, RegisterInfo registerInfo, MethodDefinition methodDef, AssemblyIndex index)
312312
{
313313
// Skip methods that are just the JNI name (type-level [Register])
314314
if (registerInfo.Signature == null && registerInfo.Connector == null) {
@@ -328,10 +328,10 @@ static void AddMarshalMethod (List<MarshalMethodInfo> methods, RegisterInfo regi
328328
if (registerInfo.Connector == null || isConstructor) {
329329
var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
330330
for (int i = 0; i < parameters.Count && i < sig.ParameterTypes.Length; i++) {
331-
parameters [i].ManagedType = ManagedTypeToAssemblyQualifiedName (sig.ParameterTypes [i]);
331+
parameters [i].ManagedType = ManagedTypeToAssemblyQualifiedName (sig.ParameterTypes [i], index);
332332
}
333333
if (sig.ReturnType != "System.Void") {
334-
managedReturnType = ManagedTypeToAssemblyQualifiedName (sig.ReturnType);
334+
managedReturnType = ManagedTypeToAssemblyQualifiedName (sig.ReturnType, index);
335335
}
336336
}
337337

@@ -409,7 +409,7 @@ List<string> ResolveImplementedInterfaceJavaNames (TypeDefinition typeDef, Assem
409409
return null;
410410
}
411411

412-
static RegisterInfo ParseExportAttribute (CustomAttribute ca, MethodDefinition methodDef, AssemblyIndex index)
412+
RegisterInfo ParseExportAttribute (CustomAttribute ca, MethodDefinition methodDef, AssemblyIndex index)
413413
{
414414
var value = ca.DecodeValue (index.customAttributeTypeProvider);
415415

@@ -439,7 +439,7 @@ static RegisterInfo ParseExportAttribute (CustomAttribute ca, MethodDefinition m
439439

440440
// Build JNI signature from method signature
441441
var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
442-
var jniSig = BuildJniSignatureFromManaged (sig);
442+
var jniSig = BuildJniSignatureFromManaged (sig, index);
443443

444444
return new RegisterInfo (exportName, jniSig, null, false,
445445
thrownNames: thrownNames, superArgumentsString: superArguments);
@@ -449,11 +449,11 @@ static RegisterInfo ParseExportAttribute (CustomAttribute ca, MethodDefinition m
449449
/// Creates a RegisterInfo for an [ExportField] method.
450450
/// The method is registered like [Export] (Connector = null) so it gets a full marshal body.
451451
/// </summary>
452-
static RegisterInfo ParseExportFieldAsRegisterInfo (MethodDefinition methodDef, AssemblyIndex index)
452+
RegisterInfo ParseExportFieldAsRegisterInfo (MethodDefinition methodDef, AssemblyIndex index)
453453
{
454454
var methodName = index.Reader.GetString (methodDef.Name);
455455
var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
456-
var jniSig = BuildJniSignatureFromManaged (sig);
456+
var jniSig = BuildJniSignatureFromManaged (sig, index);
457457
return new RegisterInfo (methodName, jniSig, null, false);
458458
}
459459

@@ -497,19 +497,19 @@ static RegisterInfo ParseExportFieldAsRegisterInfo (MethodDefinition methodDef,
497497
return null;
498498
}
499499

500-
static string BuildJniSignatureFromManaged (MethodSignature<string> sig)
500+
string BuildJniSignatureFromManaged (MethodSignature<string> sig, AssemblyIndex index)
501501
{
502502
var sb = new System.Text.StringBuilder ();
503503
sb.Append ('(');
504504
foreach (var param in sig.ParameterTypes) {
505-
sb.Append (ManagedTypeToJniDescriptor (param));
505+
sb.Append (ManagedTypeToJniDescriptor (param, index));
506506
}
507507
sb.Append (')');
508-
sb.Append (ManagedTypeToJniDescriptor (sig.ReturnType));
508+
sb.Append (ManagedTypeToJniDescriptor (sig.ReturnType, index));
509509
return sb.ToString ();
510510
}
511511

512-
static string ManagedTypeToJniDescriptor (string managedType)
512+
string ManagedTypeToJniDescriptor (string managedType, AssemblyIndex index)
513513
{
514514
switch (managedType) {
515515
case "System.Void": return "V";
@@ -526,9 +526,17 @@ static string ManagedTypeToJniDescriptor (string managedType)
526526
case "System.Single": return "F";
527527
case "System.Double": return "D";
528528
case "System.String": return "Ljava/lang/String;";
529+
case "Java.Lang.ICharSequence": return "Ljava/lang/CharSequence;";
529530
default:
530531
if (managedType.EndsWith ("[]")) {
531-
return "[" + ManagedTypeToJniDescriptor (managedType.Substring (0, managedType.Length - 2));
532+
return "[" + ManagedTypeToJniDescriptor (managedType.Substring (0, managedType.Length - 2), index);
533+
}
534+
if (IsEnumManagedType (managedType, index)) {
535+
return "I";
536+
}
537+
var jniName = ResolveManagedTypeJniName (managedType, index);
538+
if (!string.IsNullOrEmpty (jniName)) {
539+
return "L" + jniName + ";";
532540
}
533541
return "Ljava/lang/Object;";
534542
}
@@ -538,8 +546,12 @@ static string ManagedTypeToJniDescriptor (string managedType)
538546
/// Maps a managed type name (from SignatureTypeProvider) to an assembly-qualified name
539547
/// like "System.Int32, System.Private.CoreLib" used in TypeManager.Activate calls.
540548
/// </summary>
541-
static string ManagedTypeToAssemblyQualifiedName (string managedType)
549+
string ManagedTypeToAssemblyQualifiedName (string managedType, AssemblyIndex index)
542550
{
551+
if (managedType.IndexOf (", ", StringComparison.Ordinal) >= 0) {
552+
return managedType;
553+
}
554+
543555
// BCL types all live in System.Private.CoreLib
544556
switch (managedType) {
545557
case "System.Void":
@@ -561,11 +573,131 @@ static string ManagedTypeToAssemblyQualifiedName (string managedType)
561573
case "System.UIntPtr":
562574
return managedType + ", System.Private.CoreLib";
563575
default:
564-
// For non-BCL types, we don't know the assembly at this point.
565-
// This is a best-effort mapping; full assembly resolution for
566-
// arbitrary types is a follow-up.
567-
return managedType;
576+
// Best-effort assembly resolution across loaded assemblies.
577+
var assemblyName = ResolveManagedTypeAssemblyName (managedType, index);
578+
return assemblyName != null ? managedType + ", " + assemblyName : managedType;
579+
}
580+
}
581+
582+
string? ResolveManagedTypeAssemblyName (string managedType, AssemblyIndex index)
583+
{
584+
string typeName = StripManagedTypeDecorations (managedType);
585+
if (typeName.Length == 0 || typeName [0] == '!') {
586+
return null; // generic method/type parameter
587+
}
588+
589+
if (IsBclTypeName (typeName)) {
590+
return "System.Private.CoreLib";
591+
}
592+
593+
if (TryResolveManagedTypeDefinition (typeName, index, out _, out var resolvedIndex)) {
594+
return resolvedIndex.AssemblyName;
595+
}
596+
597+
return index.AssemblyName;
598+
}
599+
600+
bool IsEnumManagedType (string managedType, AssemblyIndex index)
601+
{
602+
string typeName = StripManagedTypeDecorations (managedType);
603+
if (typeName.Length == 0 || typeName [0] == '!') {
604+
return false;
605+
}
606+
607+
if (!TryResolveManagedTypeDefinition (typeName, index, out var handle, out var resolvedIndex)) {
608+
return false;
609+
}
610+
611+
var typeDef = resolvedIndex.Reader.GetTypeDefinition (handle);
612+
if (typeDef.BaseType.Kind == HandleKind.TypeReference) {
613+
var (baseTypeName, _) = ResolveTypeReference ((TypeReferenceHandle) typeDef.BaseType, resolvedIndex);
614+
return baseTypeName == "System.Enum";
615+
}
616+
if (typeDef.BaseType.Kind == HandleKind.TypeDefinition) {
617+
var baseTypeDef = resolvedIndex.Reader.GetTypeDefinition ((TypeDefinitionHandle) typeDef.BaseType);
618+
return AssemblyIndex.GetFullName (baseTypeDef, resolvedIndex.Reader) == "System.Enum";
619+
}
620+
return false;
621+
}
622+
623+
string? ResolveManagedTypeJniName (string managedType, AssemblyIndex index)
624+
{
625+
string typeName = StripManagedTypeDecorations (managedType);
626+
if (typeName.Length == 0 || typeName [0] == '!') {
627+
return null;
628+
}
629+
630+
if (!TryResolveManagedTypeDefinition (typeName, index, out var handle, out var resolvedIndex)) {
631+
return null;
632+
}
633+
634+
if (resolvedIndex.RegisterInfoByType.TryGetValue (handle, out var regInfo) &&
635+
!string.IsNullOrEmpty (regInfo.JniName)) {
636+
return regInfo.JniName;
637+
}
638+
639+
return null;
640+
}
641+
642+
bool TryResolveManagedTypeDefinition (string managedTypeName, AssemblyIndex index, out TypeDefinitionHandle handle, out AssemblyIndex resolvedIndex)
643+
{
644+
if (TryResolveType (managedTypeName, index.AssemblyName, out handle, out resolvedIndex)) {
645+
return true;
646+
}
647+
648+
foreach (var candidate in assemblyCache.Values) {
649+
if (candidate.TypesByFullName.TryGetValue (managedTypeName, out handle)) {
650+
resolvedIndex = candidate;
651+
return true;
652+
}
653+
}
654+
655+
handle = default;
656+
resolvedIndex = null!;
657+
return false;
658+
}
659+
660+
static bool IsBclTypeName (string managedTypeName)
661+
{
662+
switch (managedTypeName) {
663+
case "System.Void":
664+
case "System.Boolean":
665+
case "System.Byte":
666+
case "System.SByte":
667+
case "System.Char":
668+
case "System.Int16":
669+
case "System.UInt16":
670+
case "System.Int32":
671+
case "System.UInt32":
672+
case "System.Int64":
673+
case "System.UInt64":
674+
case "System.Single":
675+
case "System.Double":
676+
case "System.String":
677+
case "System.Object":
678+
case "System.IntPtr":
679+
case "System.UIntPtr":
680+
return true;
681+
default:
682+
return false;
683+
}
684+
}
685+
686+
static string StripManagedTypeDecorations (string managedType)
687+
{
688+
string result = managedType;
689+
while (result.EndsWith ("[]", StringComparison.Ordinal) ||
690+
result.EndsWith ("&", StringComparison.Ordinal) ||
691+
result.EndsWith ("*", StringComparison.Ordinal)) {
692+
result = result.Substring (0, result.Length - (result.EndsWith ("[]", StringComparison.Ordinal) ? 2 : 1));
693+
}
694+
695+
int genericStart = result.IndexOf ('<');
696+
if (genericStart >= 0) {
697+
result = result.Substring (0, genericStart);
568698
}
699+
700+
return result;
569701
}
570702

571703
ActivationCtorInfo? ResolveActivationCtor (string typeName, TypeDefinition typeDef, AssemblyIndex index)

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

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,29 @@ public void Generate_ExportMethod_HasWrapperMethods ()
383383
}
384384
}
385385

386+
[Fact]
387+
public void Generate_ExportMarshalComplex_UsesArrayMarshalHelpers ()
388+
{
389+
var peers = ScanFixtures ();
390+
var peer = peers.First (p => p.JavaName == "my/app/ExportMarshalComplex");
391+
var path = GenerateAssembly (new [] { peer }, "ExportMarshalComplexHelpers");
392+
try {
393+
var (pe, reader) = OpenAssembly (path);
394+
using (pe) {
395+
var memberNames = Enumerable.Range (1, reader.GetTableRowCount (TableIndex.MemberRef))
396+
.Select (i => reader.GetMemberReference (MetadataTokens.MemberReferenceHandle (i)))
397+
.Select (m => reader.GetString (m.Name))
398+
.ToList ();
399+
Assert.Contains ("GetArray", memberNames);
400+
Assert.Contains ("NewArray", memberNames);
401+
Assert.Contains ("CopyArray", memberNames);
402+
Assert.Contains ("GetCharSequence", memberNames);
403+
}
404+
} finally {
405+
CleanUp (path);
406+
}
407+
}
408+
386409
[Fact]
387410
public void Generate_ExportConstructor_HasWrapperMethods ()
388411
{
@@ -410,6 +433,34 @@ public void Generate_ExportConstructor_HasWrapperMethods ()
410433
}
411434
}
412435

436+
[Fact]
437+
public void Generate_ExportOnlyType_HasProxyAndRegistration ()
438+
{
439+
var peers = ScanFixtures ();
440+
var peer = peers.First (p => p.ManagedTypeName == "MyApp.UnregisteredExporter");
441+
var path = GenerateAssembly (new [] { peer }, "ExportOnlyType");
442+
try {
443+
var (pe, reader) = OpenAssembly (path);
444+
using (pe) {
445+
var proxy = reader.TypeDefinitions
446+
.Select (h => reader.GetTypeDefinition (h))
447+
.First (t => reader.GetString (t.Name) == "MyApp_UnregisteredExporter_Proxy");
448+
449+
var methods = proxy.GetMethods ()
450+
.Select (h => reader.GetMethodDefinition (h))
451+
.Select (m => reader.GetString (m.Name))
452+
.ToList ();
453+
Assert.Contains ("n_doExportedWork_uco_0", methods);
454+
Assert.Contains ("RegisterNatives", methods);
455+
456+
var entries = ReadRegisterNativesEntries (pe, reader, proxy);
457+
Assert.Contains (entries, e => e.jniMethodName == "n_DoExportedWork" && e.jniSignature == "()V");
458+
}
459+
} finally {
460+
CleanUp (path);
461+
}
462+
}
463+
413464
[Fact]
414465
public void Generate_ExportMethod_HasMethodBody ()
415466
{
@@ -619,6 +670,31 @@ public void Generate_StaticExportAndExportField_RegisteredInRegisterNatives ()
619670
}
620671
}
621672

673+
[Fact]
674+
public void Generate_ExportMarshalComplex_RegisteredInRegisterNatives ()
675+
{
676+
var peers = ScanFixtures ();
677+
var peer = peers.First (p => p.JavaName == "my/app/ExportMarshalComplex");
678+
var path = GenerateAssembly (new [] { peer }, "ExportMarshalComplexRegistration");
679+
try {
680+
var (pe, reader) = OpenAssembly (path);
681+
using (pe) {
682+
var proxy = reader.TypeDefinitions
683+
.Select (h => reader.GetTypeDefinition (h))
684+
.First (t => reader.GetString (t.Name) == "MyApp_ExportMarshalComplex_Proxy");
685+
686+
var entries = ReadRegisterNativesEntries (pe, reader, proxy);
687+
Assert.Contains (entries, e => e.jniMethodName == "n_MutateInts" && e.jniSignature == "([I)V");
688+
Assert.Contains (entries, e => e.jniMethodName == "n_RoundTripEnum" && e.jniSignature == "(I)I");
689+
Assert.Contains (entries, e => e.jniMethodName == "n_EchoCharSequence" && e.jniSignature == "(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;");
690+
Assert.Contains (entries, e => e.jniMethodName == "n_EchoViews" && e.jniSignature == "([Landroid/view/View;)[Landroid/view/View;");
691+
Assert.Contains (entries, e => e.jniMethodName == "n_EchoStrings" && e.jniSignature == "([Ljava/lang/String;)[Ljava/lang/String;");
692+
}
693+
} finally {
694+
CleanUp (path);
695+
}
696+
}
697+
622698
[Fact]
623699
public void Generate_ExportRegistration_HasCorrectJniSignatures ()
624700
{
@@ -1071,4 +1147,4 @@ static void CleanUp (string path)
10711147
try { Directory.Delete (dir, true); } catch { }
10721148
}
10731149
}
1074-
}
1150+
}

0 commit comments

Comments
 (0)