Skip to content

Commit 6c4f4b9

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 1b39389 commit 6c4f4b9

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
@@ -306,7 +306,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary<string, JavaPeerInfo> results
306306
return (methods, exportFields);
307307
}
308308

309-
static void AddMarshalMethod (List<MarshalMethodInfo> methods, RegisterInfo registerInfo, MethodDefinition methodDef, AssemblyIndex index)
309+
void AddMarshalMethod (List<MarshalMethodInfo> methods, RegisterInfo registerInfo, MethodDefinition methodDef, AssemblyIndex index)
310310
{
311311
// Skip methods that are just the JNI name (type-level [Register])
312312
if (registerInfo.Signature == null && registerInfo.Connector == null) {
@@ -326,10 +326,10 @@ static void AddMarshalMethod (List<MarshalMethodInfo> methods, RegisterInfo regi
326326
if (registerInfo.Connector == null || isConstructor) {
327327
var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
328328
for (int i = 0; i < parameters.Count && i < sig.ParameterTypes.Length; i++) {
329-
parameters [i].ManagedType = ManagedTypeToAssemblyQualifiedName (sig.ParameterTypes [i]);
329+
parameters [i].ManagedType = ManagedTypeToAssemblyQualifiedName (sig.ParameterTypes [i], index);
330330
}
331331
if (sig.ReturnType != "System.Void") {
332-
managedReturnType = ManagedTypeToAssemblyQualifiedName (sig.ReturnType);
332+
managedReturnType = ManagedTypeToAssemblyQualifiedName (sig.ReturnType, index);
333333
}
334334
}
335335
methods.Add (new MarshalMethodInfo {
@@ -406,7 +406,7 @@ List<string> ResolveImplementedInterfaceJavaNames (TypeDefinition typeDef, Assem
406406
return null;
407407
}
408408

409-
static RegisterInfo ParseExportAttribute (CustomAttribute ca, MethodDefinition methodDef, AssemblyIndex index)
409+
RegisterInfo ParseExportAttribute (CustomAttribute ca, MethodDefinition methodDef, AssemblyIndex index)
410410
{
411411
var value = ca.DecodeValue (index.customAttributeTypeProvider);
412412

@@ -436,7 +436,7 @@ static RegisterInfo ParseExportAttribute (CustomAttribute ca, MethodDefinition m
436436

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

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

@@ -494,19 +494,19 @@ static RegisterInfo ParseExportFieldAsRegisterInfo (MethodDefinition methodDef,
494494
return null;
495495
}
496496

497-
static string BuildJniSignatureFromManaged (MethodSignature<string> sig)
497+
string BuildJniSignatureFromManaged (MethodSignature<string> sig, AssemblyIndex index)
498498
{
499499
var sb = new System.Text.StringBuilder ();
500500
sb.Append ('(');
501501
foreach (var param in sig.ParameterTypes) {
502-
sb.Append (ManagedTypeToJniDescriptor (param));
502+
sb.Append (ManagedTypeToJniDescriptor (param, index));
503503
}
504504
sb.Append (')');
505-
sb.Append (ManagedTypeToJniDescriptor (sig.ReturnType));
505+
sb.Append (ManagedTypeToJniDescriptor (sig.ReturnType, index));
506506
return sb.ToString ();
507507
}
508508

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

568700
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)