Skip to content

Commit a760281

Browse files
authored
Marshal methods runtime support
Context: 903ba37 Context: e1af958 Context: dotnet/java-interop#1027 Commit 903ba37 mentioned a TODO: > Update/rewrite infrastructure to focus on implementing the runtime > side of marshal methods, making it possible to actually run > applications which use marshal methods. Implement the necessary runtime elements to enable running of applications with marshal methods. It is now possible, if LLVM marshal methods are enabled/`ENABLE_MARSHAL_METHODS` is defined, to run both plain .NET SDK for Android and MAUI apps. Update `src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Runtime.InteropServices.xml` so that `System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute` is always preserved, as it is required by LLVM Marshal Methods. The `[UnmanagedCallersOnly]` attribute used by marshal methods requires that the invoked method have [blittable types][0] for the method return type and all parameter types. Unfortunately, `bool` is *not* a blittable type. Implement generation of wrapper methods which replace `bool` with `byte` and convert the values appropriately before calling the actual target method. In a "hello world" MAUI test app there are 133 such methods (out of around 180 total). Implement code that enables us to show error messages with the proper assembly, class and method names on failure to look up or obtain pointer to native callback methods. TODO: * Process *all* assemblies, including `Mono.Android.dll`, for Java Callable Wrapper generation. This is necessary so that we can find and emit LLVM marshal methods for types defined within `Mono.Android.dll`. * Remove the `ENABLE_MARSHAL_METHODS` define, and enable LLVM marshal methods for everyone. * Update `<GenerateJavaStubs/>` to rewrite all assemblies for all Supported ABIs. Currently, we don't support `Java.Lang.Object` & `Java.Lang.Throwable` subclasses being located in per-ABI assemblies. * How do `Java_…` native functions interact with `JNIEnv::RegisterNatives()`? #7285 (comment) * *Can* JNI `native` methods contain "non-printable" characters such as `\n`, or "non-representable in ELF symbols" characters such as `-` (e.g. Kotlin mangled methods)? #7285 (comment) * Cleanup, cleanup, cleanup [0]: https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types
1 parent 382165d commit a760281

16 files changed

+1008
-131
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<linker>
3+
<assembly fullname="System.Runtime.InteropServices">
4+
<type fullname="System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute" />
5+
</assembly>
6+
</linker>

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

+85-6
Original file line numberDiff line numberDiff line change
@@ -473,8 +473,28 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu
473473
public override void RegisterNativeMembers (JniType nativeClass, Type type, string? methods) =>
474474
RegisterNativeMembers (nativeClass, type, methods.AsSpan ());
475475

476+
#if ENABLE_MARSHAL_METHODS
477+
// Temporary hack, see comments in RegisterNativeMembers below
478+
static readonly Dictionary<string, string[]> dynamicRegistrationMethods = new Dictionary<string, string[]> (StringComparer.Ordinal) {
479+
{"Android.Views.View+IOnLayoutChangeListenerImplementor", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }},
480+
{"Android.Views.View+IOnLayoutChangeListenerInvoker", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }},
481+
{"Java.Interop.TypeManager+JavaTypeManager", new string[] { "GetActivateHandler" }},
482+
};
483+
#endif
484+
476485
public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<char> methods)
477486
{
487+
#if ENABLE_MARSHAL_METHODS
488+
Logger.Log (LogLevel.Info, "monodroid-mm", $"RegisterNativeMembers ('{nativeClass?.Name}', '{type?.FullName}', '{methods.ToString ()}')");
489+
Logger.Log (LogLevel.Info, "monodroid-mm", "RegisterNativeMembers called from:");
490+
var st = new StackTrace (true);
491+
Logger.Log (LogLevel.Info, "monodroid-mm", st.ToString ());
492+
493+
if (methods.IsEmpty) {
494+
Logger.Log (LogLevel.Info, "monodroid-mm", "No methods to register, returning");
495+
return;
496+
}
497+
#endif
478498
try {
479499
if (FastRegisterNativeMembers (nativeClass, type, methods))
480500
return;
@@ -497,6 +517,9 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<
497517
MethodInfo []? typeMethods = null;
498518

499519
ReadOnlySpan<char> methodsSpan = methods;
520+
#if ENABLE_MARSHAL_METHODS
521+
bool needToRegisterNatives = false;
522+
#endif
500523
while (!methodsSpan.IsEmpty) {
501524
int newLineIndex = methodsSpan.IndexOf ('\n');
502525

@@ -508,7 +531,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<
508531
out ReadOnlySpan<char> callbackString,
509532
out ReadOnlySpan<char> callbackDeclaringTypeString);
510533

511-
Delegate callback;
534+
Delegate? callback = null;
512535
if (callbackString.SequenceEqual ("__export__")) {
513536
var mname = name.Slice (2);
514537
MethodInfo? minfo = null;
@@ -522,6 +545,9 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<
522545
if (minfo == null)
523546
throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname.ToString (), signature.ToString ()));
524547
callback = CreateDynamicCallback (minfo);
548+
#if ENABLE_MARSHAL_METHODS
549+
needToRegisterNatives = true;
550+
#endif
525551
} else {
526552
Type callbackDeclaringType = type;
527553
if (!callbackDeclaringTypeString.IsEmpty) {
@@ -530,20 +556,73 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<
530556
while (callbackDeclaringType.ContainsGenericParameters) {
531557
callbackDeclaringType = callbackDeclaringType.BaseType!;
532558
}
533-
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler),
534-
callbackDeclaringType, callbackString.ToString ());
535-
callback = connector ();
559+
#if ENABLE_MARSHAL_METHODS
560+
// TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which
561+
// aren't registered with [Register] but are baked into Mono.Android's managed and Java code)
562+
bool createCallback = false;
563+
string declaringTypeName = callbackDeclaringType.FullName;
564+
string callbackName = callbackString.ToString ();
565+
566+
foreach (var kvp in dynamicRegistrationMethods) {
567+
string dynamicTypeName = kvp.Key;
568+
569+
foreach (string dynamicCallbackMethodName in kvp.Value) {
570+
if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) {
571+
createCallback = true;
572+
break;
573+
}
574+
}
575+
576+
if (createCallback) {
577+
break;
578+
}
579+
}
580+
581+
if (createCallback) {
582+
Logger.Log (LogLevel.Info, "monodroid-mm", $" creating delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}");
583+
#endif
584+
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler),
585+
callbackDeclaringType, callbackString.ToString ());
586+
callback = connector ();
587+
#if ENABLE_MARSHAL_METHODS
588+
} else {
589+
Logger.Log (LogLevel.Warn, "monodroid-mm", $" would try to create delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}");
590+
}
591+
#endif
592+
}
593+
594+
if (callback != null) {
595+
#if ENABLE_MARSHAL_METHODS
596+
needToRegisterNatives = true;
597+
#endif
598+
natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback);
536599
}
537-
natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback);
538600
}
539601

540602
methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default;
541603
}
542604

543-
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, natives.Length);
605+
#if ENABLE_MARSHAL_METHODS
606+
if (needToRegisterNatives) {
607+
#endif
608+
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex);
609+
#if ENABLE_MARSHAL_METHODS
610+
}
611+
#endif
544612
} catch (Exception e) {
545613
JniEnvironment.Runtime.RaisePendingException (e);
546614
}
615+
616+
#if ENABLE_MARSHAL_METHODS
617+
bool ShouldRegisterDynamically (string callbackTypeName, string callbackString, string typeName, string callbackName)
618+
{
619+
if (String.Compare (typeName, callbackTypeName, StringComparison.Ordinal) != 0) {
620+
return false;
621+
}
622+
623+
return String.Compare (callbackName, callbackString, StringComparison.Ordinal) == 0;
624+
}
625+
#endif
547626
}
548627

549628
static int CountMethods (ReadOnlySpan<char> methodsSpan)

src/Mono.Android/Mono.Android.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
<JavaCallableWrapperAbsAssembly>$([System.IO.Path]::GetFullPath ('$(OutputPath)$(AssemblyName).dll'))</JavaCallableWrapperAbsAssembly>
5050
</PropertyGroup>
5151

52+
<PropertyGroup Condition=" '$(_EnableMarshalMethods)' == 'YesPlease' ">
53+
<DefineConstants>$(DefineConstants);ENABLE_MARSHAL_METHODS</DefineConstants>
54+
</PropertyGroup>
55+
5256
<ItemGroup Condition=" '$(TargetFramework)' == 'monoandroid10' ">
5357
<Reference Include="mscorlib">
5458
<HintPath>$(OutputPath)..\v1.0\mscorlib.dll</HintPath>

src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs

+22-1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ void Run (DirectoryAssemblyResolver res)
217217

218218
#if ENABLE_MARSHAL_METHODS
219219
if (!Debug) {
220+
// TODO: we must rewrite assemblies for all SupportedAbis. Alternatively, we need to copy the ones that are identical
221+
// Cecil does **not** guarantee that the same assembly modified twice in the same will yield the same result - tokens may differ, so can
222+
// MVID.
220223
var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, marshalMethodsAssemblyPaths, Log);
221224
rewriter.Rewrite (res);
222225
}
@@ -349,6 +352,11 @@ void Run (DirectoryAssemblyResolver res)
349352
regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first.");
350353
foreach (var type in javaTypes) {
351354
if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) {
355+
#if ENABLE_MARSHAL_METHODS
356+
if (!classifier.FoundDynamicallyRegisteredMethods (type)) {
357+
continue;
358+
}
359+
#endif
352360
string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.');
353361
regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);",
354362
type.GetAssemblyQualifiedName (cache), javaKey);
@@ -362,12 +370,25 @@ void Run (DirectoryAssemblyResolver res)
362370
template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()));
363371

364372
#if ENABLE_MARSHAL_METHODS
373+
if (!Debug) {
374+
Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}");
375+
376+
if (classifier.RejectedMethodCount > 0) {
377+
Log.LogWarning ($"Number of methods in the project that will be registered dynamically: {classifier.RejectedMethodCount}");
378+
}
379+
380+
if (classifier.WrappedMethodCount > 0) {
381+
Log.LogWarning ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}");
382+
}
383+
}
384+
365385
void StoreMarshalAssemblyPath (string name, ITaskItem asm)
366386
{
367387
if (Debug) {
368388
return;
369389
}
370390

391+
// TODO: we need to keep paths to ALL the assemblies, we need to rewrite them for all RIDs eventually. Right now we rewrite them just for one RID
371392
if (!marshalMethodsAssemblyPaths.TryGetValue (name, out HashSet<string> assemblyPaths)) {
372393
assemblyPaths = new HashSet<string> ();
373394
marshalMethodsAssemblyPaths.Add (name, assemblyPaths);
@@ -406,7 +427,7 @@ bool CreateJavaSources (IEnumerable<TypeDefinition> javaTypes, TypeDefinitionCac
406427
jti.Generate (writer);
407428
#if ENABLE_MARSHAL_METHODS
408429
if (!Debug) {
409-
if (classifier.FoundDynamicallyRegisteredMethods) {
430+
if (classifier.FoundDynamicallyRegisteredMethods (t)) {
410431
Log.LogWarning ($"Type '{t.GetAssemblyQualifiedName ()}' will register some of its Java override methods dynamically. This may adversely affect runtime performance. See preceding warnings for names of dynamically registered methods.");
411432
}
412433
}

src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -444,11 +444,12 @@ void AddEnvironment ()
444444
#if ENABLE_MARSHAL_METHODS
445445
var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<MarshalMethodsState> (GenerateJavaStubs.MarshalMethodsRegisterTaskKey, RegisteredTaskObjectLifetime.Build);
446446

447-
var marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator {
448-
NumberOfAssembliesInApk = assemblyCount,
449-
UniqueAssemblyNames = uniqueAssemblyNames,
450-
MarshalMethods = marshalMethodsState?.MarshalMethods,
451-
};
447+
var marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (
448+
assemblyCount,
449+
uniqueAssemblyNames,
450+
marshalMethodsState?.MarshalMethods,
451+
Log
452+
);
452453
marshalMethodsAsmGen.Init ();
453454
#endif
454455
foreach (string abi in SupportedAbis) {

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto
9191
"System.Console.dll",
9292
"System.Private.CoreLib.dll",
9393
"System.Runtime.dll",
94+
"System.Runtime.InteropServices.dll",
9495
"System.Linq.dll",
9596
"UnnamedProject.dll",
9697
//NOTE: appeared in .NET 7.0.100-rc.1.22423.7

src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -588,10 +588,10 @@ public void WriteStructureArray<T> (StructureInfo<T> info, IList<StructureInstan
588588
WriteStructureArray<T> (info, instances, LlvmIrVariableOptions.Default, symbolName, writeFieldComment, initialComment);
589589
}
590590

591-
public void WriteArray (IList<string> values, string symbolName)
591+
public void WriteArray (IList<string> values, string symbolName, string? initialComment = null)
592592
{
593593
WriteEOL ();
594-
WriteEOL (symbolName);
594+
WriteEOL (initialComment ?? symbolName);
595595

596596
ulong arrayStringCounter = 0;
597597
var strings = new List<StringSymbolInfo> ();

0 commit comments

Comments
 (0)