diff --git a/Directory.Build.props b/Directory.Build.props index 29990936e86..fef6d096b52 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,10 +27,6 @@ true - - <_EnableMarshalMethods>NoThanks - - 13.1.99 +- [Introduction](#introduction) +- [Java <-> Managed interoperability overview](#java-managed-interoperability-overview) + - [Java Callable Wrappers (JCW)](#java-callable-wrappers-jcw) +- [Registration](#registration) + - [Dynamic registration](#dynamic-registration) + - [Dynamic Java Callable Wrappers registration code](#dynamic-java-callable-wrappers-registration-code) + - [Dynamic Registration call sequence](#dynamic-registration-call-sequence) + - [Marshal methods](#marshal-methods) + - [Marshal Methods Java Callable Wrappers registration code](#marshal-methods-java-callable-wrappers-registration-code) + - [Marshal methods C# source code](#marshal-methods-c-source-code) + - [JNI requirements](#jni-requirements) + - [LLVM IR code generation](#llvm-ir-code-generation) + - [Assembly rewriting](#assembly-rewriting) + - [Wrappers for methods with non-blittable types](#wrappers-for-methods-with-non-blittable-types) + - [UnmanagedCallersOnly attribute](#unmanagedcallersonly-attribute) + - [Marshal Methods Registration call sequence](#marshal-methods-registration-call-sequence) + + +# Introduction + +At the core of `.NET Android` is its ability to interoperate with +the Java/Kotlin APIs implemented in the Android system. To make it +work, it is necessary to "bridge" the two separate worlds of Java VM +(`ART` in the Android OS) and the Managed VM (`MonoVM`). Application +developers expect to be able to call native Android APIs and receive +calls (or react to events) from the Android side using code written in +one of the .NET managed languages. To make it work, `.NET Android` +employs a number of techniques, both at build and at run time, which +are described in the sections below. + +This guide is meant to explain the technical implementation in a way +that is sufficient to understand the system without having to read the +actual source code. + +# Java <-> Managed interoperability overview + +Java VM and Managed VM are two entirely separate entities which +co-exist in the same process/application. Despite sharing the same +process resources, they don't "naturally" communicate with each other. +There is no direct way to call Java/Kotlin from .NET a'la the +`p/invoke` mechanism which allows calling native code APIs. Nor there +exists a way for Java/Kotlin code to invoke managed methods. To make +it possible, `.NET Android` takes advantage of the Java's [JNI](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html) +(`Java Native Interface`), a mechanism that allows native code +(.NET managed code being "native" in this context) to register +implementations of Java methods, written outside the Java VM and in +languages other than Java/Kotlin (for instance in `C`, `C++` or +`Rust`). + +Such methods need to be appropriately declared in the Java code, for +instance: + +```java +class MainActivity + extends androidx.appcompat.app.AppCompatActivity +{ + public void onCreate (android.os.Bundle p0) + { + n_onCreate (p0); + } + + private native void n_onCreate (android.os.Bundle p0); +} +``` + +Each native method is declared using the `native` keyword, and +whenever it is invoked from other Java code, the Java VM will use the +JNI to invoke the target method. + +Native methods can be registered either dynamically (by calling the +[`RegisterNatives`](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives) +JNI function) or "statically", by providing a native shared library +which exports a symbol with appropriate name which points to the +native function implementing the Java method. + +Both ways of registration are described in detail in the following +sections. + +## Java Callable Wrappers (JCW) + +`.NET Android` wraps the entire Android API by generating +appropriate C# code which mirrors the Java/Kotlin code (classes, +interfaces, methods, properties etc). Each generated class that +corresponds to a Java/Kotlin type, is derived from the +`Java.Lang.Object` class (implemented in the `Mono.Android` assembly), +which marks it as a "Java interoperable type", meaning that it can +implement or override virtual Java methods. To make registration and +invoking of such methods possible, it is necessary to generate a Java +class which mirrors the Managed one and provides an entry point to +the Java <-> Managed transition. The Java classes are generated +during application (as well as `.NET Android`) build and we call +them **Java Callable Wrappers** (or **JCW** for short). For instance, +the following managed class: + +```csharp +public class MainActivity : AppCompatActivity +{ + public override Android.Views.View? OnCreateView (Android.Views.View? parent, string name, Android.Content.Context context, Android.Util.IAttributeSet attrs) + { + return base.OnCreateView (parent, name, context, attrs); + } + + protected override void OnCreate (Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + DoSomething (savedInstanceState); + } + + void DoSomething (Bundle bundle) + { + // do something with the bundle + } +} +``` + +overrides two Java virtual methods found in the `AppCompatActivity` +type: `OnCreateView` and `OnCreate`. The `DoSomething` method does +not correspond to any method found in the base Java type, and thus it +won't be included in the JCW. + +The Java Callable Wrapper generated for the above class would look as +follows (a few generated methods not relevant to the discussion have +been omitted for brevity): + +```java +public class MainActivity + extends androidx.appcompat.app.AppCompatActivity +{ + public android.view.View onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3) + { + return n_onCreateView (p0, p1, p2, p3); + } + private native android.view.View n_onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3); + + public void onCreate (android.os.Bundle p0) + { + n_onCreate (p0); + } + private native void n_onCreate (android.os.Bundle p0); +} +``` + +Understanding the connection between Managed methods and their Java +counterparts is required in order to understand the registration +mechanisms described in sections found later in this document. The +[Dynamic registration](#dynamic-registration) section will expand on +this example in order to explain the details of how the Managed type +and its methods are registered with the Java VM. + +# Registration + +Both mechanisms of method registration rely on generation of [Java +Callable Wrappers](#java-callable-wrappers-jcw), with [Dynamic +registration](#dynamic-registration) requiring more code to be +generated so that the registration can be performed at the runtime. + +JCW are generated only for types that derive from +the `Java.Lang.Object` type. Finding such types is the task of the +Java.Interop's [`JavaTypeScanner`](../../external/Java.Interop/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaTypeScanner.cs), +which uses `Mono.Cecil` to read all the assemblies referenced by the +application and its libraries. The returned list of assemblies is +then used by a variety of tasks, JCW being only one +of them. + +After all types are found, +[`JavaCallableWrapperGenerator`](../../external/Java.Interop/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs) +is invoked in order to analyze each method in each type, looking for +those which override a virtual Java method and, thus, need to be +included in the wrapper class code. The generator optionally (if +[marshal methods](#marshal-methods) are enabled) passes each method to +an implementation of the +[`Java.Interop.Tools.JavaCallableWrappers.JavaCallableMethodClassifier`](../../external/Java.Interop/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs) +abstract class (which is +[`MarshalMethodsClassifier`](../../src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs) +in our case), to check whether the given method can be registered +statically. + +`JavaCallableWrapperGenerator` looks for methods decorated with the +`[Register]` attribute, which most frequently is created by invoking +its constructor with three parameters: + + 1. Java method name + 2. JNI method signature + 3. "Connector" method name + +The "connector" is a static method which creates a delegate that +subsequently allows calling of the native callback method: + +```csharp +public class MainActivity : AppCompatActivity +{ + // Connector backing field + static Delegate? cb_onCreate_Landroid_os_Bundle_; + + // Connector method + static Delegate GetOnCreate_Landroid_os_Bundle_Handler () + { + if (cb_onCreate_Landroid_os_Bundle_ == null) + cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_); + return cb_onCreate_Landroid_os_Bundle_; + } + + // Native callback + static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var savedInstanceState = global::Java.Lang.Object.GetObject (native_savedInstanceState, JniHandleOwnership.DoNotTransfer); + __this.OnCreate (savedInstanceState); + } + + // Target method + [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] + protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) + { + const string __id = "onCreate.(Landroid/os/Bundle;)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue ((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object) savedInstanceState).Handle); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + } finally { + global::System.GC.KeepAlive (savedInstanceState); + } + } +} +``` + +The above code is actually generated in the `Android.App.Activity` +class while .NET Android is built, from which our example +`MainActivity` eventually derives. + +What happens with the above code depends on the registration mechanism +and is described in the sections below. + +## Dynamic registration + +This registration mechanism has been used by `.NET Android` since +the beginning and it will remain in use for the foreseeable future +when the application is built in the `Debug` configuration or when +[Marshal Methods](#marshal-methods) are turned off. + +### Dynamic Java Callable Wrappers registration code + +Building on the C# example shown in the [Java Callable +Wrappers](#java-callable-wrappers-jcw) section, the following Java +code is generated (only the parts relevant to registration are shown): + +```java +public class MainActivity + extends androidx.appcompat.app.AppCompatActivity +{ +/** @hide */ + public static final String __md_methods; + static { + __md_methods = + "n_onCreateView:(Landroid/view/View;Ljava/lang/String;Landroid/content/Context;Landroid/util/AttributeSet;)Landroid/view/View;:GetOnCreateView_Landroid_view_View_Ljava_lang_String_Landroid_content_Context_Landroid_util_AttributeSet_Handler\n" + + "n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" + + ""; + mono.android.Runtime.register ("HelloAndroid.MainActivity, HelloAndroid", MainActivity.class, __md_methods); + } + + public android.view.View onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3) + { + return n_onCreateView (p0, p1, p2, p3); + } + + private native android.view.View n_onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3); + + public void onCreate (android.os.Bundle p0) + { + n_onCreate (p0); + } + + private native void n_onCreate (android.os.Bundle p0); +} +``` + +Code fragment which takes part in registration is the class's static +constructor. For each method registered for the type (that is, +implemented or overridden in the managed code), the JCW generator +outputs a single string which contains full information about the type +and method to register. Each such registration string is terminated +with the newline character and the entire sequence ends with an empty +string. Together, all the lines are concatenated and placed in the +`__md_methods` static variable. The `mono.android.Runtime.register` +method (see below for more details) is then invoked to register all +the methods. + +### Dynamic Registration call sequence + +All the "native" methods declared in the generated Java type are +registered when the type is constructed or accessed for the first +time. This is when the Java VM invokes the type's static constructor, +kicking off a sequence of calls that eventually ends with all the type +methods registered with JNI: + + 1. `mono.android.Runtime.register` is itself a native method, + declared in the + [`Runtime`](../../src/java-runtime/java/mono/android/Runtime.java) + class of .NET Android's Java runtime code, and implemented in + the native .NET Android + [runtime](../../src/monodroid/jni/monodroid-glue.cc) (the + `MonodroidRuntime::Java_mono_android_Runtime_register` method). + Purpose of this method is to prepare a call into the + .NET Android managed runtime code, the + [`Android.Runtime.JNIEnv::RegisterJniNatives`](../../src/Mono.Android/Android.Runtime/JNIEnv.cs) + method. + 2. `Android.Runtime.JNIEnv::RegisterJniNatives` is passed name of + the managed type for which to register Java methods and uses .NET + reflection to load that type, followed by a call to cache the + type (via `RegisterType` method in the + [`TypeManager`](../../src/Mono.Android/Java.Interop/TypeManager.cs) + class) to end with a call to the + `Android.Runtime.AndroidTypeManager::RegisterNativeMembers` + method. + 3. `Android.Runtime.AndroidTypeManager::RegisterNativeMembers` + eventually calls the + `Java.Interop.JniEnvironment.Types::RegisterNatives` method which + first generates a delegate to the native callback method, using + `System.Reflection.Emit` (via the + [`Android.Runtime.JNINativeWrapper::CreateDelegate`](../../src/Mono.Android/Android.Runtime/JNINativeWrapper.cs) + method) and, eventually, invokes Java JNI's `RegisterNatives` + function, finally registering the native methods for a managed + type. + +The `System.Reflection.Emit` sequence mentioned in 3. above is among +the most costly operations, repeated for each registered method. + +Some more information about Java type registration can be found +[here](https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration). + +## Marshal methods + +The goal of marshal methods is to completely bypass the [dynamic +registration sequence](#dynamic-registration-call-sequence), replacing +it with native code generated and compiled during application build, +thus saving on the startup time of the application. + +Marshal methods registration mechanism takes advantage of the JNI +ability to look up implementations of `native` Java methods in actual +native (shared) libraries. Such symbols must have names that follow a +set of rules, so that JNI is able to properly locate them (details are +explained in the [JNI Requirements](#jni-requirements) section below). + +To achieve that, the marshal methods mechanism uses a number of +classes which [generate native](#llvm-ir-code-generation) code and +[modify assemblies](#assembly-rewriting) that contain the registered +methods. + +Current implementation of the marshal methods classifier recognizes +the "standard" method registration pattern, using the example of the +`OnCreate` method shown in [Registration](#registration) above. + +The standard pattern consists of: + + * the "connector" method, `GetOnCreate_Landroid_os_Bundle_Handler` + above + * the delegate backing field, `cb_onCreate_Landroid_os_Bundle_` + above + * the native callback method, `n_OnCreate_Landroid_os_Bundle_` above + * and the virtual target method which dispatches the call to the + actual object, `OnCreate` above. + +Whenever the classifier's `ShouldBeDynamicallyRegistered` method is +called, it is passed not only the method's declaring type, but also +the `Register` attribute instance which it then uses to check whether +the method being registered conforms to the "standard" registration +pattern shown above. The connector, native callback methods as well +as the backing field must be private and static in order for the +registered method to be considered as a candidate for static +registration. + +Registered methods which don't follow the "standard" pattern will be +registered dynamically. + +### Marshal Methods Java Callable Wrappers registration code + +Building on the C# example show in the [Java Callable +Wrappers](#java-callable-wrappers-jcw) section, the following Java +code is generated (only the parts relevant to registration are shown): + +```java +public class MainActivity + extends androidx.appcompat.app.AppCompatActivity +{ + public android.view.View onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3) + { + return n_onCreateView (p0, p1, p2, p3); + } + + private native android.view.View n_onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3); + + public void onCreate (android.os.Bundle p0) + { + n_onCreate (p0); + } + + private native void n_onCreate (android.os.Bundle p0); +} +``` + +Note that, compared to the code generated for the [dynamic +registration](#dynamic-java-callable-wrappers-registration-code) +mechanism, there is no static constructor while the rest +of the code remains exactly the same. + +### Marshal methods C# source code + +The marshal methods sections below will all refer to this code +fragment: + + +```csharp +public class MainActivity : AppCompatActivity +{ + // Connector backing field + static Delegate? cb_onCreate_Landroid_os_Bundle_; + + // Connector method + static Delegate GetOnCreate_Landroid_os_Bundle_Handler () + { + if (cb_onCreate_Landroid_os_Bundle_ == null) + cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_); + return cb_onCreate_Landroid_os_Bundle_; + } + + // Native callback + static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var savedInstanceState = global::Java.Lang.Object.GetObject (native_savedInstanceState, JniHandleOwnership.DoNotTransfer); + __this.OnCreate (savedInstanceState); + } + + // Target method + [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] + protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) + { + const string __id = "onCreate.(Landroid/os/Bundle;)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue ((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object) savedInstanceState).Handle); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + } finally { + global::System.GC.KeepAlive (savedInstanceState); + } + } +} +``` + +### JNI requirements + +JNI +[specifies](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names) +a number of rules which govern how the native symbol name is +constructed, so that a mapping of object-oriented Java code (with its +package names/namespaces, class names and overloadable methods) into the essentially +"flat" procedural "namespace" of the lowest common denominator C code. + +The precise rules are outlined in the URL above, and their short +version is as follows: + + * Each symbol starts with the `Java_` prefix + * Next follows the mangled (see below) **fully qualified class + name** + * Next the `_` character serves as a separator before + * A mangled **method** name, which is optionally followed by + * Double underscore `__` and the mangled method argument signature + +"Mangling" is a way of encoding certain characters that are not +directly representable both in the source code and in the native +symbol name. The JNI specification allows for direct use of ASCII +letters (capital and lowercase) and digits, while all the other +characters are either represented by placeholders or encoded as 16-bit +hexadecimal Unicode character code (table copied from the JNI +specification for easier reference): + +| Escape sequence | Denotes | +|-----------------|------------------------------------------| +| _0XXXX | a Unicode character XXXX, all lower case | +| _1 | The `_` character | +| _2 | The `;` character in signatures | +| _3 | The `[` character in signatures | +| _ | The `.` or `/` characters | + +Generation of JNI symbol names is performed by the +[`MarshalMethodsNativeAssemblyGenerator`](../../src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs) +class while generating the native function source code. + +JNI supports two forms of the native symbol name, as signalled in the +bullet list above - a short and a long one. The former is looked up +first by the Java VM, followed by the latter. The latter needs to be +used only for overloaded methods, which is what our generator does. + +### LLVM IR code generation + +[`MarshalMethodsNativeAssemblyGenerator`](../../src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs) +uses the LLVM IR generator infrastructure to output both data and +executable code for all the marshal methods wrappers. It is not +necessary to understand the generated code unless one needs to modify +it, so this document only shows the equivalent C++ code which can +serve as a guide to understanding how the marshal method runtime +invocation works: + +```C++ +using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); + +static get_function_pointer_fn get_function_pointer; + +void xamarin_app_init (get_function_pointer_fn fn) noexcept +{ + get_function_pointer = fn; +} + +using android_app_activity_on_create_bundle_fn = void (*) (JNIEnv *env, jclass klass, jobject savedInstanceState); +static android_app_activity_on_create_bundle_fn android_app_activity_on_create_bundle = nullptr; + +extern "C" JNIEXPORT void +JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv *env, jclass klass, jobject savedInstanceState) noexcept +{ + if (android_app_activity_on_create_bundle == nullptr) { + get_function_pointer ( + 16, // mono image index + 0, // class index + 0x0600055B, // method token + reinterpret_cast(android_app_activity_on_create_bundle) // target pointer + ); + } + + android_app_activity_on_create_bundle (env, klass, savedInstanceState); +} +``` + +The `xamarin_app_init` function is output only once and is called by +the `.NET Android` runtime twice during application startup - once +to pass `get_function_pointer_fn` which does **not** use any locks (as +we know that until a certain point during startup we are in a single +lock, so no data access races can happen) and the other time just +before handing control over to the MonoVM, to pass pointer to +`get_function_pointer_fn` which **does** employ locking (since during +runtime it may very well happen that our generated Java native +functions will be called from different threads simultaneously). + +The `Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2` +function is a template which is repeated for each Java native +function, with each function having its own set of arguments and its +own callback backing field (`android_app_activity_on_create_bundle` +here). + +The `get_function_pointer` function takes as parameters indexes into a +couple of tables, one for `MonoImage*` pointers and the other for +`MonoClass*` pointers - both of which are generated by the +`MarshalMethodsNativeAssemblyGenerator` class at application build +time and allow for very fast lookup during run time. Target methods +are retrieved by their token value, within the specified `MonoImage*` +(essentially a pointer to managed assembly image in memory) and class. + +The method identified in such manner, **must** be decorated in the +managed code with the +[`[UnmanagedCallersOnly]`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0) +attribute (see [below](#unmanagedcallersonly-attribute) for more +details) so that it can be invoked directly, as if it was a native +method itself, with minimal managed marshaling overhead. + +### Assembly rewriting + +Please refer to the [C# code fragment](#marshal-methods-c-source-code) +above in order to understand this section. + +Managed assemblies (including `Mono.Android.dll`) which contain Java +types need to be usable in two contexts: with the "traditional" +dynamic registration and with marshal methods. Both of these +mechanisms, however, have different requirements. We cannot assume +that any assembly (either from .NET Android or a third party nuget) +will have "marshal methods friendly" code and thus we need to make +sure that the code meets our requirements. + +We do it by reading each relevant assembly and modifying it by +altering the definition of the native callbacks and removing the +code that's no longer used by marshal methods. This task is performed +by the +[`MarshalMethodsAssemblyRewriter`](../../src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs) +invoked during application build after all the assemblies are linked +but **before** type maps are generated (as rewriting **will** alter +the method and potentially type tokens) + +The exact modifications we apply are: + + * Removal of the **connector backing field** + * Removal of the **connector method** + * Generation of a **native callback wrapper** method, which catches + and propagates unhandled exceptions thrown by the native callback + or the target method. This method is decorated with the + `[UnmanagedCallersOnly]` attribute and called directly from the + native code. + * Optionally, generate code in the **native callback wrapper** to handle + [non-blittable types](#wrappers-for-methods-with-non-blittable-types). + +All the modifications are performed with `Mono.Cecil`. + +After modifications, the assembly contains equivalent of the following +C# code for each marshal method: + +```csharp +public class MainActivity : AppCompatActivity +{ + // Native callback + static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var savedInstanceState = global::Java.Lang.Object.GetObject (native_savedInstanceState, JniHandleOwnership.DoNotTransfer); + __this.OnCreate (savedInstanceState); + } + + // Native callback exception wrapper + [UnmanagedCallersOnly] + static void n_OnCreate_Landroid_os_Bundle__mm_wrapper (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + { + try { + n_OnCreate_Landroid_os_Bundle_ (jnienv, native__this, native_savedInstanceState) + } catch (Exception ex) { + Android.Runtime.AndroidEnvironmentInternal.UnhandledException (ex); + } + } + + // Target method + [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] + protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) + { + const string __id = "onCreate.(Landroid/os/Bundle;)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue ((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object) savedInstanceState).Handle); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + } finally { + global::System.GC.KeepAlive (savedInstanceState); + } + } +} +``` + +#### Wrappers for methods with non-blittable types + +The +[`[UnmanagedCallersOnly]`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0) +attribute requires that all the argument types as well as the method +return type are +[blittable](https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types). + +Among these types is one that's commonly used by the managed classes +implementing Java methods: `bool`. Currently this is the **only** +non-blittable type we've encountered in bindings, so at this point it +is the only one supported by the assembly rewriter. + +Whenever we encounter a method with a non-blittable type, we must +generate a wrapper for it, so that we can decorate it with the +`[UnmanagedCallersOnly]` attribute. This is easier and less error +prone than modifying the native callback method's IL stream to +implement the necessary conversion. + +An example of such method is +`Android.Views.View.IOnTouchListener::OnTouch`: + +```csharp +static bool n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_ (IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) +{ + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var v = global::Java.Lang.Object.GetObject (native_v, JniHandleOwnership.DoNotTransfer); + var e = global::Java.Lang.Object.GetObject (native_e, JniHandleOwnership.DoNotTransfer); + bool __ret = __this.OnTouch (v, e); + return __ret; +} +``` + +As it returns a `bool` value, it needs a wrapper to cast the return +value properly. Each wrapper method retains the native callback method +name, but appends the `_mm_wrapper` suffix to it: + +```csharp +static bool n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_ (IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) +{ + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var v = global::Java.Lang.Object.GetObject (native_v, JniHandleOwnership.DoNotTransfer); + var e = global::Java.Lang.Object.GetObject (native_e, JniHandleOwnership.DoNotTransfer); + bool __ret = __this.OnTouch (v, e); + return __ret; +} + +[UnmanagedCallersOnly] +static byte n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent__mm_wrapper (IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) +{ + try { + return n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_(jnienv, native__this, native_v, native_e) ? 1 : 0; + } catch (Exception ex) { + Android.Runtime.AndroidEnvironmentInternal.UnhandledException (ex); + return default; + } +} +``` + +The wrapper's return statement uses the ternary operator to "cast" the +boolean value to `1` (for `true`) or `0` (for `false`) because the +value of `bool` across the managed runtime can take a range of values: + + * `0` for `false` + * `-1` or `1` for `true` + * `!= 0` for true + +Since the `bool` type in C# can be 1, 2 or 4 bytes long, we need to +cast it to some type of a known and static size. The managed type +`byte` was chosen as it corresponds to the Java/JNI `jboolean` type, +defined as an unsigned 8-bit type. + +Whenever an **argument** value needs to be converted between `byte` and +`bool`, we generate code that is equivalent of the `argument != 0` +comparison, for instance for the +`Android.Views.View.IOnFocusChangeListener::OnFocusChange` method: + +```csharp +[UnmanagedCallersOnly] +static void n_OnFocusChange_Landroid_view_View_Z (IntPtr jnienv, IntPtr native__this, IntPtr native_v, byte hasFocus) +{ + n_OnFocusChange_Landroid_view_View_Z (jnienv, native__this, native_v, hasFocus != 0); +} + +static void n_OnFocusChange_Landroid_view_View_Z (IntPtr jnienv, IntPtr native__this, IntPtr native_v, bool hasFocus) +{ + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var v = global::Java.Lang.Object.GetObject (native_v, JniHandleOwnership.DoNotTransfer); + __this.OnFocusChange (v, hasFocus); +} +``` + +#### UnmanagedCallersOnly attribute + +Each marshal methods native callback method is decorated with the +[`[UnmanagedCallersOnly]`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0) +attribute, in order for us to be able to invoke the callback directly +from native code with minimal overhead compared to traditional +managed-from-native method calls (`mono_runtime_invoke`) + +### Marshal Methods Registration call sequence + +The sequence described in the [dynamic +registration sequence](#dynamic-registration-call-sequence) section +above is completely removed for the marshal methods. What remains +common for both dynamic and marshal methods registration, is the +resolution of the native function target done by the Java VM runtime. +In both cases the method declared in a Java class as `native` is +looked up by the Java VM when first JIT-ing the code. The difference +lies in the way this lookup is performed. + +Dynamic registration uses the +[`RegisterNatives`](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives) +JNI function at the runtime, which stores a pointer to the registered +method inside the structure which describes a Java class in the Java +VM. + +Marshal methods, however, don't register anything with the JNI, +instead they rely on the symbol lookup mechanism of the Java VM. +Whenever a call to `native` Java method is JIT-ed and it is not +registered previously using the `RegisterNatives` JNI function, Java +VM will proceed to look for symbols in the process runtime image (e.g. +using `dlopen` + `dlsym` calls on Unix) and, having found a matching +symbol, use pointer to it as the target of the `native` Java method +call. diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index d0f508ac92e..baba28b1734 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -92,6 +92,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Android", "src\Mono.An EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Android.Export", "src\Mono.Android.Export\Mono.Android.Export.csproj", "{B8105878-D423-4159-A3E7-028298281EC6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Android.Runtime", "src\Mono.Android.Runtime\Mono.Android.Runtime.csproj", "{43564FB3-0F79-4FF4-A2B0-B1637072FF01}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Android.Build.BaseTasks", "external\xamarin-android-tools\src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj", "{3DE17662-DCD6-4F49-AF06-D39AACC8649A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.AndroidSdk", "external\xamarin-android-tools\src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" @@ -296,6 +298,12 @@ Global {B8105878-D423-4159-A3E7-028298281EC6}.Debug|AnyCPU.Build.0 = Debug|Any CPU {B8105878-D423-4159-A3E7-028298281EC6}.Release|AnyCPU.ActiveCfg = Release|Any CPU {B8105878-D423-4159-A3E7-028298281EC6}.Release|AnyCPU.Build.0 = Release|Any CPU + + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|AnyCPU.Build.0 = Release|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|AnyCPU.Build.0 = Debug|Any CPU {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|AnyCPU.ActiveCfg = Release|Any CPU @@ -456,6 +464,7 @@ Global {73DF9E10-E933-4222-B8E1-F4536FFF9FAD} = {864062D3-A415-4A6F-9324-5820237BA058} {66CF299A-CE95-4131-BCD8-DB66E30C4BF7} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {B8105878-D423-4159-A3E7-028298281EC6} = {04E3E11E-B47D-4599-8AFC-50515A95E715} + {43564FB3-0F79-4FF4-A2B0-B1637072FF01} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {3DE17662-DCD6-4F49-AF06-D39AACC8649A} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {1E5501E8-49C1-4659-838D-CC9720C5208F} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483} diff --git a/build-tools/automation/yaml-templates/run-designer-tests.yml b/build-tools/automation/yaml-templates/run-designer-tests.yml index 796c181577f..f6a63f5adc6 100644 --- a/build-tools/automation/yaml-templates/run-designer-tests.yml +++ b/build-tools/automation/yaml-templates/run-designer-tests.yml @@ -62,7 +62,10 @@ steps: displayName: 'Copy binlogs' inputs: sourceFolder: ${{ parameters.designerSourcePath }}/Xamarin.Designer.Android - contents: '**/*.binlog' + contents: | + **/*.binlog + **/hs*.log + **/hs*.mdmp targetFolder: $(Build.ArtifactStagingDirectory)/designer-binlogs overWrite: true flattenFolders: true diff --git a/build-tools/create-packs/Microsoft.Android.Ref.proj b/build-tools/create-packs/Microsoft.Android.Ref.proj index a4d71c2decb..39d0b2e6c11 100644 --- a/build-tools/create-packs/Microsoft.Android.Ref.proj +++ b/build-tools/create-packs/Microsoft.Android.Ref.proj @@ -33,6 +33,7 @@ by projects that use the Microsoft.Android framework in .NET 6+. <_AndroidRefPackAssemblies Include="$(JavaInteropSourceDirectory)\bin\$(Configuration)-net7.0\ref\Java.Interop.dll" /> <_AndroidRefPackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)ref\Mono.Android.dll" /> + <_AndroidRefPackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)ref\Mono.Android.Runtime.dll" /> <_AndroidRefPackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\ref\Mono.Android.Export.dll" /> diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index aef96f9dfc8..8982eb6f911 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -35,6 +35,7 @@ projects that use the Microsoft.Android framework in .NET 6+. <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Java.Interop.dll" /> <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.dll" /> + <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.Runtime.dll" /> <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Mono.Android.Export.dll" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmono-android.debug.so" /> diff --git a/build-tools/scripts/JavaCallableWrappers.targets b/build-tools/scripts/JavaCallableWrappers.targets index 21d9bcd231c..bd0e0bbf117 100644 --- a/build-tools/scripts/JavaCallableWrappers.targets +++ b/build-tools/scripts/JavaCallableWrappers.targets @@ -23,7 +23,6 @@ /> <_JavaSources Include="$(IntermediateOutputPath)jcw\src\**\*.java" /> - <_JavaSources Include="$(JavaInteropSourceDirectory)\src\Java.Interop\java\com\xamarin\**\*.java" /> + + + diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml index 368771253da..1f27591ef8f 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml @@ -7,8 +7,10 @@ - + + + @@ -16,8 +18,6 @@ - - diff --git a/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs b/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs new file mode 100644 index 00000000000..198e6c92423 --- /dev/null +++ b/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs @@ -0,0 +1,18 @@ +using System; + +namespace Android.Runtime +{ + internal static class AndroidEnvironmentInternal + { + internal static Action? UnhandledExceptionHandler; + + internal static void UnhandledException (Exception e) + { + if (UnhandledExceptionHandler == null) { + return; + } + + UnhandledExceptionHandler (e); + } + } +} diff --git a/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj new file mode 100644 index 00000000000..62e90145971 --- /dev/null +++ b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj @@ -0,0 +1,62 @@ + + + + + + + + $(DotNetTargetFramework) + true + false + ..\..\product.snk + false + true + false + false + 10 + true + enable + true + true + + + $(NoWarn);CS0169;CS0414;CS0649 + + $(DefineConstants);INSIDE_MONO_ANDROID_RUNTIME;JAVA_INTEROP + + + + $(_MonoAndroidNETDefaultOutDir) + + + + + + + + + + + + + $(BuildDependsOn); + _CopyToPackDirs; + + + + + + + + + + + + + + + + + + + diff --git a/src/Mono.Android.Runtime/Mono.Android.Runtime.targets b/src/Mono.Android.Runtime/Mono.Android.Runtime.targets new file mode 100644 index 00000000000..0b00eed9b47 --- /dev/null +++ b/src/Mono.Android.Runtime/Mono.Android.Runtime.targets @@ -0,0 +1,52 @@ + + + + + + <_PackageVersion>$(ProductVersion) + <_PackageVersionBuild>$(XAVersionCommitCount) + + + <_PackageVersion>$(AndroidPackVersion) + <_PackageVersionBuild>$(PackVersionCommitCount) + + + + + + + + + + + + + diff --git a/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in b/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in new file mode 100644 index 00000000000..6c392e8130f --- /dev/null +++ b/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in @@ -0,0 +1,21 @@ +// +// AssemblyInfo.cs.in +// +// Authors: +// Jonathan Pryor (jonp@xamarin.com) +// +// Copyright 2014 Xamarin, Inc. +// Copyright 2016 Microsoft Corporation. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; + +[assembly: AssemblyInformationalVersion ("@PACKAGE_VERSION@.@PACKAGE_VERSION_BUILD@; git-rev-head:@PACKAGE_HEAD_REV@; git-branch:@PACKAGE_HEAD_BRANCH@")] +[assembly: AssemblyTitle ("Mono.Android.Runtime.dll")] +[assembly: AssemblyProduct ("Xamarin.Android")] +[assembly: AssemblyCompany ("Microsoft Corporation")] +[assembly: TargetPlatform("Android@API_LEVEL@.0")] +[assembly: SupportedOSPlatform("Android@MIN_API_LEVEL@.0")] + +[assembly: InternalsVisibleTo("Mono.Android, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] diff --git a/src/Mono.Android/Android.App/SyncContext.cs b/src/Mono.Android/Android.App/SyncContext.cs index ac0fe30093a..6c3daa83c7d 100644 --- a/src/Mono.Android/Android.App/SyncContext.cs +++ b/src/Mono.Android/Android.App/SyncContext.cs @@ -18,7 +18,7 @@ static bool EnsureLooper ([NotNullWhen (true)]Looper? looper, SendOrPostCallback { if (looper == null) { var message = $"No Android message loop is available. Skipping invocation of `{d.Method.DeclaringType?.FullName}.{d.Method.Name}`!"; - if (JNIEnv.IsRunningOnDesktop) + if (JNIEnvInit.IsRunningOnDesktop) message += " Using `await` when running on the Android Designer is not currently supported. Please use the `View.IsInEditMode` property."; Logger.Log (LogLevel.Error, "monodroid-synccontext", message); return false; diff --git a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs index ea5368a2f51..dda2dea521e 100644 --- a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs +++ b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs @@ -264,17 +264,14 @@ static void GetDisplayDPI (out float x_dpi, out float y_dpi) // static string GetDefaultTimeZone () { - IntPtr id = _monodroid_timezone_get_default_id (); + IntPtr id = RuntimeNativeMethods._monodroid_timezone_get_default_id (); try { return Marshal.PtrToStringAnsi (id)!; } finally { - JNIEnv.monodroid_free (id); + RuntimeNativeMethods.monodroid_free (id); } } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr _monodroid_timezone_get_default_id (); - // This is invoked by // mscorlib.dll!System.AndroidPlatform.GetDefaultSyncContext() // DO NOT REMOVE @@ -293,35 +290,26 @@ static string GetDefaultTimeZone () // These are invoked by // System.dll!System.AndroidPlatform.getifaddrs // DO NOT REMOVE - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_getifaddrs (out IntPtr ifap); - static int GetInterfaceAddresses (out IntPtr ifap) { - return _monodroid_getifaddrs (out ifap); + return RuntimeNativeMethods._monodroid_getifaddrs (out ifap); } // These are invoked by // System.dll!System.AndroidPlatform.freeifaddrs // DO NOT REMOVE - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern void _monodroid_freeifaddrs (IntPtr ifap); - static void FreeInterfaceAddresses (IntPtr ifap) { - _monodroid_freeifaddrs (ifap); + RuntimeNativeMethods._monodroid_freeifaddrs (ifap); } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern void _monodroid_detect_cpu_and_architecture (ref ushort built_for_cpu, ref ushort running_on_cpu, ref byte is64bit); - static void DetectCPUAndArchitecture (out ushort builtForCPU, out ushort runningOnCPU, out bool is64bit) { ushort built_for_cpu = 0; ushort running_on_cpu = 0; byte _is64bit = 0; - _monodroid_detect_cpu_and_architecture (ref built_for_cpu, ref running_on_cpu, ref _is64bit); + RuntimeNativeMethods._monodroid_detect_cpu_and_architecture (ref built_for_cpu, ref running_on_cpu, ref _is64bit); builtForCPU = built_for_cpu; runningOnCPU = running_on_cpu; is64bit = _is64bit != 0; diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 3bfffb9001b..fa98adf6337 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -17,7 +17,7 @@ namespace Android.Runtime { class AndroidRuntime : JniRuntime { - public const string InternalDllName = "xa-internal-api"; + public const string InternalDllName = RuntimeConstants.InternalDllName; internal AndroidRuntime (IntPtr jnienv, IntPtr vm, @@ -32,6 +32,11 @@ internal AndroidRuntime (IntPtr jnienv, classLoader_loadClass, jniAddNativeMethodRegistrationAttributePresent)) { +#if NETCOREAPP + // This is not ideal, but we need to set this while the runtime is initializing but we can't do it directly from the `JNIEnvInit.Initialize` method, since + // it lives in an assembly that does not reference Mono.Android. So we do it here, because this class is instantiated by JNIEnvInit.Initialize. + AndroidEnvironmentInternal.UnhandledExceptionHandler = AndroidEnvironment.UnhandledException; +#endif } public override void FailFast (string? message) @@ -54,7 +59,7 @@ public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool f { if (!reference.IsValid) return null; - var peeked = JNIEnv.AndroidValueManager?.PeekPeer (reference); + var peeked = JNIEnvInit.AndroidValueManager?.PeekPeer (reference); var peekedExc = peeked as Exception; if (peekedExc == null) { var throwable = Java.Lang.Object.GetObject (reference.Handle, JniHandleOwnership.DoNotTransfer); @@ -62,7 +67,7 @@ public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool f return throwable; } JniObjectReference.Dispose (ref reference, options); - var unwrapped = JNIEnv.AndroidValueManager?.UnboxException (peeked!); + var unwrapped = JNIEnvInit.AndroidValueManager?.UnboxException (peeked!); if (unwrapped != null) { return unwrapped; } @@ -102,20 +107,12 @@ public AndroidRuntimeOptions (IntPtr jnienv, } class AndroidObjectReferenceManager : JniRuntime.JniObjectReferenceManager { - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_gref_get (); - public override int GlobalReferenceCount { - get {return _monodroid_gref_get ();} + get {return RuntimeNativeMethods._monodroid_gref_get ();} } - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_weak_gref_get (); - public override int WeakGlobalReferenceCount { - get {return _monodroid_weak_gref_get ();} + get {return RuntimeNativeMethods._monodroid_weak_gref_get ();} } public override JniObjectReference CreateLocalReference (JniObjectReference value, ref int localReferenceCount) @@ -126,7 +123,7 @@ public override JniObjectReference CreateLocalReference (JniObjectReference valu var tname = Thread.CurrentThread.Name; var tid = Thread.CurrentThread.ManagedThreadId;; var from = new StringBuilder (new StackTrace (true).ToString ()); - JNIEnv._monodroid_lref_log_new (localReferenceCount, r.Handle, (byte) 'L', tname, tid, from, 1); + RuntimeNativeMethods._monodroid_lref_log_new (localReferenceCount, r.Handle, (byte) 'L', tname, tid, from, 1); } return r; @@ -138,7 +135,7 @@ public override void DeleteLocalReference (ref JniObjectReference value, ref int var tname = Thread.CurrentThread.Name; var tid = Thread.CurrentThread.ManagedThreadId;; var from = new StringBuilder (new StackTrace (true).ToString ()); - JNIEnv._monodroid_lref_log_delete (localReferenceCount-1, value.Handle, (byte) 'L', tname, tid, from, 1); + RuntimeNativeMethods._monodroid_lref_log_delete (localReferenceCount-1, value.Handle, (byte) 'L', tname, tid, from, 1); } base.DeleteLocalReference (ref value, ref localReferenceCount); } @@ -150,7 +147,7 @@ public override void CreatedLocalReference (JniObjectReference value, ref int lo var tname = Thread.CurrentThread.Name; var tid = Thread.CurrentThread.ManagedThreadId;; var from = new StringBuilder (new StackTrace (true).ToString ()); - JNIEnv._monodroid_lref_log_new (localReferenceCount, value.Handle, (byte) 'L', tname, tid, from, 1); + RuntimeNativeMethods._monodroid_lref_log_new (localReferenceCount, value.Handle, (byte) 'L', tname, tid, from, 1); } } @@ -161,14 +158,14 @@ public override IntPtr ReleaseLocalReference (ref JniObjectReference value, ref var tname = Thread.CurrentThread.Name; var tid = Thread.CurrentThread.ManagedThreadId;; var from = new StringBuilder (new StackTrace (true).ToString ()); - JNIEnv._monodroid_lref_log_delete (localReferenceCount-1, value.Handle, (byte) 'L', tname, tid, from, 1); + RuntimeNativeMethods._monodroid_lref_log_delete (localReferenceCount-1, value.Handle, (byte) 'L', tname, tid, from, 1); } return r; } public override void WriteGlobalReferenceLine (string format, params object?[] args) { - JNIEnv._monodroid_gref_log (string.Format (format, args)); + RuntimeNativeMethods._monodroid_gref_log (string.Format (format, args)); } public override JniObjectReference CreateGlobalReference (JniObjectReference value) @@ -181,8 +178,8 @@ public override JniObjectReference CreateGlobalReference (JniObjectReference val var tname = log ? Thread.CurrentThread.Name : null; var tid = log ? Thread.CurrentThread.ManagedThreadId : 0; var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null; - int gc = JNIEnv._monodroid_gref_log_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1); - if (gc >= JNIEnv.gref_gc_threshold) { + int gc = RuntimeNativeMethods._monodroid_gref_log_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1); + if (gc >= JNIEnvInit.gref_gc_threshold) { Logger.Log (LogLevel.Info, "monodroid-gc", gc + " outstanding GREFs. Performing a full GC!"); System.GC.Collect (); } @@ -208,7 +205,7 @@ public override void DeleteGlobalReference (ref JniObjectReference value) var tname = log ? Thread.CurrentThread.Name : null; var tid = log ? Thread.CurrentThread.ManagedThreadId : 0; var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null; - JNIEnv._monodroid_gref_log_delete (value.Handle, ctype, tname, tid, from, 1); + RuntimeNativeMethods._monodroid_gref_log_delete (value.Handle, ctype, tname, tid, from, 1); base.DeleteGlobalReference (ref value); } @@ -223,7 +220,7 @@ public override JniObjectReference CreateWeakGlobalReference (JniObjectReference var tname = log ? Thread.CurrentThread.Name : null; var tid = log ? Thread.CurrentThread.ManagedThreadId : 0; var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null; - JNIEnv._monodroid_weak_gref_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1); + RuntimeNativeMethods._monodroid_weak_gref_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1); return r; } @@ -235,7 +232,7 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value) var tname = log ? Thread.CurrentThread.Name : null; var tid = log ? Thread.CurrentThread.ManagedThreadId : 0; var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null; - JNIEnv._monodroid_weak_gref_delete (value.Handle, ctype, tname, tid, from, 1); + RuntimeNativeMethods._monodroid_weak_gref_delete (value.Handle, ctype, tname, tid, from, 1); base.DeleteWeakGlobalReference (ref value); } @@ -276,7 +273,7 @@ protected override IEnumerable GetTypesForSimpleReference (string jniSimpl #endif // NET j; } - if (JNIEnv.IsRunningOnDesktop) { + if (JNIEnvInit.IsRunningOnDesktop) { return JavaNativeTypeManager.ToJniName (type); } return null; @@ -288,7 +285,7 @@ protected override IEnumerable GetSimpleReferences (Type type) #if NET j = GetReplacementTypeCore (j) ?? j; #endif // NET - if (JNIEnv.IsRunningOnDesktop) { + if (JNIEnvInit.IsRunningOnDesktop) { string? d = JavaNativeTypeManager.ToJniName (type); if (j != null && d != null) { return new[]{j, d}; @@ -323,16 +320,13 @@ protected override IEnumerable GetSimpleReferences (Type type) }; } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr _monodroid_lookup_replacement_type (string jniSimpleReference); - protected override string? GetReplacementTypeCore (string jniSimpleReference) { - if (!JNIEnv.jniRemappingInUse) { + if (!JNIEnvInit.jniRemappingInUse) { return null; } - IntPtr ret = _monodroid_lookup_replacement_type (jniSimpleReference); + IntPtr ret = RuntimeNativeMethods._monodroid_lookup_replacement_type (jniSimpleReference); if (ret == IntPtr.Zero) { return null; } @@ -340,16 +334,13 @@ protected override IEnumerable GetSimpleReferences (Type type) return Marshal.PtrToStringAnsi (ret); } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr _monodroid_lookup_replacement_method_info (string jniSourceType, string jniMethodName, string jniMethodSignature); - protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature) { - if (!JNIEnv.jniRemappingInUse) { + if (!JNIEnvInit.jniRemappingInUse) { return null; } - IntPtr retInfo = _monodroid_lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature); + IntPtr retInfo = RuntimeNativeMethods._monodroid_lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature); if (retInfo == IntPtr.Zero) { return null; } @@ -473,42 +464,22 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu public override void RegisterNativeMembers (JniType nativeClass, Type type, string? methods) => RegisterNativeMembers (nativeClass, type, methods.AsSpan ()); -#if ENABLE_MARSHAL_METHODS - // Temporary hack, see comments in RegisterNativeMembers below - static readonly Dictionary dynamicRegistrationMethods = new Dictionary (StringComparer.Ordinal) { - {"Android.Views.View+IOnLayoutChangeListenerImplementor", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, - {"Android.Views.View+IOnLayoutChangeListenerInvoker", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, - {"Java.Interop.TypeManager+JavaTypeManager", new string[] { "GetActivateHandler" }}, - }; -#endif - public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) { -#if ENABLE_MARSHAL_METHODS - Logger.Log (LogLevel.Info, "monodroid-mm", $"RegisterNativeMembers ('{nativeClass?.Name}', '{type?.FullName}', '{methods.ToString ()}')"); - Logger.Log (LogLevel.Info, "monodroid-mm", "RegisterNativeMembers called from:"); - var st = new StackTrace (true); - Logger.Log (LogLevel.Info, "monodroid-mm", st.ToString ()); - - if (methods.IsEmpty) { - Logger.Log (LogLevel.Info, "monodroid-mm", "No methods to register, returning"); - return; - } -#endif try { - if (FastRegisterNativeMembers (nativeClass, type, methods)) - return; - if (methods.IsEmpty) { if (jniAddNativeMethodRegistrationAttributePresent) base.RegisterNativeMembers (nativeClass, type, methods.ToString ()); return; + } else if (FastRegisterNativeMembers (nativeClass, type, methods)) { + return; } int methodCount = CountMethods (methods); if (methodCount < 1) { - if (jniAddNativeMethodRegistrationAttributePresent) + if (jniAddNativeMethodRegistrationAttributePresent) { base.RegisterNativeMembers (nativeClass, type, methods.ToString ()); + } return; } @@ -517,9 +488,8 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< MethodInfo []? typeMethods = null; ReadOnlySpan methodsSpan = methods; -#if ENABLE_MARSHAL_METHODS bool needToRegisterNatives = false; -#endif + while (!methodsSpan.IsEmpty) { int newLineIndex = methodsSpan.IndexOf ('\n'); @@ -545,9 +515,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< if (minfo == null) throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname.ToString (), signature.ToString ())); callback = CreateDynamicCallback (minfo); -#if ENABLE_MARSHAL_METHODS needToRegisterNatives = true; -#endif } else { Type callbackDeclaringType = type; if (!callbackDeclaringTypeString.IsEmpty) { @@ -556,45 +524,14 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< while (callbackDeclaringType.ContainsGenericParameters) { callbackDeclaringType = callbackDeclaringType.BaseType!; } -#if ENABLE_MARSHAL_METHODS - // TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which - // aren't registered with [Register] but are baked into Mono.Android's managed and Java code) - bool createCallback = false; - string declaringTypeName = callbackDeclaringType.FullName; - string callbackName = callbackString.ToString (); - - foreach (var kvp in dynamicRegistrationMethods) { - string dynamicTypeName = kvp.Key; - - foreach (string dynamicCallbackMethodName in kvp.Value) { - if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) { - createCallback = true; - break; - } - } - if (createCallback) { - break; - } - } - - if (createCallback) { - Logger.Log (LogLevel.Info, "monodroid-mm", $" creating delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}"); -#endif - GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), - callbackDeclaringType, callbackString.ToString ()); - callback = connector (); -#if ENABLE_MARSHAL_METHODS - } else { - Logger.Log (LogLevel.Warn, "monodroid-mm", $" would try to create delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}"); - } -#endif + GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), + callbackDeclaringType, callbackString.ToString ()); + callback = connector (); } if (callback != null) { -#if ENABLE_MARSHAL_METHODS needToRegisterNatives = true; -#endif natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); } } @@ -602,18 +539,13 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default; } -#if ENABLE_MARSHAL_METHODS if (needToRegisterNatives) { -#endif JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex); -#if ENABLE_MARSHAL_METHODS } -#endif } catch (Exception e) { JniEnvironment.Runtime.RaisePendingException (e); } -#if ENABLE_MARSHAL_METHODS bool ShouldRegisterDynamically (string callbackTypeName, string callbackString, string typeName, string callbackName) { if (String.Compare (typeName, callbackTypeName, StringComparison.Ordinal) != 0) { @@ -622,7 +554,6 @@ bool ShouldRegisterDynamically (string callbackTypeName, string callbackString, return String.Compare (callbackName, callbackString, StringComparison.Ordinal) == 0; } -#endif } static int CountMethods (ReadOnlySpan methodsSpan) @@ -665,7 +596,7 @@ class AndroidValueManager : JniRuntime.JniValueManager { public override void WaitForGCBridgeProcessing () { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); } public override IJavaPeerable? CreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type? targetType) @@ -686,7 +617,7 @@ public override void AddPeer (IJavaPeerable value) throw new ArgumentException ("Must have a valid JNI object reference!", nameof (value)); var reference = value.PeerReference; - var hash = JNIEnv.IdentityHash! (reference.Handle); + var hash = JNIEnv.IdentityHash (reference.Handle); AddPeer (value, reference, hash); } @@ -749,14 +680,14 @@ internal void AddPeer (IJavaPeerable value, IntPtr handle, JniHandleOwnership tr if (handleField == IntPtr.Zero) throw new InvalidOperationException ("Unable to allocate Global Reference for object '" + value.ToString () + "'!"); - IntPtr hash = JNIEnv.IdentityHash! (handleField); + IntPtr hash = JNIEnv.IdentityHash (handleField); value.SetJniIdentityHashCode ((int) hash); if ((transfer & JniHandleOwnership.DoNotRegister) == 0) { AddPeer (value, new JniObjectReference (handleField, JniObjectReferenceType.Global), hash); } if (Logger.LogGlobalRef) { - JNIEnv._monodroid_gref_log ("handle 0x" + handleField.ToString ("x") + + RuntimeNativeMethods._monodroid_gref_log ("handle 0x" + handleField.ToString ("x") + "; key_handle 0x" + hash.ToString ("x") + ": Java Type: `" + JNIEnv.GetClassNameFromInstance (handleField) + "`; " + "MCW type: `" + value.GetType ().FullName + "`\n"); @@ -807,7 +738,7 @@ public override void RemovePeer (IJavaPeerable value) // Likely an idempotent DIspose(); ignore. return; } - var hash = JNIEnv.IdentityHash! (reference.Handle); + var hash = JNIEnv.IdentityHash (reference.Handle); RemovePeer (value, hash); } @@ -841,7 +772,7 @@ internal void RemovePeer (IJavaPeerable value, IntPtr hash) if (!reference.IsValid) return null; - var hash = JNIEnv.IdentityHash! (reference.Handle); + var hash = JNIEnv.IdentityHash (reference.Handle); lock (instances) { if (instances.TryGetValue (hash, out var targets)) { for (int i = targets.Count - 1; i >= 0; i--) { @@ -894,7 +825,7 @@ public override void FinalizePeer (IJavaPeerable value) throw new ArgumentNullException (nameof (value)); if (Logger.LogGlobalRef) { - JNIEnv._monodroid_gref_log ($"Finalizing handle {value.PeerReference}\n"); + RuntimeNativeMethods._monodroid_gref_log ($"Finalizing handle {value.PeerReference}\n"); } // FIXME: need hash cleanup mechanism. diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs new file mode 100644 index 00000000000..37df6ef353a --- /dev/null +++ b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs @@ -0,0 +1,41 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME +using System; +using System.Reflection; + +namespace Android.Runtime +{ + public static class AndroidRuntimeInternal + { + internal static MethodInfo? mono_unhandled_exception_method = null; +#if NETCOREAPP + internal static Action mono_unhandled_exception = RuntimeNativeMethods.monodroid_debugger_unhandled_exception; +#else + internal static Action? mono_unhandled_exception = null; +#endif + +#pragma warning disable CS0649 // Field is never assigned to. This field is assigned from monodroid-glue.cc. + internal static volatile bool BridgeProcessing; // = false +#pragma warning restore CS0649 // Field is never assigned to. + + internal static void InitializeUnhandledExceptionMethod () + { + if (mono_unhandled_exception == null) { + mono_unhandled_exception_method = typeof (System.Diagnostics.Debugger) + .GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); + if (mono_unhandled_exception_method != null) + mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), mono_unhandled_exception_method); + } + if (mono_unhandled_exception_method == null && mono_unhandled_exception != null) { + mono_unhandled_exception_method = mono_unhandled_exception.Method; + } + } + + public static void WaitForBridgeProcessing () + { + if (!BridgeProcessing) + return; + RuntimeNativeMethods._monodroid_gc_wait_for_bridge_processing (); + } + } +} +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/BoundExceptionType.cs b/src/Mono.Android/Android.Runtime/BoundExceptionType.cs index 442f05ea7a1..f760ef89530 100644 --- a/src/Mono.Android/Android.Runtime/BoundExceptionType.cs +++ b/src/Mono.Android/Android.Runtime/BoundExceptionType.cs @@ -1,7 +1,7 @@ namespace Android.Runtime { // Keep the enum values in sync with those in src/monodroid/jni/monodroid-glue-internal.hh - enum BoundExceptionType : byte + internal enum BoundExceptionType : byte { System = 0x00, Java = 0x01, diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index d9ef52d1255..46c8d75313c 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -14,74 +14,15 @@ using System.Diagnostics.CodeAnalysis; namespace Android.Runtime { -#pragma warning disable 0649 - struct JnienvInitializeArgs { - public IntPtr javaVm; - public IntPtr env; - public IntPtr grefLoader; - public IntPtr Loader_loadClass; - public IntPtr grefClass; - public IntPtr Class_forName; - public uint logCategories; - public int version; - public int androidSdkVersion; - public int localRefsAreIndirect; - public int grefGcThreshold; - public IntPtr grefIGCUserPeer; - public int isRunningOnDesktop; - public byte brokenExceptionTransitions; - public int packageNamingPolicy; - public byte ioExceptionType; - public int jniAddNativeMethodRegistrationAttributePresent; - public bool jniRemappingInUse; - } -#pragma warning restore 0649 - public static partial class JNIEnv { - static IntPtr java_class_loader; - static IntPtr java_vm; - static IntPtr load_class_id; - static IntPtr gref_class; - static JniMethodInfo? mid_Class_forName; - static int version; - static int androidSdkVersion; - - static bool AllocObjectSupported; - internal static bool jniRemappingInUse; - - static IntPtr grefIGCUserPeer_class; - - internal static int gref_gc_threshold; - - internal static bool PropagateExceptions; - - internal static bool IsRunningOnDesktop; - internal static bool LogAssemblyCategory; - - static AndroidRuntime? androidRuntime; - static BoundExceptionType BoundExceptionType; - [ThreadStatic] static byte[]? mvid_bytes; - internal static AndroidValueManager? AndroidValueManager; + public static IntPtr Handle => JniEnvironment.EnvironmentPointer; - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal extern static void monodroid_log (LogLevel level, LogCategories category, string message); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal extern static IntPtr monodroid_timing_start (string? message); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal extern static void monodroid_timing_stop (IntPtr sequence, string? message); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal extern static void monodroid_free (IntPtr ptr); - - public static IntPtr Handle { - get { - return JniEnvironment.EnvironmentPointer; - } + internal static IntPtr IdentityHash (IntPtr v) + { + return JNIEnvInit.LocalRefsAreIndirect ? RuntimeNativeMethods._monodroid_get_identity_hash_code (Handle, v) : v; } public static void CheckHandle (IntPtr jnienv) @@ -94,23 +35,25 @@ internal static bool IsGCUserPeer (IntPtr value) if (value == IntPtr.Zero) return false; - return IsInstanceOf (value, grefIGCUserPeer_class); + return IsInstanceOf (value, JNIEnvInit.grefIGCUserPeer_class); } internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMemberName] string? caller = null) { if (t == null) { - monodroid_log (LogLevel.Warn, + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"ShouldWrapJavaException was not passed a valid `Java.Lang.Throwable` instance. Called from method `{caller}`"); return false; } - bool wrap = BoundExceptionType == BoundExceptionType.System; + bool wrap = JNIEnvInit.BoundExceptionType == BoundExceptionType.System; if (!wrap) { - monodroid_log (LogLevel.Warn, - LogCategories.Default, - $"Not wrapping exception of type {t.GetType().FullName} from method `{caller}`. This will change in a future release."); + RuntimeNativeMethods.monodroid_log ( + LogLevel.Warn, + LogCategories.Default, + $"Not wrapping exception of type {t.GetType().FullName} from method `{caller}`. This will change in a future release." + ); } return wrap; @@ -119,94 +62,6 @@ internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMem [DllImport ("libc")] static extern int gettid (); -#if NETCOREAPP - [UnmanagedCallersOnly] -#endif - static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) - { - string typeName = new string ((char*) typeName_ptr, 0, typeName_len); - var type = Type.GetType (typeName); - if (type == null) { - monodroid_log (LogLevel.Error, - LogCategories.Default, - $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); - return; - } - - var className = Java.Interop.TypeManager.GetClassName (jniClass); - Java.Interop.TypeManager.RegisterType (className, type); - - JniType? jniType = null; - JniType.GetCachedJniType (ref jniType, className); - - ReadOnlySpan methods = new ReadOnlySpan ((void*) methods_ptr, methods_len); - ((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods); - } - -#if NETCOREAPP - [UnmanagedCallersOnly] -#endif - internal static unsafe void Initialize (JnienvInitializeArgs* args) - { - IntPtr total_timing_sequence = IntPtr.Zero; - IntPtr partial_timing_sequence = IntPtr.Zero; - - LogAssemblyCategory = (args->logCategories & (uint)LogCategories.Assembly) != 0; - - gref_gc_threshold = args->grefGcThreshold; - - jniRemappingInUse = args->jniRemappingInUse; - java_vm = args->javaVm; - - version = args->version; - - androidSdkVersion = args->androidSdkVersion; - - java_class_loader = args->grefLoader; - load_class_id = args->Loader_loadClass; - gref_class = args->grefClass; - mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true); - - if (args->localRefsAreIndirect == 1) - IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v); - else - IdentityHash = v => v; - -#if MONOANDROID1_0 - Mono.SystemDependencyProvider.Initialize (); -#endif - - BoundExceptionType = (BoundExceptionType)args->ioExceptionType; - androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); - AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; - - AllocObjectSupported = androidSdkVersion > 10; - IsRunningOnDesktop = args->isRunningOnDesktop == 1; - - grefIGCUserPeer_class = args->grefIGCUserPeer; - - PropagateExceptions = args->brokenExceptionTransitions == 0; - - JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; - if (IsRunningOnDesktop) { - var packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__"); - if (Enum.TryParse (packageNamingPolicy, out PackageNamingPolicy pnp)) { - JavaNativeTypeManager.PackageNamingPolicy = pnp; - } - } - -#if !MONOANDROID1_0 - SetSynchronizationContext (); -#endif - } - -#if !MONOANDROID1_0 - // NOTE: prevents Android.App.Application static ctor from running - [MethodImpl (MethodImplOptions.NoInlining)] - static void SetSynchronizationContext () => - SynchronizationContext.SetSynchronizationContext (Android.App.Application.SynchronizationContext); -#endif - internal static void Exit () { /* Manually dispose surfaced objects and close the current JniEnvironment to @@ -219,7 +74,7 @@ internal static void Exit () obj.Dispose (); continue; } catch (Exception e) { - monodroid_log (LogLevel.Warn, LogCategories.Default, $"Couldn't dispose object: {e}"); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"Couldn't dispose object: {e}"); } /* If calling Dispose failed, the assumption is that user-code in * the Dispose(bool) overload is to blame for it. In that case we @@ -247,29 +102,13 @@ static void ManualJavaObjectDispose (Java.Lang.Object obj) GC.SuppressFinalize (obj); } -#if NETCOREAPP - internal static Action mono_unhandled_exception = monodroid_debugger_unhandled_exception; -#else // NETCOREAPP - internal static Action mono_unhandled_exception = null!; -#endif // NETCOREAPP - internal static MethodInfo? mono_unhandled_exception_method = null; - #if !NETCOREAPP static Action AppDomain_DoUnhandledException = null!; #endif // ndef NETCOREAPP static void Initialize () { - if (mono_unhandled_exception == null) { - mono_unhandled_exception_method = typeof (System.Diagnostics.Debugger) - .GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); - if (mono_unhandled_exception_method != null) - mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), mono_unhandled_exception_method); - } - if (mono_unhandled_exception_method == null && mono_unhandled_exception != null) { - mono_unhandled_exception_method = mono_unhandled_exception.Method; - } - + AndroidRuntimeInternal.InitializeUnhandledExceptionMethod (); #if !NETCOREAPP if (AppDomain_DoUnhandledException == null) { var ad_due = typeof (AppDomain) @@ -286,14 +125,9 @@ static void Initialize () #endif // ndef NETCOREAPP } -#if NETCOREAPP - [MethodImplAttribute(MethodImplOptions.InternalCall)] - extern static void monodroid_unhandled_exception (Exception javaException); -#endif // def NETCOREAPP - internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPtr, IntPtr javaExceptionPtr) { - if (!PropagateExceptions) + if (!JNIEnvInit.PropagateExceptions) return; try { @@ -305,7 +139,7 @@ internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPt var javaException = JavaObject.GetObject (env, javaExceptionPtr, JniHandleOwnership.DoNotTransfer)!; if (Debugger.IsAttached) { - mono_unhandled_exception?.Invoke (javaException); + AndroidRuntimeInternal.mono_unhandled_exception?.Invoke (javaException); } try { @@ -321,32 +155,18 @@ internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPt //AppDomain.CurrentDomain.DoUnhandledException (args); AppDomain_DoUnhandledException?.Invoke (AppDomain.CurrentDomain, args); #else // ndef NETCOREAPP - monodroid_unhandled_exception (innerException ?? javaException); + RuntimeNativeMethods.monodroid_unhandled_exception (innerException ?? javaException); #endif // def NETCOREAPP } catch (Exception e) { Logger.Log (LogLevel.Error, "monodroid", "Exception thrown while raising AppDomain.UnhandledException event: " + e.ToString ()); } } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static void _monodroid_gc_wait_for_bridge_processing (); - -#pragma warning disable CS0649 // Field is never assigned to. This field is assigned from monodroid-glue.cc. - static volatile bool BridgeProcessing; // = false -#pragma warning restore CS0649 // Field is never assigned to. - public static void WaitForBridgeProcessing () { - if (!BridgeProcessing) - return; - _monodroid_gc_wait_for_bridge_processing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); - - internal static Func? IdentityHash; - public static IntPtr AllocObject (string jniClassName) { IntPtr jniClass = JNIEnv.FindClass (jniClassName); @@ -371,7 +191,7 @@ public static IntPtr AllocObject (Type type) public static unsafe IntPtr StartCreateInstance (IntPtr jclass, IntPtr constructorId, JValue* constructorParameters) { - if (AllocObjectSupported) { + if (JNIEnvInit.AllocObjectSupported) { return AllocObject (jclass); } return NewObject (jclass, constructorId, constructorParameters); @@ -385,7 +205,7 @@ public static unsafe IntPtr StartCreateInstance (IntPtr jclass, IntPtr construct public static unsafe void FinishCreateInstance (IntPtr instance, IntPtr jclass, IntPtr constructorId, JValue* constructorParameters) { - if (!AllocObjectSupported) + if (!JNIEnvInit.AllocObjectSupported) return; CallNonvirtualVoidMethod (instance, jclass, constructorId, constructorParameters); } @@ -398,7 +218,7 @@ public static unsafe void FinishCreateInstance (IntPtr instance, IntPtr jclass, public static unsafe IntPtr StartCreateInstance (Type type, string jniCtorSignature, JValue* constructorParameters) { - if (AllocObjectSupported) { + if (JNIEnvInit.AllocObjectSupported) { return AllocObject (type); } return CreateInstance (type, jniCtorSignature, constructorParameters); @@ -412,7 +232,7 @@ public static unsafe IntPtr StartCreateInstance (Type type, string jniCtorSignat public static unsafe IntPtr StartCreateInstance (string jniClassName, string jniCtorSignature, JValue* constructorParameters) { - if (AllocObjectSupported) + if (JNIEnvInit.AllocObjectSupported) return AllocObject (jniClassName); return CreateInstance (jniClassName, jniCtorSignature, constructorParameters); } @@ -425,7 +245,7 @@ public static unsafe IntPtr StartCreateInstance (string jniClassName, string jni public static unsafe void FinishCreateInstance (IntPtr instance, string jniCtorSignature, JValue* constructorParameters) { - if (!AllocObjectSupported) + if (!JNIEnvInit.AllocObjectSupported) return; InvokeConstructor (instance, jniCtorSignature, constructorParameters); } @@ -513,7 +333,7 @@ public static IntPtr FindClass (System.Type type) } catch (Java.Lang.Throwable e) { if (!((e is Java.Lang.NoClassDefFoundError) || (e is Java.Lang.ClassNotFoundException))) throw; - monodroid_log (LogLevel.Warn, LogCategories.Default, $"JNIEnv.FindClass(Type) caught unexpected exception: {e}"); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"JNIEnv.FindClass(Type) caught unexpected exception: {e}"); var jni = Java.Interop.TypeManager.GetJniTypeName (type); if (jni != null) { e.Dispose (); @@ -533,7 +353,7 @@ public static IntPtr FindClass (System.Type type) } } - static readonly int nameBufferLength = 1024; + const int nameBufferLength = 1024; [ThreadStatic] static char[]? nameBuffer; static unsafe IntPtr BinaryName (string classname) @@ -572,9 +392,9 @@ public unsafe static IntPtr FindClass (string classname) JniArgumentValue* parameters = stackalloc JniArgumentValue [3] { new JniArgumentValue (native_str), new JniArgumentValue (true), - new JniArgumentValue (java_class_loader), + new JniArgumentValue (JNIEnvInit.java_class_loader), }; - local_ref = JniEnvironment.StaticMethods.CallStaticObjectMethod (Java.Lang.Class.Members.JniPeerType.PeerReference, mid_Class_forName!, parameters); + local_ref = JniEnvironment.StaticMethods.CallStaticObjectMethod (Java.Lang.Class.Members.JniPeerType.PeerReference, JNIEnvInit.mid_Class_forName!, parameters); } finally { DeleteLocalRef (native_str); } @@ -634,27 +454,6 @@ internal static void DeleteRef (IntPtr handle, JniHandleOwnership transfer) } } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern int _monodroid_gref_log (string message); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern int _monodroid_gref_log_new (IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void _monodroid_gref_log_delete (IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void _monodroid_weak_gref_new (IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void _monodroid_weak_gref_delete (IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern int _monodroid_lref_log_new (int lrefc, IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void _monodroid_lref_log_delete (int lrefc, IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder from, int from_writable); - public static IntPtr NewGlobalRef (IntPtr jobject) { var r = new JniObjectReference (jobject); @@ -715,20 +514,15 @@ public static string GetClassNameFromInstance (IntPtr jobject) [MethodImplAttribute(MethodImplOptions.InternalCall)] static extern unsafe IntPtr monodroid_typemap_managed_to_java (Type type, byte* mvid); -#if NETCOREAPP - [MethodImplAttribute(MethodImplOptions.InternalCall)] - static extern unsafe void monodroid_debugger_unhandled_exception (Exception e); -#endif // NETCOREAPP - internal static void LogTypemapTrace (StackTrace st) { string? trace = st.ToString ()?.Trim (); if (String.IsNullOrEmpty (trace)) return; - monodroid_log (LogLevel.Warn, LogCategories.Assembly, "typemap: called from"); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Assembly, "typemap: called from"); foreach (string line in trace!.Split ('\n')) { - monodroid_log (LogLevel.Warn, LogCategories.Assembly, line); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Assembly, line); } } @@ -740,7 +534,7 @@ internal static void LogTypemapTrace (StackTrace st) var mvid = new Span(mvid_bytes); byte[]? mvid_data = null; if (!type.Module.ModuleVersionId.TryWriteBytes (mvid)) { - monodroid_log (LogLevel.Warn, LogCategories.Default, $"Failed to obtain module MVID using the fast method, falling back to the slow one"); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"Failed to obtain module MVID using the fast method, falling back to the slow one"); mvid_data = type.Module.ModuleVersionId.ToByteArray (); } else { mvid_data = mvid_bytes; @@ -752,8 +546,8 @@ internal static void LogTypemapTrace (StackTrace st) } if (ret == IntPtr.Zero) { - if (LogAssemblyCategory) { - monodroid_log (LogLevel.Warn, LogCategories.Default, $"typemap: failed to map managed type to Java type: {type.AssemblyQualifiedName} (Module ID: {type.Module.ModuleVersionId}; Type token: {type.MetadataToken})"); + if (JNIEnvInit.LogAssemblyCategory) { + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"typemap: failed to map managed type to Java type: {type.AssemblyQualifiedName} (Module ID: {type.Module.ModuleVersionId}; Type token: {type.MetadataToken})"); LogTypemapTrace (new StackTrace (true)); } diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs new file mode 100644 index 00000000000..9311757806e --- /dev/null +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -0,0 +1,136 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +using Java.Interop; +using Java.Interop.Tools.TypeNameMappings; + +namespace Android.Runtime +{ + static internal class JNIEnvInit + { +#pragma warning disable 0649 + internal struct JnienvInitializeArgs { + public IntPtr javaVm; + public IntPtr env; + public IntPtr grefLoader; + public IntPtr Loader_loadClass; + public IntPtr grefClass; // TODO: remove, not needed anymore + public IntPtr Class_forName; + public uint logCategories; + public int version; // TODO: remove, not needed anymore + public int androidSdkVersion; + public int localRefsAreIndirect; + public int grefGcThreshold; + public IntPtr grefIGCUserPeer; + public int isRunningOnDesktop; + public byte brokenExceptionTransitions; + public int packageNamingPolicy; + public byte ioExceptionType; + public int jniAddNativeMethodRegistrationAttributePresent; + public bool jniRemappingInUse; + public bool marshalMethodsEnabled; + } +#pragma warning restore 0649 + + internal static AndroidValueManager? AndroidValueManager; + internal static bool AllocObjectSupported; + internal static bool IsRunningOnDesktop; + internal static bool jniRemappingInUse; + internal static bool LocalRefsAreIndirect; + internal static bool LogAssemblyCategory; + internal static bool MarshalMethodsEnabled; + internal static bool PropagateExceptions; + internal static BoundExceptionType BoundExceptionType; + internal static int gref_gc_threshold; + internal static IntPtr grefIGCUserPeer_class; + internal static IntPtr java_class_loader; + internal static JniMethodInfo? mid_Class_forName; + + static AndroidRuntime? androidRuntime; + +#if NETCOREAPP + [UnmanagedCallersOnly] +#endif + static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) + { + string typeName = new string ((char*) typeName_ptr, 0, typeName_len); + var type = Type.GetType (typeName); + if (type == null) { + RuntimeNativeMethods.monodroid_log (LogLevel.Error, + LogCategories.Default, + $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); + return; + } + + var className = Java.Interop.TypeManager.GetClassName (jniClass); + Java.Interop.TypeManager.RegisterType (className, type); + + JniType? jniType = null; + JniType.GetCachedJniType (ref jniType, className); + + ReadOnlySpan methods = new ReadOnlySpan ((void*) methods_ptr, methods_len); + ((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods); + } + +#if NETCOREAPP + [UnmanagedCallersOnly] +#endif + internal static unsafe void Initialize (JnienvInitializeArgs* args) + { + IntPtr total_timing_sequence = IntPtr.Zero; + IntPtr partial_timing_sequence = IntPtr.Zero; + + LogAssemblyCategory = (args->logCategories & (uint)LogCategories.Assembly) != 0; + + gref_gc_threshold = args->grefGcThreshold; + + jniRemappingInUse = args->jniRemappingInUse; +#if NETCOREAPP + MarshalMethodsEnabled = args->marshalMethodsEnabled; +#endif + java_class_loader = args->grefLoader; + + mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true); + + LocalRefsAreIndirect = args->localRefsAreIndirect == 1; + +#if MONOANDROID1_0 + Mono.SystemDependencyProvider.Initialize (); +#endif + + bool androidNewerThan10 = args->androidSdkVersion > 10; + BoundExceptionType = (BoundExceptionType)args->ioExceptionType; + androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidNewerThan10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); + AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; + + AllocObjectSupported = androidNewerThan10; + IsRunningOnDesktop = args->isRunningOnDesktop == 1; + + grefIGCUserPeer_class = args->grefIGCUserPeer; + + PropagateExceptions = args->brokenExceptionTransitions == 0; + + JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; + if (IsRunningOnDesktop) { + var packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__"); + if (Enum.TryParse (packageNamingPolicy, out PackageNamingPolicy pnp)) { + JavaNativeTypeManager.PackageNamingPolicy = pnp; + } + } + +#if !MONOANDROID1_0 + SetSynchronizationContext (); +#endif + } + +#if !MONOANDROID1_0 + // NOTE: prevents Android.App.Application static ctor from running + [MethodImpl (MethodImplOptions.NoInlining)] + static void SetSynchronizationContext () => + SynchronizationContext.SetSynchronizationContext (Android.App.Application.SynchronizationContext); +#endif + } +} diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs index 1d09ccf9772..d7b27e855d8 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs @@ -20,9 +20,9 @@ static void get_runtime_types () if (exception_handler_method == null) AndroidEnvironment.FailFast ("Cannot find AndroidEnvironment.UnhandledException"); - wait_for_bridge_processing_method = typeof (JNIEnv).GetMethod ("WaitForBridgeProcessing", BindingFlags.Public | BindingFlags.Static); + wait_for_bridge_processing_method = typeof (AndroidRuntimeInternal).GetMethod ("WaitForBridgeProcessing", BindingFlags.Public | BindingFlags.Static); if (wait_for_bridge_processing_method == null) - AndroidEnvironment.FailFast ("Cannot find JNIEnv.WaitForBridgeProcessing"); + AndroidEnvironment.FailFast ("Cannot find AndroidRuntimeInternal.WaitForBridgeProcessing"); } public static Delegate CreateDelegate (Delegate dlg) @@ -41,8 +41,8 @@ public static Delegate CreateDelegate (Delegate dlg) if (result != null) return result; - if (JNIEnv.LogAssemblyCategory) { - JNIEnv.monodroid_log (LogLevel.Debug, LogCategories.Assembly, $"Falling back to System.Reflection.Emit for delegate type '{delegateType}': {dlg.Method}"); + if (JNIEnvInit.LogAssemblyCategory) { + RuntimeNativeMethods.monodroid_log (LogLevel.Debug, LogCategories.Assembly, $"Falling back to System.Reflection.Emit for delegate type '{delegateType}': {dlg.Method}"); } var ret_type = dlg.Method.ReturnType; @@ -72,11 +72,11 @@ public static Delegate CreateDelegate (Delegate dlg) ig.Emit (OpCodes.Leave, label); - bool filter = Debugger.IsAttached || !JNIEnv.PropagateExceptions; - if (filter && JNIEnv.mono_unhandled_exception_method != null) { + bool filter = Debugger.IsAttached || !JNIEnvInit.PropagateExceptions; + if (filter && AndroidRuntimeInternal.mono_unhandled_exception_method != null) { ig.BeginExceptFilterBlock (); - ig.Emit (OpCodes.Call, JNIEnv.mono_unhandled_exception_method); + ig.Emit (OpCodes.Call, AndroidRuntimeInternal.mono_unhandled_exception_method); ig.Emit (OpCodes.Ldc_I4_1); ig.BeginCatchBlock (null!); } else { @@ -101,4 +101,3 @@ public static Delegate CreateDelegate (Delegate dlg) } } - diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs index 301a488008a..738b2e07bc1 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs @@ -8,8 +8,8 @@ public static partial class JNINativeWrapper { static bool _unhandled_exception (Exception e) { - if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) { - JNIEnv.mono_unhandled_exception?.Invoke (e); + if (Debugger.IsAttached || !JNIEnvInit.PropagateExceptions) { + AndroidRuntimeInternal.mono_unhandled_exception?.Invoke (e); return false; } return true; @@ -17,18 +17,18 @@ static bool _unhandled_exception (Exception e) internal static void Wrap_JniMarshal_PP_V (this _JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static int Wrap_JniMarshal_PP_I (this _JniMarshal_PP_I callback, IntPtr jnienv, IntPtr klazz) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz); } catch (Exception e) when (_unhandled_exception (e)) { @@ -39,7 +39,7 @@ internal static int Wrap_JniMarshal_PP_I (this _JniMarshal_PP_I callback, IntPtr internal static bool Wrap_JniMarshal_PP_Z (this _JniMarshal_PP_Z callback, IntPtr jnienv, IntPtr klazz) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz); } catch (Exception e) when (_unhandled_exception (e)) { @@ -50,18 +50,18 @@ internal static bool Wrap_JniMarshal_PP_Z (this _JniMarshal_PP_Z callback, IntPt internal static void Wrap_JniMarshal_PPI_V (this _JniMarshal_PPI_V callback, IntPtr jnienv, IntPtr klazz, int p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static IntPtr Wrap_JniMarshal_PPI_L (this _JniMarshal_PPI_L callback, IntPtr jnienv, IntPtr klazz, int p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -72,7 +72,7 @@ internal static IntPtr Wrap_JniMarshal_PPI_L (this _JniMarshal_PPI_L callback, I internal static int Wrap_JniMarshal_PPI_I (this _JniMarshal_PPI_I callback, IntPtr jnienv, IntPtr klazz, int p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -83,7 +83,7 @@ internal static int Wrap_JniMarshal_PPI_I (this _JniMarshal_PPI_I callback, IntP internal static long Wrap_JniMarshal_PPI_J (this _JniMarshal_PPI_J callback, IntPtr jnienv, IntPtr klazz, int p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -94,7 +94,7 @@ internal static long Wrap_JniMarshal_PPI_J (this _JniMarshal_PPI_J callback, Int internal static int Wrap_JniMarshal_PPL_I (this _JniMarshal_PPL_I callback, IntPtr jnienv, IntPtr klazz, IntPtr p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -105,7 +105,7 @@ internal static int Wrap_JniMarshal_PPL_I (this _JniMarshal_PPL_I callback, IntP internal static IntPtr Wrap_JniMarshal_PPL_L (this _JniMarshal_PPL_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -116,18 +116,18 @@ internal static IntPtr Wrap_JniMarshal_PPL_L (this _JniMarshal_PPL_L callback, I internal static void Wrap_JniMarshal_PPL_V (this _JniMarshal_PPL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static bool Wrap_JniMarshal_PPL_Z (this _JniMarshal_PPL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -138,7 +138,7 @@ internal static bool Wrap_JniMarshal_PPL_Z (this _JniMarshal_PPL_Z callback, Int internal static bool Wrap_JniMarshal_PPJ_Z (this _JniMarshal_PPJ_Z callback, IntPtr jnienv, IntPtr klazz, long p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -149,62 +149,62 @@ internal static bool Wrap_JniMarshal_PPJ_Z (this _JniMarshal_PPJ_Z callback, Int internal static void Wrap_JniMarshal_PPII_V (this _JniMarshal_PPII_V callback, IntPtr jnienv, IntPtr klazz, int p0, int p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static void Wrap_JniMarshal_PPLI_V (this _JniMarshal_PPLI_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static void Wrap_JniMarshal_PPLZ_V (this _JniMarshal_PPLZ_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, bool p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static void Wrap_JniMarshal_PPLL_V (this _JniMarshal_PPLL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static void Wrap_JniMarshal_PPLF_V (this _JniMarshal_PPLF_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, float p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static IntPtr Wrap_JniMarshal_PPLI_L (this _JniMarshal_PPLI_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -215,7 +215,7 @@ internal static IntPtr Wrap_JniMarshal_PPLI_L (this _JniMarshal_PPLI_L callback, internal static IntPtr Wrap_JniMarshal_PPLL_L (this _JniMarshal_PPLL_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -226,7 +226,7 @@ internal static IntPtr Wrap_JniMarshal_PPLL_L (this _JniMarshal_PPLL_L callback, internal static bool Wrap_JniMarshal_PPLL_Z (this _JniMarshal_PPLL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -237,7 +237,7 @@ internal static bool Wrap_JniMarshal_PPLL_Z (this _JniMarshal_PPLL_Z callback, I internal static bool Wrap_JniMarshal_PPIL_Z (this _JniMarshal_PPIL_Z callback, IntPtr jnienv, IntPtr klazz, int p0, IntPtr p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -248,18 +248,18 @@ internal static bool Wrap_JniMarshal_PPIL_Z (this _JniMarshal_PPIL_Z callback, I internal static void Wrap_JniMarshal_PPIIL_V (this _JniMarshal_PPIIL_V callback, IntPtr jnienv, IntPtr klazz, int p0, int p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static int Wrap_JniMarshal_PPLII_I (this _JniMarshal_PPLII_I callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -270,7 +270,7 @@ internal static int Wrap_JniMarshal_PPLII_I (this _JniMarshal_PPLII_I callback, internal static bool Wrap_JniMarshal_PPLII_Z (this _JniMarshal_PPLII_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -281,29 +281,29 @@ internal static bool Wrap_JniMarshal_PPLII_Z (this _JniMarshal_PPLII_Z callback, internal static void Wrap_JniMarshal_PPLII_V (this _JniMarshal_PPLII_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static void Wrap_JniMarshal_PPIII_V (this _JniMarshal_PPIII_V callback, IntPtr jnienv, IntPtr klazz, int p0, int p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static bool Wrap_JniMarshal_PPLLJ_Z (this _JniMarshal_PPLLJ_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, long p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -314,18 +314,18 @@ internal static bool Wrap_JniMarshal_PPLLJ_Z (this _JniMarshal_PPLLJ_Z callback, internal static void Wrap_JniMarshal_PPILL_V (this _JniMarshal_PPILL_V callback, IntPtr jnienv, IntPtr klazz, int p0, IntPtr p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static bool Wrap_JniMarshal_PPLIL_Z (this _JniMarshal_PPLIL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -336,18 +336,18 @@ internal static bool Wrap_JniMarshal_PPLIL_Z (this _JniMarshal_PPLIL_Z callback, internal static void Wrap_JniMarshal_PPLLL_V (this _JniMarshal_PPLLL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static IntPtr Wrap_JniMarshal_PPLLL_L (this _JniMarshal_PPLLL_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -358,7 +358,7 @@ internal static IntPtr Wrap_JniMarshal_PPLLL_L (this _JniMarshal_PPLLL_L callbac internal static bool Wrap_JniMarshal_PPLLL_Z (this _JniMarshal_PPLLL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -369,7 +369,7 @@ internal static bool Wrap_JniMarshal_PPLLL_Z (this _JniMarshal_PPLLL_Z callback, internal static IntPtr Wrap_JniMarshal_PPIZI_L (this _JniMarshal_PPIZI_L callback, IntPtr jnienv, IntPtr klazz, int p0, bool p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -380,29 +380,29 @@ internal static IntPtr Wrap_JniMarshal_PPIZI_L (this _JniMarshal_PPIZI_L callbac internal static void Wrap_JniMarshal_PPIIII_V (this _JniMarshal_PPIIII_V callback, IntPtr jnienv, IntPtr klazz, int p0, int p1, int p2, int p3) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static void Wrap_JniMarshal_PPLLLL_V (this _JniMarshal_PPLLLL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2, IntPtr p3) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static bool Wrap_JniMarshal_PPLZZL_Z (this _JniMarshal_PPLZZL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, bool p1, bool p2, IntPtr p3) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2, p3); } catch (Exception e) when (_unhandled_exception (e)) { @@ -413,34 +413,34 @@ internal static bool Wrap_JniMarshal_PPLZZL_Z (this _JniMarshal_PPLZZL_Z callbac internal static void Wrap_JniMarshal_PPLIIII_V (this _JniMarshal_PPLIIII_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2, int p3, int p4) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3, p4); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static void Wrap_JniMarshal_PPZIIII_V (this _JniMarshal_PPZIIII_V callback, IntPtr jnienv, IntPtr klazz, bool p0, int p1, int p2, int p3, int p4) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3, p4); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } internal static void Wrap_JniMarshal_PPLIIIIIIII_V (this _JniMarshal_PPLIIIIIIII_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3, p4, p5, p6, p7, p8); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt index 94a5da0272e..b3bb339b6e7 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt @@ -250,8 +250,12 @@ namespace Android.Runtime { static bool _unhandled_exception (Exception e) { - if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) { - JNIEnv.mono_unhandled_exception?.Invoke (e); + if (Debugger.IsAttached || !JNIEnvInit.PropagateExceptions) { +#if NETCOREAPP + AndroidRuntimeInternal.mono_unhandled_exception?.Invoke (e); +#else + JNIEnvInit.mono_unhandled_exception?.Invoke (e); +#endif return false; } return true; @@ -262,7 +266,7 @@ foreach (var info in delegateTypes) { #> internal static <#= info.Return #> Wrap<#= info.Type #> (this <#= info.Type #> callback, <#= info.Signature #>) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { <#= info.Return != "void" ? "return " : "" #>callback (<#= info.Invoke #>); } catch (Exception e) when (_unhandled_exception (e)) { diff --git a/src/Mono.Android/Android.Runtime/LogCategories.cs b/src/Mono.Android/Android.Runtime/LogCategories.cs new file mode 100644 index 00000000000..5eb6020cfad --- /dev/null +++ b/src/Mono.Android/Android.Runtime/LogCategories.cs @@ -0,0 +1,23 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME +using System; + +namespace Android.Runtime +{ + // Keep in sync with the LogCategories enum in + // monodroid/libmonodroid/logger.{c,h} + [Flags] + internal enum LogCategories { + None = 0, + Default = 1 << 0, + Assembly = 1 << 1, + Debugger = 1 << 2, + GC = 1 << 3, + GlobalRef = 1 << 4, + LocalRef = 1 << 5, + Timing = 1 << 6, + Bundle = 1 << 7, + Net = 1 << 8, + Netlink = 1 << 9, + } +} +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/LogLevel.cs b/src/Mono.Android/Android.Runtime/LogLevel.cs new file mode 100644 index 00000000000..347f2037523 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/LogLevel.cs @@ -0,0 +1,18 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME +namespace Android.Runtime +{ + // Keep in sync with the LogLevel enum in + // monodroid/libmonodroid/logger.{c,h} + internal enum LogLevel { + Unknown = 0x00, + Default = 0x01, + Verbose = 0x02, + Debug = 0x03, + Info = 0x04, + Warn = 0x05, + Error = 0x06, + Fatal = 0x07, + Silent = 0x08 + } +} +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/Logger.cs b/src/Mono.Android/Android.Runtime/Logger.cs index 35e0651a835..f4665ab3a63 100644 --- a/src/Mono.Android/Android.Runtime/Logger.cs +++ b/src/Mono.Android/Android.Runtime/Logger.cs @@ -60,7 +60,7 @@ public static void Log (LogLevel level, string appname, string? log) { } } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] extern static uint monodroid_get_log_categories (); static Logger () @@ -68,35 +68,4 @@ static Logger () Categories = (LogCategories) monodroid_get_log_categories (); } } - - // Keep in sync with the LogLevel enum in - // monodroid/libmonodroid/logger.{c,h} - internal enum LogLevel { - Unknown = 0x00, - Default = 0x01, - Verbose = 0x02, - Debug = 0x03, - Info = 0x04, - Warn = 0x05, - Error = 0x06, - Fatal = 0x07, - Silent = 0x08 - } - - // Keep in sync with the LogCategories enum in - // monodroid/libmonodroid/logger.{c,h} - [Flags] - internal enum LogCategories { - None = 0, - Default = 1 << 0, - Assembly = 1 << 1, - Debugger = 1 << 2, - GC = 1 << 3, - GlobalRef = 1 << 4, - LocalRef = 1 << 5, - Timing = 1 << 6, - Bundle = 1 << 7, - Net = 1 << 8, - Netlink = 1 << 9, - } } diff --git a/src/Mono.Android/Android.Runtime/RuntimeConstants.cs b/src/Mono.Android/Android.Runtime/RuntimeConstants.cs new file mode 100644 index 00000000000..254fb8c2170 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/RuntimeConstants.cs @@ -0,0 +1,9 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME +namespace Android.Runtime +{ + internal static class RuntimeConstants + { + public const string InternalDllName = "xa-internal-api"; + } +} +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs new file mode 100644 index 00000000000..3487514a6a7 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs @@ -0,0 +1,88 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Android.Runtime +{ + internal static class RuntimeNativeMethods + { + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void monodroid_log (LogLevel level, LogCategories category, string message); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static IntPtr monodroid_timing_start (string? message); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void monodroid_timing_stop (IntPtr sequence, string? message); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void monodroid_free (IntPtr ptr); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_gref_get (); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_weak_gref_get (); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _monodroid_lookup_replacement_type (string jniSimpleReference); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _monodroid_lookup_replacement_method_info (string jniSourceType, string jniMethodName, string jniMethodSignature); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _monodroid_timezone_get_default_id (); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_getifaddrs (out IntPtr ifap); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_freeifaddrs (IntPtr ifap); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_detect_cpu_and_architecture (ref ushort built_for_cpu, ref ushort running_on_cpu, ref byte is64bit); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void _monodroid_gc_wait_for_bridge_processing (); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_gref_log (string message); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_gref_log_new (IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_gref_log_delete (IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_weak_gref_new (IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_weak_gref_delete (IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_lref_log_new (int lrefc, IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_lref_log_delete (int lrefc, IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr monodroid_TypeManager_get_java_class_name (IntPtr klass); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_max_gref_get (); +#if NETCOREAPP + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void monodroid_unhandled_exception (Exception javaException); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern unsafe void monodroid_debugger_unhandled_exception (Exception e); +#endif + } +} +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/TimingLogger.cs b/src/Mono.Android/Android.Runtime/TimingLogger.cs index 9bbbf538c0a..e34ec0d701e 100644 --- a/src/Mono.Android/Android.Runtime/TimingLogger.cs +++ b/src/Mono.Android/Android.Runtime/TimingLogger.cs @@ -45,7 +45,7 @@ public void Start (string? startMessage = null) if (sequence != IntPtr.Zero) return; - sequence = JNIEnv.monodroid_timing_start (startMessage ?? initStartMessage); + sequence = RuntimeNativeMethods.monodroid_timing_start (startMessage ?? initStartMessage); } /// @@ -65,7 +65,7 @@ public void Stop (string stopMessage) if (sequence == IntPtr.Zero) return; - JNIEnv.monodroid_timing_stop (sequence, stopMessage); + RuntimeNativeMethods.monodroid_timing_stop (sequence, stopMessage); sequence = IntPtr.Zero; } @@ -86,7 +86,7 @@ protected virtual void Dispose (bool disposing) { if (!disposed) { if (sequence != IntPtr.Zero) { - JNIEnv.monodroid_timing_stop (sequence, null); + RuntimeNativeMethods.monodroid_timing_stop (sequence, null); sequence = IntPtr.Zero; } diff --git a/src/Mono.Android/Java.Interop/Runtime.cs b/src/Mono.Android/Java.Interop/Runtime.cs index 4ebf234200e..ceb14ca9d87 100644 --- a/src/Mono.Android/Java.Interop/Runtime.cs +++ b/src/Mono.Android/Java.Interop/Runtime.cs @@ -11,7 +11,7 @@ public static class Runtime { [Obsolete ("Please use Java.Interop.JniEnvironment.Runtime.ValueManager.GetSurfacedPeers()")] public static List GetSurfacedObjects () { - var peers = JNIEnv.AndroidValueManager!.GetSurfacedPeers (); + var peers = JNIEnvInit.AndroidValueManager!.GetSurfacedPeers (); var r = new List (peers.Count); foreach (var p in peers) { if (p.SurfacedPeer.TryGetTarget (out var target)) @@ -20,18 +20,12 @@ public static List GetSurfacedObjects () return r; } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_max_gref_get (); - public static int MaxGlobalReferenceCount { - get {return _monodroid_max_gref_get ();} + get {return RuntimeNativeMethods._monodroid_max_gref_get ();} } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_gref_get (); - public static int GlobalReferenceCount { - get {return _monodroid_gref_get ();} + get {return RuntimeNativeMethods._monodroid_gref_get ();} } public static int LocalReferenceCount { diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 0c32fb53b84..e70d00bc424 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -38,14 +38,11 @@ public static Dictionary ManagedToJni { } public static partial class TypeManager { - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static IntPtr monodroid_TypeManager_get_java_class_name (IntPtr klass); - internal static string GetClassName (IntPtr class_ptr) { - IntPtr ptr = monodroid_TypeManager_get_java_class_name (class_ptr); + IntPtr ptr = RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name (class_ptr); string ret = Marshal.PtrToStringAnsi (ptr)!; - JNIEnv.monodroid_free (ptr); + RuntimeNativeMethods.monodroid_free (ptr); return ret; } @@ -144,7 +141,7 @@ static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPt string.Format ("warning: Skipping managed constructor invocation for handle 0x{0} (key_handle 0x{1}). " + "Please use JNIEnv.StartCreateInstance() + JNIEnv.FinishCreateInstance() instead of " + "JNIEnv.NewObject() and/or JNIEnv.CreateInstance().", - jobject.ToString ("x"), JNIEnv.IdentityHash! (jobject).ToString ("x"))); + jobject.ToString ("x"), JNIEnv.IdentityHash (jobject).ToString ("x"))); } return; } @@ -183,7 +180,7 @@ internal static void Activate (IntPtr jobject, ConstructorInfo cinfo, object? [] cinfo.Invoke (newobj, parms); } catch (Exception e) { var m = string.Format ("Could not activate JNI Handle 0x{0} (key_handle 0x{1}) of Java type '{2}' as managed type '{3}'.", - jobject.ToString ("x"), JNIEnv.IdentityHash! (jobject).ToString ("x"), JNIEnv.GetClassNameFromInstance (jobject), cinfo.DeclaringType.FullName); + jobject.ToString ("x"), JNIEnv.IdentityHash (jobject).ToString ("x"), JNIEnv.GetClassNameFromInstance (jobject), cinfo.DeclaringType.FullName); Logger.Log (LogLevel.Warn, "monodroid", m); Logger.Log (LogLevel.Warn, "monodroid", CreateJavaLocationException ().ToString ()); @@ -224,9 +221,9 @@ static Exception CreateJavaLocationException () if (type != null) return type; - if (!JNIEnv.IsRunningOnDesktop) { + if (!JNIEnvInit.IsRunningOnDesktop) { // Miss message is logged in the native runtime - if (JNIEnv.LogAssemblyCategory) + if (JNIEnvInit.LogAssemblyCategory) JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); return null; } @@ -314,7 +311,7 @@ internal static IJavaPeerable CreateInstance (IntPtr handle, JniHandleOwnership result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable); } } catch (MissingMethodException e) { - var key_handle = JNIEnv.IdentityHash! (handle); + var key_handle = JNIEnv.IdentityHash (handle); JNIEnv.DeleteRef (handle, transfer); throw new NotSupportedException ( string.Format ("Unable to activate instance of type {0} from native handle 0x{1} (key_handle 0x{2}).", @@ -359,7 +356,7 @@ public static void RegisterType (string java_class, Type t) if (String.Compare (jniFromType, java_class, StringComparison.OrdinalIgnoreCase) != 0) { TypeManagerMapDictionaries.ManagedToJni.Add (t, java_class); } - } else if (!JNIEnv.IsRunningOnDesktop || t != typeof (Java.Interop.TypeManager)) { + } else if (!JNIEnvInit.IsRunningOnDesktop || t != typeof (Java.Interop.TypeManager)) { // skip the registration and output a warning Logger.Log (LogLevel.Warn, "monodroid", string.Format ("Type Registration Skipped for {0} to {1} ", java_class, t.ToString())); } @@ -379,7 +376,7 @@ public static void RegisterPackage (string package, Converter look { LazyInitPackageLookup (); - lock (packageLookup) { + lock (packageLookup!) { if (!packageLookup.TryGetValue (package, out var lookups)) packageLookup.Add (package, lookups = new List> ()); lookups.Add (lookup); @@ -397,7 +394,7 @@ public static void RegisterPackages (string[] packages, Converter if (packages.Length != lookups.Length) throw new ArgumentException ("`packages` and `lookups` arrays must have same number of elements."); - lock (packageLookup) { + lock (packageLookup!) { for (int i = 0; i < packages.Length; ++i) { string package = packages [i]; var lookup = lookups [i]; @@ -418,6 +415,18 @@ static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPt TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); } +#if NETCOREAPP + [UnmanagedCallersOnly] + static void n_Activate_mm (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) + { + // TODO: need a full wrapper code here, a'la JNINativeWrapper.CreateDelegate + try { + TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + } catch (Exception ex) { + AndroidEnvironment.UnhandledException (ex); + } + } +#endif internal static Delegate GetActivateHandler () { return TypeManager.GetActivateHandler (); diff --git a/src/Mono.Android/Java.Lang/Object.cs b/src/Mono.Android/Java.Lang/Object.cs index 76618d7026e..e968f0369ff 100644 --- a/src/Mono.Android/Java.Lang/Object.cs +++ b/src/Mono.Android/Java.Lang/Object.cs @@ -170,7 +170,7 @@ void IJavaPeerable.DisposeUnlessReferenced () [EditorBrowsable (EditorBrowsableState.Never)] public void UnregisterFromRuntime () { - JNIEnv.AndroidValueManager?.RemovePeer (this, key_handle); + JNIEnvInit.AndroidValueManager?.RemovePeer (this, key_handle); } void IJavaPeerable.Disposed () @@ -207,7 +207,7 @@ void IJavaPeerable.SetPeerReference (JniObjectReference reference) public void Dispose () { - JNIEnv.AndroidValueManager?.DisposePeer (this); + JNIEnvInit.AndroidValueManager?.DisposePeer (this); } protected virtual void Dispose (bool disposing) @@ -220,11 +220,11 @@ internal static void Dispose (IJavaPeerable instance, ref IntPtr handle, IntPtr return; if (Logger.LogGlobalRef) { - JNIEnv._monodroid_gref_log ( + RuntimeNativeMethods._monodroid_gref_log ( string.Format ("Disposing handle 0x{0}\n", handle.ToString ("x"))); } - JNIEnv.AndroidValueManager?.RemovePeer (instance, key_handle); + JNIEnvInit.AndroidValueManager?.RemovePeer (instance, key_handle); switch (handle_type) { case JObjectRefType.Global: @@ -248,13 +248,13 @@ internal static void Dispose (IJavaPeerable instance, ref IntPtr handle, IntPtr [EditorBrowsable (EditorBrowsableState.Never)] protected void SetHandle (IntPtr value, JniHandleOwnership transfer) { - JNIEnv.AndroidValueManager?.AddPeer (this, value, transfer, out handle); + JNIEnvInit.AndroidValueManager?.AddPeer (this, value, transfer, out handle); handle_type = JObjectRefType.Global; } internal static IJavaPeerable? PeekObject (IntPtr handle, Type? requiredType = null) { - var peeked = JNIEnv.AndroidValueManager?.PeekPeer (new JniObjectReference (handle)); + var peeked = JNIEnvInit.AndroidValueManager?.PeekPeer (new JniObjectReference (handle)); if (peeked == null) return null; if (requiredType != null && !requiredType.IsAssignableFrom (peeked.GetType ())) diff --git a/src/Mono.Android/Java.Lang/Throwable.cs b/src/Mono.Android/Java.Lang/Throwable.cs index b483c557c76..5dc63ef7952 100644 --- a/src/Mono.Android/Java.Lang/Throwable.cs +++ b/src/Mono.Android/Java.Lang/Throwable.cs @@ -241,7 +241,7 @@ public unsafe Java.Lang.Class? Class { [EditorBrowsable (EditorBrowsableState.Never)] protected void SetHandle (IntPtr value, JniHandleOwnership transfer) { - JNIEnv.AndroidValueManager?.AddPeer (this, value, transfer, out handle); + JNIEnvInit.AndroidValueManager?.AddPeer (this, value, transfer, out handle); handle_type = JObjectRefType.Global; } @@ -296,7 +296,7 @@ void IJavaPeerable.DisposeUnlessReferenced () public void UnregisterFromRuntime () { - JNIEnv.AndroidValueManager?.RemovePeer (this, key_handle); + JNIEnvInit.AndroidValueManager?.RemovePeer (this, key_handle); } void IJavaPeerable.Disposed () @@ -332,7 +332,7 @@ void IJavaPeerable.SetPeerReference (JniObjectReference reference) public void Dispose () { - JNIEnv.AndroidValueManager?.DisposePeer (this); + JNIEnvInit.AndroidValueManager?.DisposePeer (this); } protected virtual void Dispose (bool disposing) @@ -340,4 +340,3 @@ protected virtual void Dispose (bool disposing) } } } - diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 97e25263da6..1fdbe977281 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -229,6 +229,7 @@ + @@ -249,6 +250,7 @@ + @@ -259,6 +261,8 @@ + + @@ -266,7 +270,9 @@ + + @@ -387,6 +393,10 @@ + + + + diff --git a/src/Mono.Android/java/mono/android/TypeManager.java b/src/Mono.Android/java/mono/android/TypeManager.java index 6915cb96034..bf64780bf9e 100644 --- a/src/Mono.Android/java/mono/android/TypeManager.java +++ b/src/Mono.Android/java/mono/android/TypeManager.java @@ -9,10 +9,12 @@ public static void Activate (String typeName, String sig, Object instance, Objec private static native void n_activate (String typeName, String sig, Object instance, Object[] parameterList); +//#MARSHAL_METHODS:START - do not remove or modify this line, it is required during application build static { String methods = "n_activate:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V:GetActivateHandler\n" + ""; mono.android.Runtime.register ("Java.Interop.TypeManager+JavaTypeManager, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", TypeManager.class, methods); } +//#MARSHAL_METHODS:END - do not remove or modify this line, it is required during application build } diff --git a/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml index 3c4d22fbfe0..dbcd130d347 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml +++ b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml @@ -13,6 +13,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets index 009618373d4..ce005fa8959 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets @@ -208,7 +208,9 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. <_ProtobufFormat Condition=" '$(AndroidPackageFormat)' == 'aab' ">True <_ProtobufFormat Condition=" '$(_ProtobufFormat)' == '' ">False <_Aapt2ProguardRules Condition=" '$(AndroidLinkTool)' != '' ">$(IntermediateOutputPath)aapt_rules.txt + <_OutputFileDir>$([System.IO.Path]::GetDirectoryName ('$(_PackagedResources)')) + + <_AotResolvedFileToPublish + Include="@(_AotCompiledAssemblies)" + Condition=" '$(_AndroidUseMarshalMethods)' == 'True' " + ArchiveFileName="libaot-$([System.IO.Path]::GetFileNameWithoutExtension('%(_AotCompiledAssemblies.Identity)')).so" + /> + + + + + + <_AotResolvedFileToPublish Remove="@(_SourceItemsToCopyToPublishDirectory)" /> + <_AotResolvedFileToPublish + Condition=" '%(_AotResolvedFileToPublish.RuntimeIdentifier)' == '' " + Update="@(_AotResolvedFileToPublish)" + RuntimeIdentifier="$(RuntimeIdentifier)" + /> diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index 188ddf12e42..3ccabedc09d 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -38,8 +38,13 @@ _ResolveAssemblies MSBuild target. + + + <_RunAotMaybe Condition=" '$(_AndroidUseMarshalMethods)' != 'True' ">_AndroidAot + + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs new file mode 100644 index 00000000000..37872f31927 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs @@ -0,0 +1,79 @@ +using System.IO; +using System.Reflection; +using System.Text; +using System; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; + +namespace Xamarin.Android.Tasks +{ + public class CreateTypeManagerJava : AndroidTask + { + public override string TaskPrefix => "CTMJ"; + + [Required] + public string ResourceName { get; set; } + + [Required] + public string OutputFilePath { get; set; } + + static readonly Assembly ExecutingAssembly = Assembly.GetExecutingAssembly (); + + public override bool RunTask () + { + string? content = ReadResource (ResourceName); + + if (String.IsNullOrEmpty (content)) { + return false; + } + + var result = new StringBuilder (); + bool ignoring = false; + foreach (string line in content.Split ('\n')) { + if (!ignoring) { + if (ignoring = line.StartsWith ("//#MARSHAL_METHODS:START", StringComparison.Ordinal)) { + continue; + } + result.AppendLine (line); + } else if (line.StartsWith ("//#MARSHAL_METHODS:END", StringComparison.Ordinal)) { + ignoring = false; + } + } + + if (result.Length == 0) { + Log.LogDebugMessage ("TypeManager.java not generated, empty resource data"); + return false; + } + + using (var ms = new MemoryStream ()) { + using (var sw = new StreamWriter (ms)) { + sw.Write (result.ToString ()); + sw.Flush (); + + if (Files.CopyIfStreamChanged (ms, OutputFilePath)) { + Log.LogDebugMessage ($"Wrote resource {OutputFilePath}."); + } else { + Log.LogDebugMessage ($"Resource {OutputFilePath} is unchanged. Skipping."); + } + } + } + + return !Log.HasLoggedErrors; + } + + string? ReadResource (string resourceName) + { + using (var from = ExecutingAssembly.GetManifestResourceStream (resourceName)) { + if (from == null) { + Log.LogCodedError ("XA0116", Properties.Resources.XA0116, resourceName); + return null; + } + + using (var sr = new StreamReader (from)) { + return sr.ReadToEnd (); + } + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 6ff7428ad7e..4053a0f9b1e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -51,6 +51,10 @@ public class GenerateJavaStubs : AndroidTask [Required] public bool GenerateNativeAssembly { get; set; } + public string IntermediateOutputDirectory { get; set; } + public bool LinkingEnabled { get; set; } + public bool HaveMultipleRIDs { get; set; } + public bool EnableMarshalMethods { get; set; } public string ManifestTemplate { get; set; } public string[] MergedManifestDocuments { get; set; } @@ -86,6 +90,8 @@ public class GenerateJavaStubs : AndroidTask public string SupportedOSPlatformVersion { get; set; } + public ITaskItem[] Environments { get; set; } + [Output] public string [] GeneratedBinaryTypeMaps { get; set; } @@ -96,8 +102,8 @@ public override bool RunTask () try { // We're going to do 3 steps here instead of separate tasks so // we can share the list of JLO TypeDefinitions between them - using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true)) { - Run (res); + using (DirectoryAssemblyResolver res = MakeResolver ()) { + Run (res, useMarshalMethods: !Debug && EnableMarshalMethods); } } catch (XamarinAndroidException e) { Log.LogCodedError (string.Format ("XA{0:0000}", e.Code), e.MessageWithoutCode); @@ -115,21 +121,33 @@ public override bool RunTask () return !Log.HasLoggedErrors; } - void Run (DirectoryAssemblyResolver res) + DirectoryAssemblyResolver MakeResolver () { - PackageNamingPolicy pnp; - JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64; + var readerParams = new ReaderParameters { + ReadWrite = true, + InMemory = true, + }; + var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true, loadReaderParameters: readerParams); foreach (var dir in FrameworkDirectories) { - if (Directory.Exists (dir.ItemSpec)) + if (Directory.Exists (dir.ItemSpec)) { res.SearchDirectories.Add (dir.ItemSpec); + } } -#if ENABLE_MARSHAL_METHODS + + return res; + } + + void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) + { + PackageNamingPolicy pnp; + JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64; + Dictionary> marshalMethodsAssemblyPaths = null; - if (!Debug) { + if (useMarshalMethods) { marshalMethodsAssemblyPaths = new Dictionary> (StringComparer.Ordinal); } -#endif + // Put every assembly we'll need in the resolver bool hasExportReference = false; bool haveMonoAndroid = false; @@ -164,11 +182,9 @@ void Run (DirectoryAssemblyResolver res) } res.Load (assembly.ItemSpec); -#if ENABLE_MARSHAL_METHODS - if (!Debug) { + if (useMarshalMethods) { StoreMarshalAssemblyPath (Path.GetFileNameWithoutExtension (assembly.ItemSpec), assembly); } -#endif } // However we only want to look for JLO types in user code for Java stub code generation @@ -182,9 +198,7 @@ void Run (DirectoryAssemblyResolver res) string name = Path.GetFileNameWithoutExtension (asm.ItemSpec); if (!userAssemblies.ContainsKey (name)) userAssemblies.Add (name, asm.ItemSpec); -#if ENABLE_MARSHAL_METHODS StoreMarshalAssemblyPath (name, asm); -#endif } // Step 1 - Find all the JLO types @@ -197,7 +211,10 @@ void Run (DirectoryAssemblyResolver res) var javaTypes = new List (); foreach (TypeDefinition td in allJavaTypes) { - if (!userAssemblies.ContainsKey (td.Module.Assembly.Name.Name) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (td, cache)) { + // Whem marshal methods are in use we do not want to skip non-user assemblies (such as Mono.Android) - we need to generate JCWs for them during + // application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android + // build and stored in a jar file. + if ((!useMarshalMethods && !userAssemblies.ContainsKey (td.Module.Assembly.Name.Name)) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (td, cache)) { continue; } @@ -205,25 +222,43 @@ void Run (DirectoryAssemblyResolver res) } MarshalMethodsClassifier classifier = null; -#if ENABLE_MARSHAL_METHODS - if (!Debug) { + if (useMarshalMethods) { classifier = new MarshalMethodsClassifier (cache, res, Log); } -#endif + // Step 2 - Generate Java stub code - var success = CreateJavaSources (javaTypes, cache, classifier); + var success = CreateJavaSources (javaTypes, cache, classifier, useMarshalMethods); if (!success) return; -#if ENABLE_MARSHAL_METHODS - if (!Debug) { - // TODO: we must rewrite assemblies for all SupportedAbis. Alternatively, we need to copy the ones that are identical - // Cecil does **not** guarantee that the same assembly modified twice in the same will yield the same result - tokens may differ, so can - // MVID. + if (useMarshalMethods) { + // We need to parse the environment files supplied by the user to see if they want to use broken exception transitions. This information is needed + // in order to properly generate wrapper methods in the marshal methods assembly rewriter. + // We don't care about those generated by us, since they won't contain the `XA_BROKEN_EXCEPTION_TRANSITIONS` variable we look for. + var environmentParser = new EnvironmentFilesParser (); + var targetPaths = new List (); + + if (!LinkingEnabled) { + targetPaths.Add (Path.GetDirectoryName (ResolvedAssemblies[0].ItemSpec)); + } else { + if (String.IsNullOrEmpty (IntermediateOutputDirectory)) { + throw new InvalidOperationException ($"Internal error: marshal methods require the `IntermediateOutputDirectory` property of the `GenerateJavaStubs` task to have a value"); + } + + // If the property is set then, even if we have just one RID, the linked assemblies path will include the RID + if (!HaveMultipleRIDs && SupportedAbis.Length == 1) { + targetPaths.Add (Path.Combine (IntermediateOutputDirectory, "linked")); + } else { + foreach (string abi in SupportedAbis) { + targetPaths.Add (Path.Combine (IntermediateOutputDirectory, AbiToRid (abi), "linked")); + } + } + } + var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, marshalMethodsAssemblyPaths, Log); - rewriter.Rewrite (res); + rewriter.Rewrite (res, targetPaths, environmentParser.AreBrokenExceptionTransitionsEnabled (Environments)); } -#endif + // Step 3 - Generate type maps // Type mappings need to use all the assemblies, always. WriteTypeMappings (allJavaTypes, cache); @@ -240,9 +275,6 @@ void Run (DirectoryAssemblyResolver res) string managedKey = type.FullName.Replace ('/', '.'); string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.'); -#if ENABLE_MARSHAL_METHODS - Console.WriteLine ($"##G2: {type.FullName} -> {javaKey}"); -#endif acw_map.Write (type.GetPartialAssemblyQualifiedName (cache)); acw_map.Write (';'); acw_map.Write (javaKey); @@ -352,11 +384,10 @@ void Run (DirectoryAssemblyResolver res) regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first."); foreach (var type in javaTypes) { if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) { -#if ENABLE_MARSHAL_METHODS - if (!classifier.FoundDynamicallyRegisteredMethods (type)) { + if (classifier != null && !classifier.FoundDynamicallyRegisteredMethods (type)) { continue; } -#endif + string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.'); regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);", type.GetAssemblyQualifiedName (cache), javaKey); @@ -369,8 +400,9 @@ void Run (DirectoryAssemblyResolver res) SaveResource (applicationTemplateFile, applicationTemplateFile, real_app_dir, template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ())); -#if ENABLE_MARSHAL_METHODS - if (!Debug) { + if (useMarshalMethods) { + classifier.AddSpecialCaseMethods (); + Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}"); if (classifier.RejectedMethodCount > 0) { @@ -378,13 +410,14 @@ void Run (DirectoryAssemblyResolver res) } if (classifier.WrappedMethodCount > 0) { - Log.LogWarning ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}"); + // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers + Log.LogDebugMessage ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}"); } } void StoreMarshalAssemblyPath (string name, ITaskItem asm) { - if (Debug) { + if (!useMarshalMethods) { return; } @@ -396,11 +429,34 @@ void StoreMarshalAssemblyPath (string name, ITaskItem asm) assemblyPaths.Add (asm.ItemSpec); } -#endif + + string AbiToRid (string abi) + { + switch (abi) { + case "arm64-v8a": + return "android-arm64"; + + case "armeabi-v7a": + return "android-arm"; + + case "x86": + return "android-x86"; + + case "x86_64": + return "android-x64"; + + default: + throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); + } + } } - bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier classifier) + bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier classifier, bool useMarshalMethods) { + if (useMarshalMethods && classifier == null) { + throw new ArgumentNullException (nameof (classifier)); + } + string outputPath = Path.Combine (OutputDirectory, "src"); string monoInit = GetMonoInitSource (AndroidSdkPlatform); bool hasExportReference = ResolvedAssemblies.Any (assembly => Path.GetFileName (assembly.ItemSpec) == "Mono.Android.Export.dll"); @@ -408,9 +464,6 @@ bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCac bool ok = true; foreach (var t in javaTypes) { -#if ENABLE_MARSHAL_METHODS - Console.WriteLine ($"##G0: JCW for {t.FullName}"); -#endif if (t.IsInterface) { // Interfaces are in typemap but they shouldn't have JCW generated for them continue; @@ -425,13 +478,11 @@ bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCac }; jti.Generate (writer); -#if ENABLE_MARSHAL_METHODS - if (!Debug) { + if (useMarshalMethods) { if (classifier.FoundDynamicallyRegisteredMethods (t)) { 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."); } } -#endif writer.Flush (); var path = jti.GetDestinationPath (outputPath); @@ -465,11 +516,11 @@ bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCac } } } -#if ENABLE_MARSHAL_METHODS - if (!Debug) { + + if (useMarshalMethods) { BuildEngine4.RegisterTaskObjectAssemblyLocal (MarshalMethodsRegisterTaskKey, new MarshalMethodsState (classifier.MarshalMethods), RegisteredTaskObjectLifetime.Build); } -#endif + return ok; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index e06e300a462..e8638b29761 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -68,6 +68,7 @@ public class GeneratePackageManagerJava : AndroidTask [Required] public bool InstantRunEnabled { get; set; } + public bool EnableMarshalMethods { get; set; } public string RuntimeConfigBinFilePath { get; set; } public string BoundExceptionType { get; set; } @@ -175,8 +176,6 @@ static internal AndroidTargetArch GetAndroidTargetArchForAbi (string abi) void AddEnvironment () { bool usesMonoAOT = false; - bool usesAssemblyPreload = EnablePreloadAssembliesDefault; - bool brokenExceptionTransitions = false; var environmentVariables = new Dictionary (StringComparer.Ordinal); var systemProperties = new Dictionary (StringComparer.Ordinal); @@ -189,84 +188,48 @@ void AddEnvironment () usesMonoAOT = true; } - bool haveLogLevel = false; - bool haveMonoDebug = false; - bool havebuildId = false; - bool haveHttpMessageHandler = false; - bool haveTlsProvider = false; - bool haveMonoGCParams = false; - SequencePointsMode sequencePointsMode; if (!Aot.TryGetSequencePointsMode (AndroidSequencePointsMode, out sequencePointsMode)) sequencePointsMode = SequencePointsMode.None; - foreach (ITaskItem env in Environments ?? Array.Empty ()) { - foreach (string line in File.ReadLines (env.ItemSpec)) { - var lineToWrite = line; - if (lineToWrite.StartsWith ("MONO_LOG_LEVEL=", StringComparison.Ordinal)) - haveLogLevel = true; - if (lineToWrite.StartsWith ("MONO_GC_PARAMS=", StringComparison.Ordinal)) { - haveMonoGCParams = true; - if (lineToWrite.IndexOf ("bridge-implementation=old", StringComparison.Ordinal) >= 0) { - Log.LogCodedWarning ("XA2000", Properties.Resources.XA2000_gcParams_bridgeImpl); - } - } - if (lineToWrite.StartsWith ("XAMARIN_BUILD_ID=", StringComparison.Ordinal)) - havebuildId = true; - if (lineToWrite.StartsWith ("MONO_DEBUG=", StringComparison.Ordinal)) { - haveMonoDebug = true; - if (sequencePointsMode != SequencePointsMode.None && !lineToWrite.Contains ("gen-compact-seq-points")) - lineToWrite = line + ",gen-compact-seq-points"; - } - if (lineToWrite.StartsWith ("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) - haveHttpMessageHandler = true; - - if (!UsingAndroidNETSdk && lineToWrite.StartsWith ("XA_TLS_PROVIDER=", StringComparison.Ordinal)) - haveTlsProvider = true; - - if (lineToWrite.StartsWith ("mono.enable_assembly_preload=", StringComparison.Ordinal)) { - int idx = lineToWrite.IndexOf ('='); - uint val; - if (idx < lineToWrite.Length - 1 && UInt32.TryParse (lineToWrite.Substring (idx + 1), out val)) { - usesAssemblyPreload = idx == 1; - } - continue; - } - if (lineToWrite.StartsWith ("XA_BROKEN_EXCEPTION_TRANSITIONS=", StringComparison.Ordinal)) { - brokenExceptionTransitions = true; - continue; - } + // Even though environment files were potentially parsed in GenerateJavaStubs, we need to do it here again because we might have additional environment + // files (generated by us) which weren't present by the time GeneratJavaStubs ran. + var environmentParser = new EnvironmentFilesParser { + BrokenExceptionTransitions = false, + UsesAssemblyPreload = EnablePreloadAssembliesDefault, + }; + environmentParser.Parse (Environments, sequencePointsMode, UsingAndroidNETSdk, Log); - AddEnvironmentVariableLine (lineToWrite); - } + foreach (string line in environmentParser.EnvironmentVariableLines) { + AddEnvironmentVariableLine (line); } - if (_Debug && !haveLogLevel) { + if (_Debug && !environmentParser.HaveLogLevel) { AddEnvironmentVariable (defaultLogLevel[0], defaultLogLevel[1]); } - if (sequencePointsMode != SequencePointsMode.None && !haveMonoDebug) { + if (sequencePointsMode != SequencePointsMode.None && !environmentParser.HaveMonoDebug) { AddEnvironmentVariable (defaultMonoDebug[0], defaultMonoDebug[1]); } - if (!havebuildId) + if (!environmentParser.HavebuildId) AddEnvironmentVariable ("XAMARIN_BUILD_ID", BuildId); - if (!haveHttpMessageHandler) { + if (!environmentParser.HaveHttpMessageHandler) { if (HttpClientHandlerType == null) AddEnvironmentVariable (defaultHttpMessageHandler[0], defaultHttpMessageHandler[1]); else AddEnvironmentVariable ("XA_HTTP_CLIENT_HANDLER_TYPE", HttpClientHandlerType.Trim ()); } - if (!UsingAndroidNETSdk && !haveTlsProvider) { + if (!UsingAndroidNETSdk && !environmentParser.HaveTlsProvider) { if (TlsProvider == null) AddEnvironmentVariable (defaultTlsProvider[0], defaultTlsProvider[1]); else AddEnvironmentVariable ("XA_TLS_PROVIDER", TlsProvider.Trim ()); } - if (!haveMonoGCParams) { + if (!environmentParser.HaveMonoGCParams) { if (EnableSGenConcurrent) AddEnvironmentVariable ("MONO_GC_PARAMS", "major=marksweep-conc"); else @@ -298,17 +261,15 @@ void AddEnvironment () }; int assemblyCount = 0; + bool enableMarshalMethods = EnableMarshalMethods; HashSet archAssemblyNames = null; -#if ENABLE_MARSHAL_METHODS - var uniqueAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase); -#endif + HashSet uniqueAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase); Action updateAssemblyCount = (ITaskItem assembly) => { string assemblyName = Path.GetFileName (assembly.ItemSpec); -#if ENABLE_MARSHAL_METHODS if (!uniqueAssemblyNames.Contains (assemblyName)) { uniqueAssemblyNames.Add (assemblyName); } -#endif + if (!UseAssemblyStore) { assemblyCount++; return; @@ -415,11 +376,11 @@ void AddEnvironment () IsBundledApp = IsBundledApplication, UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, - UsesAssemblyPreload = usesAssemblyPreload, + UsesAssemblyPreload = environmentParser.UsesAssemblyPreload, MonoAOTMode = aotMode.ToString ().ToLowerInvariant (), AotEnableLazyLoad = AndroidAotEnableLazyLoad, AndroidPackageName = AndroidPackageName, - BrokenExceptionTransitions = brokenExceptionTransitions, + BrokenExceptionTransitions = environmentParser.BrokenExceptionTransitions, PackageNamingPolicy = pnp, BoundExceptionType = boundExceptionType, InstantRunEnabled = InstantRunEnabled, @@ -439,19 +400,25 @@ void AddEnvironment () JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token, JniRemappingReplacementTypeCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementTypeCount, JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, + MarshalMethodsEnabled = EnableMarshalMethods, }; appConfigAsmGen.Init (); -#if ENABLE_MARSHAL_METHODS - var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (GenerateJavaStubs.MarshalMethodsRegisterTaskKey, RegisteredTaskObjectLifetime.Build); - var marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator ( - assemblyCount, - uniqueAssemblyNames, - marshalMethodsState?.MarshalMethods, - Log - ); + var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (GenerateJavaStubs.MarshalMethodsRegisterTaskKey, RegisteredTaskObjectLifetime.Build); + MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen; + + if (enableMarshalMethods) { + marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator ( + assemblyCount, + uniqueAssemblyNames, + marshalMethodsState?.MarshalMethods, + Log + ); + } else { + marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (uniqueAssemblyNames); + } marshalMethodsAsmGen.Init (); -#endif + foreach (string abi in SupportedAbis) { string targetAbi = abi.ToLowerInvariant (); string environmentBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"environment.{targetAbi}"); @@ -465,13 +432,12 @@ void AddEnvironment () sw.Flush (); Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath); } -#if ENABLE_MARSHAL_METHODS + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { marshalMethodsAsmGen.Write (targetArch, sw, marshalMethodsLlFilePath); sw.Flush (); Files.CopyIfStreamChanged (sw.BaseStream, marshalMethodsLlFilePath); } -#endif } void AddEnvironmentVariable (string name, string value) @@ -505,7 +471,7 @@ void GetRequiredTokens (string assemblyFilePath, out int android_runtime_jnienv_ } if (android_runtime_jnienv_class_token == -1 || jnienv_initialize_method_token == -1 || jnienv_registerjninatives_method_token == -1) { - throw new InvalidOperationException ($"Unable to find the required Android.Runtime.JNIEnv method tokens"); + throw new InvalidOperationException ($"Unable to find the required Android.Runtime.JNIEnvInit method tokens"); } } @@ -556,7 +522,7 @@ bool TypeMatches (TypeDefinition td) } string name = reader.GetString (td.Name); - if (String.Compare (name, "JNIEnv", StringComparison.Ordinal) != 0) { + if (String.Compare (name, "JNIEnvInit", StringComparison.Ordinal) != 0) { return false; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs index 62ccbf3bfbc..d826e94110c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs @@ -15,10 +15,7 @@ public class PrepareAbiItems : AndroidTask const string EnvBase = "environment"; const string CompressedAssembliesBase = "compressed_assemblies"; const string JniRemappingBase = "jni_remap"; - -#if ENABLE_MARSHAL_METHODS const string MarshalMethodsBase = "marshal_methods"; -#endif public override string TaskPrefix => "PAI"; @@ -57,10 +54,8 @@ public override bool RunTask () baseName = CompressedAssembliesBase; } else if (String.Compare ("jniremap", Mode, StringComparison.OrdinalIgnoreCase) == 0) { baseName = JniRemappingBase; -#if ENABLE_MARSHAL_METHODS } else if (String.Compare ("marshal_methods", Mode, StringComparison.OrdinalIgnoreCase) == 0) { baseName = MarshalMethodsBase; -#endif } else { Log.LogError ($"Unknown mode: {Mode}"); return false; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs index 0f19a5a6926..f561c36c590 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs @@ -24,6 +24,66 @@ namespace Xamarin.Android.Build.Tests [Parallelizable (ParallelScope.Children)] public partial class BuildTest2 : BaseTest { + static object [] MarshalMethodsDefaultStatusSource = new object [] { + new object[] { + /* isClassic */ false, + /* isRelease */ true, + /* marshalMethodsEnabled */ true, + }, + new object[] { + /* isClassic */ false, + /* isRelease */ false, + /* marshalMethodsEnabled */ false, + }, + new object[] { + /* isClassic */ true, + /* isRelease */ false, + /* marshalMethodsEnabled */ false, + }, + new object[] { + /* isClassic */ true, + /* isRelease */ true, + /* marshalMethodsEnabled */ false, + }, + }; + + [Test] + [TestCaseSource (nameof (MarshalMethodsDefaultStatusSource))] + public void MarshalMethodsDefaultEnabledStatus (bool isClassic, bool isRelease, bool marshalMethodsEnabled) + { + if (isClassic) { + if (Builder.UseDotNet) { + Assert.Ignore ("Ignored in .NET6+"); + return; + } + } else if (!Builder.UseDotNet) { + Assert.Ignore ("Ignored in classic"); + return; + } + + var abis = new [] { "armeabi-v7a", "x86" }; + var proj = new XamarinAndroidApplicationProject { + IsRelease = isRelease + }; + + proj.SetAndroidSupportedAbis (abis); + + using (var b = CreateApkBuilder ()) { + Assert.IsTrue (b.Build (proj), "Build should have succeeded."); + Assert.IsTrue ( + StringAssertEx.ContainsText (b.LastBuildOutput, $"_AndroidUseMarshalMethods = {marshalMethodsEnabled}"), + $"The '_AndroidUseMarshalMethods' MSBuild property should have had the value of '{marshalMethodsEnabled}'" + ); + + string objPath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (objPath, String.Join (";", abis), true); + EnvironmentHelper.ApplicationConfig app_config = EnvironmentHelper.ReadApplicationConfig (envFiles); + + Assert.That (app_config, Is.Not.Null, "application_config must be present in the environment files"); + Assert.AreEqual (app_config.marshal_methods_enabled, marshalMethodsEnabled, $"Marshal methods enabled status should be '{marshalMethodsEnabled}', but it was '{app_config.marshal_methods_enabled}'"); + } + } + [Test] public void CompressedWithoutLinker () { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index e78f6267066..d7d53020f59 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -87,6 +87,7 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto new [] { "Java.Interop.dll", "Mono.Android.dll", + "Mono.Android.Runtime.dll", "rc.bin", "System.Console.dll", "System.Private.CoreLib.dll", diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 0f6e7fd910d..c3c30ebae39 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -49,6 +49,7 @@ public sealed class ApplicationConfig public bool jni_add_native_method_registration_attribute_present; public bool have_runtime_config_blob; public bool have_assemblies_blob; + public bool marshal_methods_enabled; public byte bound_stream_io_exception_type; public uint package_naming_policy; public uint environment_variable_count; @@ -66,7 +67,7 @@ public sealed class ApplicationConfig public string android_package_name = String.Empty; } - const uint ApplicationConfigFieldCount = 25; + const uint ApplicationConfigFieldCount = 26; const string ApplicationConfigSymbolName = "application_config"; const string AppEnvironmentVariablesSymbolName = "app_environment_variables"; @@ -255,77 +256,82 @@ static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile) ret.have_assemblies_blob = ConvertFieldToBool ("have_assemblies_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 10: // bound_stream_io_exception_type: byte / .byte + case 10: // marshal_methods_enabled: bool / .byte + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.marshal_methods_enabled = ConvertFieldToBool ("marshal_methods_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); + break; + + case 11: // bound_stream_io_exception_type: byte / .byte AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); ret.bound_stream_io_exception_type = ConvertFieldToByte ("bound_stream_io_exception_type", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 11: // package_naming_policy: uint32_t / .word | .long + case 12: // package_naming_policy: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.package_naming_policy = ConvertFieldToUInt32 ("package_naming_policy", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 12: // environment_variable_count: uint32_t / .word | .long + case 13: // environment_variable_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.environment_variable_count = ConvertFieldToUInt32 ("environment_variable_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 13: // system_property_count: uint32_t / .word | .long + case 14: // system_property_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 14: // number_of_assemblies_in_apk: uint32_t / .word | .long + case 15: // number_of_assemblies_in_apk: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_assemblies_in_apk = ConvertFieldToUInt32 ("number_of_assemblies_in_apk", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 15: // bundled_assembly_name_width: uint32_t / .word | .long + case 16: // bundled_assembly_name_width: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.bundled_assembly_name_width = ConvertFieldToUInt32 ("bundled_assembly_name_width", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 16: // number_of_assembly_store_files: uint32_t / .word | .long + case 17: // number_of_assembly_store_files: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_assembly_store_files = ConvertFieldToUInt32 ("number_of_assembly_store_files", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 17: // number_of_dso_cache_entries: uint32_t / .word | .long + case 18: // number_of_dso_cache_entries: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 18: // android_runtime_jnienv_class_token: uint32_t / .word | .long + case 19: // android_runtime_jnienv_class_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("android_runtime_jnienv_class_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 19: // jnienv_initialize_method_token: uint32_t / .word | .long + case 20: // jnienv_initialize_method_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_initialize_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 20: // jnienv_registerjninatives_method_token: uint32_t / .word | .long + case 21: // jnienv_registerjninatives_method_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 21: // jni_remapping_replacement_type_count: uint32_t / .word | .long + case 22: // jni_remapping_replacement_type_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jni_remapping_replacement_type_count = ConvertFieldToUInt32 ("jni_remapping_replacement_type_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 22: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long + case 23: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jni_remapping_replacement_method_index_entry_count = ConvertFieldToUInt32 ("jni_remapping_replacement_method_index_entry_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 23: // mono_components_mask: uint32_t / .word | .long + case 24: // mono_components_mask: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 24: // android_package_name: string / [pointer type] + case 25: // android_package_name: string / [pointer type] Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); pointers.Add (field [1].Trim ()); break; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index 880a58bb615..33cfb16c606 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -5,10 +5,13 @@ "Size": 3032 }, "assemblies/Java.Interop.dll": { - "Size": 59188 + "Size": 59187 }, "assemblies/Mono.Android.dll": { - "Size": 88549 + "Size": 88390 + }, + "assemblies/Mono.Android.Runtime.dll": { + "Size": 5776 }, "assemblies/rc.bin": { "Size": 1182 @@ -26,10 +29,10 @@ "Size": 3388 }, "assemblies/System.Private.CoreLib.dll": { - "Size": 533485 + "Size": 533617 }, "assemblies/System.Runtime.dll": { - "Size": 3498 + "Size": 3655 }, "assemblies/System.Runtime.InteropServices.dll": { "Size": 3770 @@ -47,13 +50,13 @@ "Size": 62998 }, "assemblies/UnnamedProject.dll": { - "Size": 3560 + "Size": 3559 }, "classes.dex": { - "Size": 360720 + "Size": 18612 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 425416 + "Size": 433600 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3073392 @@ -68,16 +71,16 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 10216 + "Size": 16896 }, "META-INF/BNDLTOOL.RSA": { "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 3339 + "Size": 3446 }, "META-INF/MANIFEST.MF": { - "Size": 3212 + "Size": 3319 }, "res/drawable-hdpi-v4/icon.png": { "Size": 4762 @@ -104,5 +107,5 @@ "Size": 1904 } }, - "PackageSize": 2791978 + "PackageSize": 2730619 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc index ac323f1cebb..e814dd13ded 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc @@ -5,10 +5,10 @@ "Size": 2604 }, "assemblies/Java.Interop.dll": { - "Size": 68921 + "Size": 69028 }, "assemblies/Mono.Android.dll": { - "Size": 259883 + "Size": 264788 }, "assemblies/mscorlib.dll": { "Size": 769017 @@ -17,13 +17,13 @@ "Size": 28199 }, "assemblies/System.dll": { - "Size": 9179 + "Size": 9180 }, "assemblies/UnnamedProject.dll": { - "Size": 2877 + "Size": 2881 }, "classes.dex": { - "Size": 362940 + "Size": 370480 }, "lib/arm64-v8a/libmono-btls-shared.so": { "Size": 1613872 @@ -32,7 +32,7 @@ "Size": 750976 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 332128 + "Size": 332880 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 4051864 @@ -41,7 +41,7 @@ "Size": 66184 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 20240 + "Size": 21112 }, "META-INF/ANDROIDD.RSA": { "Size": 1213 @@ -74,5 +74,5 @@ "Size": 1724 } }, - "PackageSize": 4028116 + "PackageSize": 4036308 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index 692414a6c1e..db99dbbb8a5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -5,13 +5,16 @@ "Size": 3568 }, "assemblies/FormsViewGroup.dll": { - "Size": 7114 + "Size": 7314 }, "assemblies/Java.Interop.dll": { - "Size": 66767 + "Size": 66760 }, "assemblies/Mono.Android.dll": { - "Size": 442556 + "Size": 444587 + }, + "assemblies/Mono.Android.Runtime.dll": { + "Size": 5776 }, "assemblies/mscorlib.dll": { "Size": 3859 @@ -104,7 +107,7 @@ "Size": 16635 }, "assemblies/System.Runtime.dll": { - "Size": 3647 + "Size": 3808 }, "assemblies/System.Runtime.InteropServices.dll": { "Size": 3770 @@ -143,16 +146,16 @@ "Size": 1860 }, "assemblies/UnnamedProject.dll": { - "Size": 117252 + "Size": 117251 }, "assemblies/Xamarin.AndroidX.Activity.dll": { "Size": 5872 }, "assemblies/Xamarin.AndroidX.AppCompat.AppCompatResources.dll": { - "Size": 5889 + "Size": 6118 }, "assemblies/Xamarin.AndroidX.AppCompat.dll": { - "Size": 112363 + "Size": 112654 }, "assemblies/Xamarin.AndroidX.CardView.dll": { "Size": 6596 @@ -161,13 +164,13 @@ "Size": 16407 }, "assemblies/Xamarin.AndroidX.Core.dll": { - "Size": 96515 + "Size": 96736 }, "assemblies/Xamarin.AndroidX.DrawerLayout.dll": { - "Size": 14065 + "Size": 14237 }, "assemblies/Xamarin.AndroidX.Fragment.dll": { - "Size": 39713 + "Size": 39952 }, "assemblies/Xamarin.AndroidX.Legacy.Support.Core.UI.dll": { "Size": 5924 @@ -185,16 +188,16 @@ "Size": 12476 }, "assemblies/Xamarin.AndroidX.RecyclerView.dll": { - "Size": 84474 + "Size": 84803 }, "assemblies/Xamarin.AndroidX.SavedState.dll": { "Size": 4869 }, "assemblies/Xamarin.AndroidX.SwipeRefreshLayout.dll": { - "Size": 10180 + "Size": 10391 }, "assemblies/Xamarin.AndroidX.ViewPager.dll": { - "Size": 17786 + "Size": 18044 }, "assemblies/Xamarin.Forms.Core.dll": { "Size": 528450 @@ -209,13 +212,13 @@ "Size": 60774 }, "assemblies/Xamarin.Google.Android.Material.dll": { - "Size": 39911 + "Size": 40159 }, "classes.dex": { - "Size": 3473196 + "Size": 3090268 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 425416 + "Size": 433600 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3073392 @@ -230,7 +233,7 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 99640 + "Size": 324464 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -344,13 +347,13 @@ "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 79628 + "Size": 79735 }, "META-INF/com.google.android.material_material.version": { "Size": 10 }, "META-INF/MANIFEST.MF": { - "Size": 79501 + "Size": 79608 }, "META-INF/proguard/androidx-annotations.pro": { "Size": 339 @@ -1985,5 +1988,5 @@ "Size": 341228 } }, - "PackageSize": 8078308 + "PackageSize": 8057909 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc index 4498b397cca..aea0929a8d2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc @@ -8,10 +8,10 @@ "Size": 7215 }, "assemblies/Java.Interop.dll": { - "Size": 69945 + "Size": 70056 }, "assemblies/Mono.Android.dll": { - "Size": 570605 + "Size": 571897 }, "assemblies/Mono.Security.dll": { "Size": 68433 @@ -32,7 +32,7 @@ "Size": 110693 }, "assemblies/System.Numerics.dll": { - "Size": 15682 + "Size": 15683 }, "assemblies/System.Runtime.Serialization.dll": { "Size": 186660 @@ -44,7 +44,7 @@ "Size": 395656 }, "assemblies/UnnamedProject.dll": { - "Size": 116896 + "Size": 116897 }, "assemblies/Xamarin.AndroidX.Activity.dll": { "Size": 7697 @@ -65,7 +65,7 @@ "Size": 131930 }, "assemblies/Xamarin.AndroidX.DrawerLayout.dll": { - "Size": 15426 + "Size": 15425 }, "assemblies/Xamarin.AndroidX.Fragment.dll": { "Size": 43135 @@ -95,7 +95,7 @@ "Size": 11272 }, "assemblies/Xamarin.AndroidX.ViewPager.dll": { - "Size": 19423 + "Size": 19424 }, "assemblies/Xamarin.Forms.Core.dll": { "Size": 524736 @@ -113,7 +113,7 @@ "Size": 43497 }, "classes.dex": { - "Size": 3475144 + "Size": 3482568 }, "lib/arm64-v8a/libmono-btls-shared.so": { "Size": 1613872 @@ -122,7 +122,7 @@ "Size": 750976 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 332128 + "Size": 332880 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 4051864 @@ -131,7 +131,7 @@ "Size": 66184 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 105288 + "Size": 106160 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -1883,5 +1883,5 @@ "Size": 341040 } }, - "PackageSize": 9545886 + "PackageSize": 9549982 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs index 369bc794b50..e5a1405b127 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs @@ -34,6 +34,7 @@ sealed class ApplicationConfig public bool jni_add_native_method_registration_attribute_present; public bool have_runtime_config_blob; public bool have_assemblies_blob; + public bool marshal_methods_enabled; public byte bound_stream_io_exception_type; public uint package_naming_policy; public uint environment_variable_count; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 979d705c162..16bc2daa34a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -173,6 +173,7 @@ sealed class XamarinAndroidBundledAssembly public MonoComponent MonoComponents { get; set; } public PackageNamingPolicy PackageNamingPolicy { get; set; } public List NativeLibraries { get; set; } + public bool MarshalMethodsEnabled { get; set; } public ApplicationConfigNativeAssemblyGenerator (IDictionary environmentVariables, IDictionary systemProperties, TaskLoggingHelper log) { @@ -201,6 +202,7 @@ public override void Init () jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent, have_runtime_config_blob = HaveRuntimeConfigBlob, have_assemblies_blob = HaveAssemblyStore, + marshal_methods_enabled = MarshalMethodsEnabled, bound_stream_io_exception_type = (byte)BoundExceptionType, package_naming_policy = (uint)PackageNamingPolicy, environment_variable_count = (uint)(environmentVariables == null ? 0 : environmentVariables.Count * 2), diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs new file mode 100644 index 00000000000..c663d3eaa8d --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tasks +{ + class EnvironmentFilesParser + { + public bool BrokenExceptionTransitions { get; set; } + public bool HavebuildId { get; private set; } + public bool HaveHttpMessageHandler { get; private set; } + public bool HaveLogLevel { get; private set; } + public bool HaveMonoDebug { get; private set; } + public bool HaveMonoGCParams { get; private set; } + public bool HaveTlsProvider { get; private set; } + public bool UsesAssemblyPreload { get; set; } + public List EnvironmentVariableLines { get; } = new List (); + + public bool AreBrokenExceptionTransitionsEnabled (ITaskItem[] environments) + { + foreach (ITaskItem env in environments ?? Array.Empty ()) { + foreach (string line in File.ReadLines (env.ItemSpec)) { + if (IsBrokenExceptionTransitionsLine (line.Trim ())) { + return true; + } + } + } + + return false; + } + + public void Parse (ITaskItem[] environments, SequencePointsMode sequencePointsMode, bool usingAndroidNETSdk, TaskLoggingHelper log) + { + foreach (ITaskItem env in environments ?? Array.Empty ()) { + foreach (string line in File.ReadLines (env.ItemSpec)) { + var lineToWrite = line.Trim (); + if (lineToWrite.StartsWith ("MONO_LOG_LEVEL=", StringComparison.Ordinal)) + HaveLogLevel = true; + if (lineToWrite.StartsWith ("MONO_GC_PARAMS=", StringComparison.Ordinal)) { + HaveMonoGCParams = true; + if (lineToWrite.IndexOf ("bridge-implementation=old", StringComparison.Ordinal) >= 0) { + log.LogCodedWarning ("XA2000", Properties.Resources.XA2000_gcParams_bridgeImpl); + } + } + if (lineToWrite.StartsWith ("XAMARIN_BUILD_ID=", StringComparison.Ordinal)) + HavebuildId = true; + if (lineToWrite.StartsWith ("MONO_DEBUG=", StringComparison.Ordinal)) { + HaveMonoDebug = true; + if (sequencePointsMode != SequencePointsMode.None && !lineToWrite.Contains ("gen-compact-seq-points")) + lineToWrite = line + ",gen-compact-seq-points"; + } + if (lineToWrite.StartsWith ("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) + HaveHttpMessageHandler = true; + + if (!usingAndroidNETSdk && lineToWrite.StartsWith ("XA_TLS_PROVIDER=", StringComparison.Ordinal)) + HaveTlsProvider = true; + + if (lineToWrite.StartsWith ("mono.enable_assembly_preload=", StringComparison.Ordinal)) { + int idx = lineToWrite.IndexOf ('='); + uint val; + if (idx < lineToWrite.Length - 1 && UInt32.TryParse (lineToWrite.Substring (idx + 1), out val)) { + UsesAssemblyPreload = idx == 1; + } + continue; + } + if (IsBrokenExceptionTransitionsLine (lineToWrite)) { + BrokenExceptionTransitions = true; + continue; + } + + EnvironmentVariableLines.Add (lineToWrite); + } + } + } + + bool IsBrokenExceptionTransitionsLine (string lineToWrite) => lineToWrite.StartsWith ("XA_BROKEN_EXCEPTION_TRANSITIONS=", StringComparison.Ordinal); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index d5537296a7e..85b9662c524 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -26,7 +26,7 @@ abstract partial class LlvmIrGenerator /// /// Writes the function definition up to the opening curly brace /// - public void WriteFunctionStart (LlvmIrFunction function) + public void WriteFunctionStart (LlvmIrFunction function, string? comment = null) { if (function == null) { throw new ArgumentNullException (nameof (function)); @@ -38,6 +38,12 @@ public void WriteFunctionStart (LlvmIrFunction function) } Output.WriteLine (); + if (!String.IsNullOrEmpty (comment)) { + foreach (string line in comment.Split ('\n')) { + WriteCommentLine (line); + } + } + if (attributes != null) { WriteCommentLine ($"Function attributes: {attributes.Render ()}"); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 8af1d3878ea..acd69f2d7ea 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -1,4 +1,3 @@ -#if ENABLE_MARSHAL_METHODS using System; using System.Collections.Generic; using System.IO; @@ -8,12 +7,20 @@ using Microsoft.Build.Utilities; using Mono.Cecil; using Mono.Cecil.Cil; -using Xamarin.Android.Tools; namespace Xamarin.Android.Tasks { class MarshalMethodsAssemblyRewriter { + sealed class AssemblyImports + { + public MethodReference MonoUnhandledExceptionMethod; + public TypeReference SystemException; + public MethodReference UnhandledExceptionMethod; + public CustomAttribute UnmanagedCallersOnlyAttribute; + public MethodReference WaitForBridgeProcessingMethod; + } + IDictionary> methods; ICollection uniqueAssemblies; IDictionary > assemblyPaths; @@ -27,19 +34,55 @@ public MarshalMethodsAssemblyRewriter (IDictionary targetAssemblyPaths, bool brokenExceptionTransitions) { + if (resolver == null) { + throw new ArgumentNullException (nameof (resolver)); + } + + if (targetAssemblyPaths == null) { + throw new ArgumentNullException (nameof (targetAssemblyPaths)); + } + + if (targetAssemblyPaths.Count == 0) { + throw new ArgumentException ("must contain at least one target path", nameof (targetAssemblyPaths)); + } + + AssemblyDefinition? monoAndroidRuntime = resolver.Resolve ("Mono.Android.Runtime"); + if (monoAndroidRuntime == null) { + throw new InvalidOperationException ($"Internal error: unable to load the Mono.Android.Runtime assembly"); + } + + TypeDefinition runtime = FindType (monoAndroidRuntime, "Android.Runtime.AndroidRuntimeInternal", required: true)!; + MethodDefinition waitForBridgeProcessingMethod = FindMethod (runtime, "WaitForBridgeProcessing", required: true)!; + + TypeDefinition androidEnvironment = FindType (monoAndroidRuntime, "Android.Runtime.AndroidEnvironmentInternal", required: true)!; + MethodDefinition unhandledExceptionMethod = FindMethod (androidEnvironment, "UnhandledException", required: true)!; + + TypeDefinition runtimeNativeMethods = FindType (monoAndroidRuntime, "Android.Runtime.RuntimeNativeMethods", required: true); + MethodDefinition monoUnhandledExceptionMethod = FindMethod (runtimeNativeMethods, "monodroid_debugger_unhandled_exception", required: true); + + AssemblyDefinition corlib = resolver.Resolve ("System.Private.CoreLib"); + TypeDefinition systemException = FindType (corlib, "System.Exception", required: true); + MethodDefinition unmanagedCallersOnlyAttributeCtor = GetUnmanagedCallersOnlyAttributeConstructor (resolver); - var unmanagedCallersOnlyAttributes = new Dictionary (); + var assemblyImports = new Dictionary (); foreach (AssemblyDefinition asm in uniqueAssemblies) { - unmanagedCallersOnlyAttributes.Add (asm, CreateImportedUnmanagedCallersOnlyAttribute (asm, unmanagedCallersOnlyAttributeCtor)); + var imports = new AssemblyImports { + MonoUnhandledExceptionMethod = asm.MainModule.ImportReference (monoUnhandledExceptionMethod), + SystemException = asm.MainModule.ImportReference (systemException), + UnhandledExceptionMethod = asm.MainModule.ImportReference (unhandledExceptionMethod), + UnmanagedCallersOnlyAttribute = CreateImportedUnmanagedCallersOnlyAttribute (asm, unmanagedCallersOnlyAttributeCtor), + WaitForBridgeProcessingMethod = asm.MainModule.ImportReference (waitForBridgeProcessingMethod), + }; + + assemblyImports.Add (asm, imports); } - Console.WriteLine (); - Console.WriteLine ("Modifying assemblies"); + log.LogDebugMessage ("Rewriting assemblies for marshal methods support"); var processedMethods = new Dictionary (StringComparer.Ordinal); - Console.WriteLine ("Adding the [UnmanagedCallersOnly] attribute to native callback methods and removing unneeded fields+methods"); foreach (IList methodList in methods.Values) { foreach (MarshalMethodEntry method in methodList) { string fullNativeCallbackName = method.NativeCallback.FullName; @@ -48,33 +91,29 @@ public void Rewrite (DirectoryAssemblyResolver resolver) continue; } - Console.WriteLine ($"\t{fullNativeCallbackName} (token: 0x{method.NativeCallback.MetadataToken.RID:x})"); - Console.WriteLine ($"\t Top type == '{method.DeclaringType}'"); - Console.WriteLine ($"\t NativeCallback == '{method.NativeCallback}'"); - Console.WriteLine ($"\t Connector == '{method.Connector}'"); - Console.WriteLine ($"\t method.NativeCallback.CustomAttributes == {ToStringOrNull (method.NativeCallback.CustomAttributes)}"); - Console.WriteLine ($"\t method.Connector.DeclaringType == {ToStringOrNull (method.Connector?.DeclaringType)}"); - Console.WriteLine ($"\t method.Connector.DeclaringType.Methods == {ToStringOrNull (method.Connector.DeclaringType?.Methods)}"); - Console.WriteLine ($"\t method.CallbackField == {ToStringOrNull (method.CallbackField)}"); - Console.WriteLine ($"\t method.CallbackField?.DeclaringType == {ToStringOrNull (method.CallbackField?.DeclaringType)}"); - Console.WriteLine ($"\t method.CallbackField?.DeclaringType.Fields == {ToStringOrNull (method.CallbackField?.DeclaringType?.Fields)}"); - - if (method.NeedsBlittableWorkaround) { - method.NativeCallbackWrapper = GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); - } else { - method.NativeCallback.CustomAttributes.Add (unmanagedCallersOnlyAttributes [method.NativeCallback.Module.Assembly]); + method.NativeCallbackWrapper = GenerateWrapper (method, assemblyImports, brokenExceptionTransitions); + if (method.Connector != null) { + if (method.Connector.IsStatic && method.Connector.IsPrivate) { + log.LogDebugMessage ($"Removing connector method {method.Connector.FullName}"); + method.Connector.DeclaringType?.Methods?.Remove (method.Connector); + } else { + log.LogWarning ($"NOT removing connector method {method.Connector.FullName} because it's either not static or not private"); + } } - method.Connector?.DeclaringType?.Methods?.Remove (method.Connector); - method.CallbackField?.DeclaringType?.Fields?.Remove (method.CallbackField); + if (method.CallbackField != null) { + if (method.CallbackField.IsStatic && method.CallbackField.IsPrivate) { + log.LogDebugMessage ($"Removing callback delegate backing field {method.CallbackField.FullName}"); + method.CallbackField.DeclaringType?.Fields?.Remove (method.CallbackField); + } else { + log.LogWarning ($"NOT removing callback field {method.CallbackField.FullName} because it's either not static or not private"); + } + } processedMethods.Add (fullNativeCallbackName, method.NativeCallback); } } - Console.WriteLine (); - Console.WriteLine ("Rewriting assemblies"); - var newAssemblyPaths = new List (); foreach (AssemblyDefinition asm in uniqueAssemblies) { foreach (string path in GetAssemblyPaths (asm)) { @@ -82,8 +121,13 @@ public void Rewrite (DirectoryAssemblyResolver resolver) WriteSymbols = (File.Exists (path + ".mdb") || File.Exists (Path.ChangeExtension (path, ".pdb"))), }; + string output = $"{path}.new"; - Console.WriteLine ($"\t{asm.Name} => {output}"); + log.LogDebugMessage ($"Writing new version of assembly: {output}"); + + // TODO: this should be used eventually, but it requires that all the types are reloaded from the assemblies before typemaps are generated + // since Cecil doesn't update the MVID in the already loaded types + //asm.MainModule.Mvid = Guid.NewGuid (); asm.Write (output, writerParams); newAssemblyPaths.Add (output); } @@ -92,119 +136,172 @@ public void Rewrite (DirectoryAssemblyResolver resolver) // Replace old versions of the assemblies only after we've finished rewriting without issues, otherwise leave the new // versions around. foreach (string path in newAssemblyPaths) { - string target = Path.Combine (Path.GetDirectoryName (path), Path.GetFileNameWithoutExtension (path)); - MoveFile (path, target); + string? pdb = null; + string? mdb = null; - string source = Path.ChangeExtension (path, ".pdb"); + string source = Path.ChangeExtension (Path.Combine (Path.GetDirectoryName (path), Path.GetFileNameWithoutExtension (path)), ".pdb"); if (File.Exists (source)) { - target = Path.ChangeExtension (Path.Combine (Path.GetDirectoryName (source), Path.GetFileNameWithoutExtension (source)), ".pdb"); - - MoveFile (source, target); + pdb = source; } source = $"{path}.mdb"; if (File.Exists (source)) { - target = Path.ChangeExtension (path, ".mdb"); - MoveFile (source, target); + mdb = source; } - } - Console.WriteLine (); - Console.WriteLine ("Method tokens:"); - foreach (IList methodList in methods.Values) { - foreach (MarshalMethodEntry method in methodList) { - Console.WriteLine ($"\t{method.NativeCallback.FullName} (token: 0x{method.NativeCallback.MetadataToken.RID:x})"); + foreach (string targetPath in targetAssemblyPaths) { + string target = Path.Combine (targetPath, Path.GetFileNameWithoutExtension (path)); + CopyFile (path, target); + + if (!String.IsNullOrEmpty (pdb)) { + target = Path.ChangeExtension (Path.Combine (targetPath, Path.GetFileNameWithoutExtension (pdb)), ".pdb"); + CopyFile (pdb, target); + } + + if (!String.IsNullOrEmpty (mdb)) { + target = Path.Combine (targetPath, Path.ChangeExtension (Path.GetFileName (path), ".mdb")); + CopyFile (mdb, target); + } } + + RemoveFile (path); + RemoveFile (pdb); + RemoveFile (mdb); } - void MoveFile (string source, string target) + void CopyFile (string source, string target) { - Console.WriteLine ($"Moving '{source}' => '{target}'"); - Files.CopyIfChanged (source, target); - try { - File.Delete (source); - } catch (Exception ex) { - log.LogWarning ($"Unable to delete source file '{source}' when moving it to '{target}'"); - log.LogDebugMessage (ex.ToString ()); - } + log.LogDebugMessage ($"Copying rewritten assembly: {source} -> {target}"); + File.Copy (source, target, true); } - string ToStringOrNull (object? o) + void RemoveFile (string? path) { - if (o == null) { - return "'null'"; + if (String.IsNullOrEmpty (path) || !File.Exists (path)) { + return; } - return o.ToString (); + try { + File.Delete (path); + } catch (Exception ex) { + log.LogWarning ($"Unable to delete source file '{path}'"); + log.LogDebugMessage (ex.ToString ()); + } } } - MethodDefinition GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary unmanagedCallersOnlyAttributes) + MethodDefinition GenerateWrapper (MarshalMethodEntry method, Dictionary assemblyImports, bool brokenExceptionTransitions) { - Console.WriteLine ($"\t Generating blittable wrapper for: {method.NativeCallback.FullName}"); MethodDefinition callback = method.NativeCallback; + AssemblyImports imports = assemblyImports [callback.Module.Assembly]; string wrapperName = $"{callback.Name}_mm_wrapper"; TypeReference retType = MapToBlittableTypeIfNecessary (callback.ReturnType, out bool returnTypeMapped); - bool hasReturnValue = String.Compare ("System.Void", retType.FullName, StringComparison.Ordinal) != 0; + bool hasReturnValue = String.Compare ("System.Void", callback.ReturnType.FullName, StringComparison.Ordinal) != 0; var wrapperMethod = new MethodDefinition (wrapperName, callback.Attributes, retType); + callback.DeclaringType.Methods.Add (wrapperMethod); - wrapperMethod.CustomAttributes.Add (unmanagedCallersOnlyAttributes [callback.Module.Assembly]); + wrapperMethod.CustomAttributes.Add (imports.UnmanagedCallersOnlyAttribute); MethodBody body = wrapperMethod.Body; - int nparam = 0; + body.InitLocals = true; + + VariableDefinition? retval = null; + if (hasReturnValue) { + retval = new VariableDefinition (retType); + body.Variables.Add (retval); + } + + body.Instructions.Add (Instruction.Create (OpCodes.Call, imports.WaitForBridgeProcessingMethod)); + var exceptionHandler = new ExceptionHandler (ExceptionHandlerType.Catch) { + CatchType = imports.SystemException, + }; + body.ExceptionHandlers.Add (exceptionHandler); + + Instruction? firstTryInstruction = null; + Instruction? inst = null; + uint nparam = 0; foreach (ParameterDefinition pdef in callback.Parameters) { TypeReference newType = MapToBlittableTypeIfNecessary (pdef.ParameterType, out _); wrapperMethod.Parameters.Add (new ParameterDefinition (pdef.Name, pdef.Attributes, newType)); - OpCode ldargOp; - bool paramRef = false; - switch (nparam++) { - case 0: - ldargOp = OpCodes.Ldarg_0; - break; - - case 1: - ldargOp = OpCodes.Ldarg_1; - break; - - case 2: - ldargOp = OpCodes.Ldarg_2; - break; - - case 3: - ldargOp = OpCodes.Ldarg_3; - break; - - default: - ldargOp = OpCodes.Ldarg_S; - paramRef = true; - break; + inst = GetLoadArgInstruction (nparam++, pdef); + if (firstTryInstruction == null) { + firstTryInstruction = inst; } - Instruction ldarg; + body.Instructions.Add (inst); - if (!paramRef) { - ldarg = Instruction.Create (ldargOp); - } else { - ldarg = Instruction.Create (ldargOp, pdef); + if (!pdef.ParameterType.IsBlittable ()) { + GenerateNonBlittableConversion (pdef.ParameterType, newType); } + } - body.Instructions.Add (ldarg); + inst = Instruction.Create (OpCodes.Call, callback); + if (firstTryInstruction == null) { + firstTryInstruction = inst; + } + body.Instructions.Add (inst); - if (!pdef.ParameterType.IsBlittable ()) { - GenerateNonBlittableConversion (pdef.ParameterType, newType); + exceptionHandler.TryStart = firstTryInstruction; + + if (hasReturnValue && !returnTypeMapped) { + body.Instructions.Add (Instruction.Create (OpCodes.Stloc, retval)); + } + + Instruction ret = Instruction.Create (OpCodes.Ret); + Instruction? retValLoadInst = null; + Instruction leaveTarget; + + if (hasReturnValue) { + if (returnTypeMapped) { + GenerateRetValCast (callback.ReturnType, retType); + body.Instructions.Add (Instruction.Create (OpCodes.Stloc, retval)); } + + retValLoadInst = Instruction.Create (OpCodes.Ldloc, retval); + leaveTarget = retValLoadInst; + } else { + leaveTarget = ret; } - body.Instructions.Add (Instruction.Create (OpCodes.Call, callback)); + body.Instructions.Add (Instruction.Create (OpCodes.Leave_S, leaveTarget)); + + var exceptionVar = new VariableDefinition (imports.SystemException); + body.Variables.Add (exceptionVar); + + var catchStartInst = Instruction.Create (OpCodes.Stloc, exceptionVar); + exceptionHandler.HandlerStart = catchStartInst; + + // TryEnd must point to the next instruction after the try block + exceptionHandler.TryEnd = catchStartInst; + + body.Instructions.Add (catchStartInst); + body.Instructions.Add (Instruction.Create (OpCodes.Ldarg_0)); + body.Instructions.Add (Instruction.Create (OpCodes.Ldloc, exceptionVar)); + + if (brokenExceptionTransitions) { + body.Instructions.Add (Instruction.Create (OpCodes.Call, imports.MonoUnhandledExceptionMethod)); + body.Instructions.Add (Instruction.Create (OpCodes.Throw)); + } else { + body.Instructions.Add (Instruction.Create (OpCodes.Call, imports.UnhandledExceptionMethod)); - if (hasReturnValue && returnTypeMapped) { - GenerateRetValCast (callback.ReturnType, retType); + if (hasReturnValue) { + AddSetDefaultValueInstructions (body, retType, retval); + } + } + + body.Instructions.Add (Instruction.Create (OpCodes.Leave_S, leaveTarget)); + + // HandlerEnd must point to the next instruction after the catch block + if (hasReturnValue) { + body.Instructions.Add (retValLoadInst); + exceptionHandler.HandlerEnd = retValLoadInst; + } else { + exceptionHandler.HandlerEnd = ret; } + body.Instructions.Add (ret); - body.Instructions.Add (Instruction.Create (OpCodes.Ret)); - Console.WriteLine ($"\t New method: {wrapperMethod.FullName}"); return wrapperMethod; void GenerateNonBlittableConversion (TypeReference sourceType, TypeReference targetType) @@ -255,6 +352,91 @@ void ThrowUnsupportedType (TypeReference type) } } + void AddSetDefaultValueInstructions (MethodBody body, TypeReference type, VariableDefinition retval) + { + bool supported = false; + + switch (type.FullName) { + case "System.Boolean": + case "System.Byte": + case "System.Int16": + case "System.Int32": + case "System.SByte": + case "System.UInt16": + case "System.UInt32": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_I4_0)); + break; + + case "System.Int64": + case "System.UInt64": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_I4_0)); + body.Instructions.Add (Instruction.Create (OpCodes.Conv_I8)); + break; + + case "System.IntPtr": + case "System.UIntPtr": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_I4_0)); + body.Instructions.Add (Instruction.Create (OpCodes.Conv_I)); + break; + + case "System.Single": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_R4, 0.0F)); + break; + + case "System.Double": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_R8, 0.0)); + break; + } + + if (supported) { + body.Instructions.Add (Instruction.Create (OpCodes.Stloc, retval)); + return; + } + + throw new InvalidOperationException ($"Unsupported type: '{type.FullName}'"); + } + + + Instruction GetLoadArgInstruction (uint nparam, ParameterDefinition pdef) + { + OpCode ldargOp; + bool paramRef = false; + + switch (nparam++) { + case 0: + ldargOp = OpCodes.Ldarg_0; + break; + + case 1: + ldargOp = OpCodes.Ldarg_1; + break; + + case 2: + ldargOp = OpCodes.Ldarg_2; + break; + + case 3: + ldargOp = OpCodes.Ldarg_3; + break; + + default: + ldargOp = OpCodes.Ldarg_S; + paramRef = true; + break; + } + + if (!paramRef) { + return Instruction.Create (ldargOp); + } + + return Instruction.Create (ldargOp, pdef); + } + TypeReference MapToBlittableTypeIfNecessary (TypeReference type, out bool typeMapped) { if (type.IsBlittable () || String.Compare ("System.Void", type.FullName, StringComparison.Ordinal) == 0) { @@ -328,6 +510,41 @@ CustomAttribute CreateImportedUnmanagedCallersOnlyAttribute (AssemblyDefinition { return new CustomAttribute (targetAssembly.MainModule.ImportReference (unmanagedCallersOnlyAtributeCtor)); } + + MethodDefinition? FindMethod (TypeDefinition type, string methodName, bool required) + { + log.LogDebugMessage ($"Looking for method '{methodName}' in type {type}"); + foreach (MethodDefinition method in type.Methods) { + log.LogDebugMessage ($" method: {method.Name}"); + if (String.Compare (methodName, method.Name, StringComparison.Ordinal) == 0) { + log.LogDebugMessage (" match!"); + return method; + } + } + + if (required) { + throw new InvalidOperationException ($"Internal error: required method '{methodName}' in type {type} not found"); + } + + return null; + } + + TypeDefinition? FindType (AssemblyDefinition asm, string typeName, bool required) + { + log.LogDebugMessage ($"Looking for type '{typeName}' in assembly '{asm}'"); + foreach (TypeDefinition t in asm.MainModule.Types) { + log.LogDebugMessage ($" checking {t.FullName}"); + if (String.Compare (typeName, t.FullName, StringComparison.Ordinal) == 0) { + log.LogDebugMessage ($" match!"); + return t; + } + } + + if (required) { + throw new InvalidOperationException ($"Internal error: required type '{typeName}' in assembly {asm} not found"); + } + + return null; + } } } -#endif diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 478c52389d0..3a7d4712b3f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -5,14 +5,14 @@ using Java.Interop.Tools.Cecil; using Java.Interop.Tools.JavaCallableWrappers; using Java.Interop.Tools.TypeNameMappings; + using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; using Mono.Cecil; namespace Xamarin.Android.Tasks { -#if ENABLE_MARSHAL_METHODS - public sealed class MarshalMethodEntry + sealed class MarshalMethodEntry { /// /// The "real" native callback, used if it doesn't contain any non-blittable types in its parameters @@ -27,16 +27,17 @@ public sealed class MarshalMethodEntry /// public MethodDefinition? NativeCallbackWrapper { get; set; } public TypeDefinition DeclaringType { get; } - public MethodDefinition Connector { get; } - public MethodDefinition RegisteredMethod { get; } - public MethodDefinition ImplementedMethod { get; } - public FieldDefinition CallbackField { get; } + public MethodDefinition? Connector { get; } + public MethodDefinition? RegisteredMethod { get; } + public MethodDefinition? ImplementedMethod { get; } + public FieldDefinition? CallbackField { get; } public string JniTypeName { get; } public string JniMethodName { get; } public string JniMethodSignature { get; } public bool NeedsBlittableWorkaround { get; } public MethodDefinition NativeCallback => NativeCallbackWrapper ?? nativeCallbackReal; + public bool IsSpecial { get; } public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, MethodDefinition connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, FieldDefinition callbackField, string jniTypeName, @@ -52,6 +53,17 @@ public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition native JniMethodName = EnsureNonEmpty (jniName, nameof (jniName)); JniMethodSignature = EnsureNonEmpty (jniSignature, nameof (jniSignature)); NeedsBlittableWorkaround = needsBlittableWorkaround; + IsSpecial = false; + } + + public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, string jniTypeName, string jniName, string jniSignature) + { + DeclaringType = declaringType ?? throw new ArgumentNullException (nameof (declaringType)); + nativeCallbackReal = nativeCallback ?? throw new ArgumentNullException (nameof (nativeCallback)); + JniTypeName = EnsureNonEmpty (jniTypeName, nameof (jniTypeName)); + JniMethodName = EnsureNonEmpty (jniName, nameof (jniName)); + JniMethodSignature = EnsureNonEmpty (jniSignature, nameof (jniSignature)); + IsSpecial = true; } string EnsureNonEmpty (string s, string argName) @@ -63,11 +75,9 @@ string EnsureNonEmpty (string s, string argName) return s; } } -#endif class MarshalMethodsClassifier : JavaCallableMethodClassifier { -#if ENABLE_MARSHAL_METHODS sealed class ConnectorInfo { public string MethodName { get; } @@ -142,8 +152,7 @@ string MapType (TypeReference typeRef) } if (typeDef.IsEnum) { - // TODO: get the underlying type - return "System.Int32"; + return GetEnumUnderlyingType (typeDef).FullName; } } @@ -155,9 +164,27 @@ string MapType (TypeReference typeRef) return typeName; } + // Android.Graphics.Color is mapped to/from a native `int` + if (String.Compare (typeName, "Android.Graphics.Color", StringComparison.Ordinal) == 0) { + return "System.Int32"; + } + return "System.IntPtr"; } + static TypeReference GetEnumUnderlyingType (TypeDefinition td) + { + var fields = td.Fields; + + for (int i = 0; i < fields.Count; i++) { + var field = fields [i]; + if (!field.IsStatic) + return field.FieldType; + } + + throw new InvalidOperationException ($"Unable to determine underlying type of the '{td.FullName}' enum"); + } + public bool Matches (MethodDefinition method) { if (method.Parameters.Count != paramTypes.Count || !method.IsStatic) { @@ -166,7 +193,7 @@ public bool Matches (MethodDefinition method) } if (String.Compare (returnType, method.ReturnType.FullName, StringComparison.Ordinal) != 0) { - log.LogWarning ($"Method '{method.FullName}' doesn't match native callback signature (invalid return type)"); + log.LogWarning ($"Method '{method.FullName}' doesn't match native callback signature (invalid return type: expected '{returnType}', found '{method.ReturnType.FullName}')"); return false; } @@ -203,23 +230,19 @@ public bool Matches (MethodDefinition method) public ICollection Assemblies => assemblies; public ulong RejectedMethodCount => rejectedMethodCount; public ulong WrappedMethodCount => wrappedMethodCount; -#endif public MarshalMethodsClassifier (TypeDefinitionCache tdCache, DirectoryAssemblyResolver res, TaskLoggingHelper log) { -#if ENABLE_MARSHAL_METHODS this.log = log ?? throw new ArgumentNullException (nameof (log)); this.tdCache = tdCache ?? throw new ArgumentNullException (nameof (tdCache)); resolver = res ?? throw new ArgumentNullException (nameof (tdCache)); marshalMethods = new Dictionary> (StringComparer.Ordinal); assemblies = new HashSet (); typesWithDynamicallyRegisteredMethods = new HashSet (); -#endif } public override bool ShouldBeDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute? registerAttribute) { -#if ENABLE_MARSHAL_METHODS if (registeredMethod == null) { throw new ArgumentNullException (nameof (registeredMethod)); } @@ -237,20 +260,144 @@ public override bool ShouldBeDynamicallyRegistered (TypeDefinition topType, Meth } typesWithDynamicallyRegisteredMethods.Add (topType); -#endif // def ENABLE_MARSHAL_METHODS return true; } -#if ENABLE_MARSHAL_METHODS public bool FoundDynamicallyRegisteredMethods (TypeDefinition type) { return typesWithDynamicallyRegisteredMethods.Contains (type); } + void AddTypeManagerSpecialCaseMethods () + { + const string FullTypeName = "Java.Interop.TypeManager+JavaTypeManager, Mono.Android"; + + AssemblyDefinition monoAndroid = resolver.Resolve ("Mono.Android"); + TypeDefinition? typeManager = monoAndroid?.MainModule.FindType ("Java.Interop.TypeManager"); + TypeDefinition? javaTypeManager = typeManager?.GetNestedType ("JavaTypeManager"); + + if (javaTypeManager == null) { + throw new InvalidOperationException ($"Internal error: unable to find the {FullTypeName} type in the Mono.Android assembly"); + } + + MethodDefinition? nActivate_mm = null; + MethodDefinition? nActivate = null; + + foreach (MethodDefinition method in javaTypeManager.Methods) { + if (nActivate_mm == null && IsMatchingMethod (method, "n_Activate_mm")) { + if (method.GetCustomAttributes ("System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute").Any (cattr => cattr != null)) { + nActivate_mm = method; + } else { + log.LogWarning ($"Method '{method.FullName}' isn't decorated with the UnmanagedCallersOnly attribute"); + continue; + } + } + + if (nActivate == null && IsMatchingMethod (method, "n_Activate")) { + nActivate = method; + } + + if (nActivate_mm != null && nActivate != null) { + break; + } + } + + if (nActivate_mm == null) { + ThrowMissingMethod ("nActivate_mm"); + } + + if (nActivate == null) { + ThrowMissingMethod ("nActivate"); + } + + string? jniTypeName = null; + foreach (CustomAttribute cattr in javaTypeManager.GetCustomAttributes ("Android.Runtime.RegisterAttribute")) { + if (cattr.ConstructorArguments.Count != 1) { + log.LogDebugMessage ($"[Register] attribute on type '{FullTypeName}' is expected to have 1 constructor argument, found {cattr.ConstructorArguments.Count}"); + continue; + } + + jniTypeName = (string)cattr.ConstructorArguments[0].Value; + if (!String.IsNullOrEmpty (jniTypeName)) { + break; + } + } + + string? jniMethodName = null; + string? jniSignature = null; + foreach (CustomAttribute cattr in nActivate.GetCustomAttributes ("Android.Runtime.RegisterAttribute")) { + if (cattr.ConstructorArguments.Count != 3) { + log.LogDebugMessage ($"[Register] attribute on method '{nActivate.FullName}' is expected to have 3 constructor arguments, found {cattr.ConstructorArguments.Count}"); + continue; + } + + jniMethodName = (string)cattr.ConstructorArguments[0].Value; + jniSignature = (string)cattr.ConstructorArguments[1].Value; + + if (!String.IsNullOrEmpty (jniMethodName) && !String.IsNullOrEmpty (jniSignature)) { + break; + } + } + + bool missingInfo = false; + if (String.IsNullOrEmpty (jniTypeName)) { + missingInfo = true; + log.LogDebugMessage ($"Failed to obtain Java type name from the [Register] attribute on type '{FullTypeName}'"); + } + + if (String.IsNullOrEmpty (jniMethodName)) { + missingInfo = true; + log.LogDebugMessage ($"Failed to obtain Java method name from the [Register] attribute on method '{nActivate.FullName}'"); + } + + if (String.IsNullOrEmpty (jniSignature)) { + missingInfo = true; + log.LogDebugMessage ($"Failed to obtain Java method signature from the [Register] attribute on method '{nActivate.FullName}'"); + } + + if (missingInfo) { + throw new InvalidOperationException ($"Missing information while constructing marshal method for the '{nActivate_mm.FullName}' method"); + } + + var entry = new MarshalMethodEntry (javaTypeManager, nActivate_mm, jniTypeName, jniMethodName, jniSignature); + marshalMethods.Add (".:!SpEcIaL:Java.Interop.TypeManager+JavaTypeManager::n_Activate_mm", new List { entry }); + + void ThrowMissingMethod (string name) + { + throw new InvalidOperationException ($"Internal error: unable to find the '{name}' method in the '{FullTypeName}' type"); + } + + bool IsMatchingMethod (MethodDefinition method, string name) + { + if (String.Compare (name, method.Name, StringComparison.Ordinal) != 0) { + return false; + } + + if (!method.IsStatic) { + log.LogWarning ($"Method '{method.FullName}' is not static"); + return false; + } + + if (!method.IsPrivate) { + log.LogWarning ($"Method '{method.FullName}' is not private"); + return false; + } + + return true; + } + } + + /// + /// Adds MarshalMethodEntry for each method that won't be returned by the JavaInterop type scanner, mostly + /// used for hand-written methods (e.g. Java.Interop.TypeManager+JavaTypeManager::n_Activate) + /// + public void AddSpecialCaseMethods () + { + AddTypeManagerSpecialCaseMethods (); + } + bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute registerAttribute) { - Console.WriteLine ($"Classifying:\n\tmethod: {implementedMethod.FullName}\n\tregistered method: {registeredMethod.FullName})\n\tAttr: {registerAttribute.AttributeType.FullName} (parameter count: {registerAttribute.ConstructorArguments.Count})"); - Console.WriteLine ($"\tTop type: {topType.FullName}\n\tManaged type: {registeredMethod.DeclaringType.FullName}, {registeredMethod.DeclaringType.GetPartialAssemblyName (tdCache)}"); if (registerAttribute.ConstructorArguments.Count != 3) { log.LogWarning ($"Method '{registeredMethod.FullName}' will be registered dynamically, not enough arguments to the [Register] attribute to generate marshal method."); return true; @@ -258,8 +405,6 @@ bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registere var connector = new ConnectorInfo ((string)registerAttribute.ConstructorArguments[2].Value); - Console.WriteLine ($"\tconnector: {connector.MethodName} (from spec: '{(string)registerAttribute.ConstructorArguments[2].Value}')"); - if (IsStandardHandler (topType, connector, registeredMethod, implementedMethod, jniName: (string)registerAttribute.ConstructorArguments[0].Value, jniSignature: (string)registerAttribute.ConstructorArguments[1].Value)) { return false; } @@ -269,7 +414,6 @@ bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registere return true; } - // TODO: Probably should check if all the methods and fields are private and static - only then it is safe(ish) to remove them bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, string jniName, string jniSignature) { const string HandlerNameStart = "Get"; @@ -291,7 +435,6 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD string delegateFieldName = $"cb_{Char.ToLowerInvariant (callbackNameCore[0])}{callbackNameCore.Substring (1)}"; TypeDefinition connectorDeclaringType = connector.AssemblyName == null ? registeredMethod.DeclaringType : FindType (resolver.Resolve (connector.AssemblyName), connector.TypeName); - Console.WriteLine ($"\tconnector name: {connectorName}\n\tnative callback name: {nativeCallbackName}\n\tdelegate field name: {delegateFieldName}"); MethodDefinition connectorMethod = FindMethod (connectorDeclaringType, connectorName); if (connectorMethod == null) { @@ -355,13 +498,7 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD // method.CallbackField?.DeclaringType == 'null' // method.CallbackField?.DeclaringType.Fields == 'null' - Console.WriteLine ($"##G1: {implementedMethod.DeclaringType.FullName} -> {JavaNativeTypeManager.ToJniName (implementedMethod.DeclaringType, tdCache)}"); - Console.WriteLine ($"##G1: top type: {topType.FullName} -> {JavaNativeTypeManager.ToJniName (topType, tdCache)}"); - Console.WriteLine ($"##G1: connectorMethod: {connectorMethod?.FullName}"); - Console.WriteLine ($"##G1: delegateField: {delegateField?.FullName}"); - StoreMethod ( - connectorName, registeredMethod, new MarshalMethodEntry ( topType, @@ -468,7 +605,8 @@ bool LogReasonWhyAndReturnFailure (string why) void WarnWhy (string why) { - log.LogWarning ($"Method '{method.FullName}' {why}. A workaround is required, this may make the application slower"); + // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers + log.LogDebugMessage ($"Method '{method.FullName}' {why}. A workaround is required, this may make the application slower"); } } @@ -545,7 +683,7 @@ FieldDefinition FindField (TypeDefinition type, string fieldName, bool lookForIn return FindField (tdCache.Resolve (type.BaseType), fieldName, lookForInherited); } - void StoreMethod (string connectorName, MethodDefinition registeredMethod, MarshalMethodEntry entry) + void StoreMethod (MethodDefinition registeredMethod, MarshalMethodEntry entry) { string typeName = registeredMethod.DeclaringType.FullName.Replace ('/', '+'); string key = $"{typeName}, {registeredMethod.DeclaringType.GetPartialAssemblyName (tdCache)}\t{registeredMethod.Name}"; @@ -571,6 +709,5 @@ void StoreAssembly (AssemblyDefinition asm) assemblies.Add (asm); } -#endif } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index e34a3395f44..d83208521e5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -1,16 +1,10 @@ -#if ENABLE_MARSHAL_METHODS using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection.Metadata; using System.Text; -using Java.Interop.Tools.TypeNameMappings; -using Java.Interop.Tools.JavaCallableWrappers; - using Microsoft.Android.Build.Tasks; -using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Xamarin.Android.Tasks.LLVMIR; @@ -205,6 +199,20 @@ sealed class MarshalMethodName List methods; List> classes = new List> (); + readonly bool generateEmptyCode; + + /// + /// Constructor to be used ONLY when marshal methods are DISABLED + /// + public MarshalMethodsNativeAssemblyGenerator (ICollection uniqueAssemblyNames) + { + this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); + generateEmptyCode = true; + } + + /// + /// Constructor to be used ONLY when marshal methods are ENABLED + /// public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, TaskLoggingHelper logger) { this.numberOfAssembliesInApk = numberOfAssembliesInApk; @@ -212,15 +220,12 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl this.marshalMethods = marshalMethods; this.logger = logger ?? throw new ArgumentNullException (nameof (logger)); - if (uniqueAssemblyNames.Count != numberOfAssembliesInApk) { - throw new InvalidOperationException ("Internal error: number of assemblies in the apk doesn't match the number of unique assembly names"); - } + generateEmptyCode = false; } public override void Init () { - Console.WriteLine ($"Marshal methods count: {marshalMethods?.Count ?? 0}"); - if (marshalMethods == null || marshalMethods.Count == 0) { + if (generateEmptyCode || marshalMethods == null || marshalMethods.Count == 0) { return; } @@ -267,11 +272,8 @@ public override void Init () continue; } - Console.WriteLine ($"Overloaded MM: {mmiList[0].NativeSymbolName}"); foreach (MarshalMethodInfo overloadedMethod in mmiList) { - Console.WriteLine ($" implemented in: {overloadedMethod.Method.DeclaringType.FullName} ({overloadedMethod.Method.RegisteredMethod.FullName})"); overloadedMethod.NativeSymbolName = MakeNativeSymbolName (overloadedMethod.Method, useFullNativeSignature: true); - Console.WriteLine ($" new native symbol name: {overloadedMethod.NativeSymbolName}"); } } @@ -307,7 +309,6 @@ string MakeNativeSymbolName (MarshalMethodEntry entry, bool useFullNativeSignatu sb.Append (MangleForJni ($"n_{entry.JniMethodName}")); if (useFullNativeSignature) { - Console.WriteLine (" Using FULL signature"); string signature = entry.JniMethodSignature; if (signature.Length < 2) { ThrowInvalidSignature (signature, "must be at least two characters long"); @@ -339,20 +340,10 @@ void ThrowInvalidSignature (string signature, string reason) void ProcessAndAddMethod (List allMethods, MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses, Dictionary> overloadedNativeSymbolNames) { - Console.WriteLine ("marshal method:"); - Console.WriteLine ($" top type: {entry.DeclaringType.FullName} (token: 0x{entry.DeclaringType.MetadataToken.ToUInt32 ():x})"); - Console.WriteLine ($" registered method: [{entry.RegisteredMethod.DeclaringType.FullName}] {entry.RegisteredMethod.FullName}"); - Console.WriteLine ($" implemented method: [{entry.ImplementedMethod.DeclaringType.FullName}] {entry.ImplementedMethod.FullName}"); - Console.WriteLine ($" native callback: {entry.NativeCallback.FullName} (token: 0x{entry.NativeCallback.MetadataToken.ToUInt32 ():x})"); - Console.WriteLine ($" native callback wrapper: {entry.NativeCallbackWrapper}"); - Console.WriteLine ($" connector: {entry.Connector.FullName}"); - Console.WriteLine ($" JNI name: {entry.JniMethodName}"); - Console.WriteLine ($" JNI signature: {entry.JniMethodSignature}"); - CecilMethodDefinition nativeCallback = entry.NativeCallback; string nativeSymbolName = MakeNativeSymbolName (entry, useFullNativeSignature); string klass = $"{nativeCallback.DeclaringType.FullName}, {nativeCallback.Module.Assembly.FullName}"; - Console.WriteLine ($" klass == {klass}"); + if (!seenClasses.TryGetValue (klass, out int classIndex)) { classIndex = classes.Count; seenClasses.Add (klass, classIndex); @@ -365,25 +356,14 @@ void ProcessAndAddMethod (List allMethods, MarshalMethodEntry classes.Add (new StructureInstance (mc)); } - Console.WriteLine (" about to parse JNI sig"); - (Type returnType, List? parameters) = ParseJniSignature (entry.JniMethodSignature, entry.ImplementedMethod); - Console.WriteLine (" parsed!"); + // Methods with `IsSpecial == true` are "synthetic" methods - they contain only the callback reference + (Type returnType, List? parameters) = ParseJniSignature (entry.JniMethodSignature, entry.IsSpecial ? entry.NativeCallback : entry.ImplementedMethod); var method = new MarshalMethodInfo (entry, returnType, nativeSymbolName: nativeSymbolName, classIndex); if (parameters != null && parameters.Count > 0) { method.Parameters.AddRange (parameters); } - Console.WriteLine ($" Generated native symbol: {method.NativeSymbolName}"); - Console.WriteLine ($" Parsed return type: {returnType}"); - if (method.Parameters.Count > 0) { - Console.WriteLine (" Parsed parameters:"); - foreach (LlvmIrFunctionParameter p in method.Parameters) { - Console.WriteLine ($" {p.Type} {p.Name}"); - } - } - Console.WriteLine (); - if (!overloadedNativeSymbolNames.TryGetValue (method.NativeSymbolName, out List overloadedMethods)) { overloadedMethods = new List (); overloadedNativeSymbolNames.Add (method.NativeSymbolName, overloadedMethods); @@ -395,19 +375,19 @@ void ProcessAndAddMethod (List allMethods, MarshalMethodEntry string MangleForJni (string name) { - Console.WriteLine ($" mangling '{name}'"); var sb = new StringBuilder (); foreach (char ch in name) { switch (ch) { - case '_': - sb.Append ("_1"); - break; - case '/': + case '.': sb.Append ('_'); break; + case '_': + sb.Append ("_1"); + break; + case ';': sb.Append ("_2"); break; @@ -416,16 +396,12 @@ string MangleForJni (string name) sb.Append ("_3"); break; - case '$': - sb.Append ("_00024"); - break; - default: - if ((int)ch > 127) { + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) { + sb.Append (ch); + } else { sb.Append ("_0"); sb.Append (((int)ch).ToString ("x04")); - } else { - sb.Append (ch); } break; } @@ -477,10 +453,8 @@ string MangleForJni (string name) Type? JniTypeToManaged (char jniType) { - Console.WriteLine ($" turning JNI type '{jniType}' into managed type"); if (jniSimpleTypeMap.TryGetValue (jniType, out Type managedType)) { idx++; - Console.WriteLine ($" will return {managedType}"); return managedType; } @@ -489,24 +463,21 @@ string MangleForJni (string name) } if (jniType == '[') { - Console.WriteLine (" an array"); idx++; jniType = signature[idx]; if (jniArrayTypeMap.TryGetValue (jniType, out managedType)) { if (jniType == 'L') { - Console.WriteLine (" skipping"); JavaClassToManaged (justSkip: true); } else { idx++; } - Console.WriteLine ($" will return {managedType}"); + return managedType; } throw new InvalidOperationException ($"Unsupported JNI array type '{jniType}' at index {idx} of signature '{signature}'"); } - Console.WriteLine (" returning NULL managed type"); return null; } @@ -609,17 +580,19 @@ protected override void Write (LlvmIrGenerator generator) generator.WriteArray (mm_class_names, "mm_class_names", "Names of classes in which marshal methods reside"); var uniqueMethods = new Dictionary (); - foreach (MarshalMethodInfo mmi in methods) { - string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); - if (!asmNameToIndex.TryGetValue (asmName, out uint idx)) { - throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to cache array index"); - } + if (!generateEmptyCode && methods != null) { + foreach (MarshalMethodInfo mmi in methods) { + string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); + if (!asmNameToIndex.TryGetValue (asmName, out uint idx)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to cache array index"); + } - ulong id = ((ulong)idx << 32) | (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); - if (uniqueMethods.ContainsKey (id)) { - continue; + ulong id = ((ulong)idx << 32) | (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); + if (uniqueMethods.ContainsKey (id)) { + continue; + } + uniqueMethods.Add (id, mmi); } - uniqueMethods.Add (id, mmi); } MarshalMethodName name; @@ -672,7 +645,7 @@ void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) { - if (methods == null || methods.Count == 0) { + if (generateEmptyCode || methods == null || methods.Count == 0) { return; } @@ -713,7 +686,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll parameters: method.Parameters ); - generator.WriteFunctionStart (func); + generator.WriteFunctionStart (func, $"Method: {nativeCallback.FullName}\nAssembly: {nativeCallback.Module.Assembly.Name}"); LlvmIrFunctionLocalVariable callbackVariable1 = generator.EmitLoadInstruction (func, backingFieldRef, "cb1"); var callbackVariable1Ref = new LlvmIrVariableReference (callbackVariable1, isGlobal: false); @@ -816,6 +789,7 @@ void WriteClassCache (LlvmIrGenerator generator) generator.WriteStructureArray (marshalMethodsClass, classes, LlvmIrVariableOptions.GlobalWritable, "marshal_methods_class_cache"); } + // TODO: this should probably be moved to a separate writer, since not only marshal methods use the cache void WriteAssemblyImageCache (LlvmIrGenerator generator, out Dictionary asmNameToIndex) { bool is64Bit = generator.Is64Bit; @@ -834,6 +808,7 @@ void WriteHashes () where T: struct { var hashes = new Dictionary (); uint index = 0; + foreach (string name in uniqueAssemblyNames) { string clippedName = Path.GetFileNameWithoutExtension (name); ulong hashFull = HashName (name, is64Bit); @@ -874,4 +849,3 @@ void WriteHashes () where T: struct } } } -#endif diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsState.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsState.cs index 85063dc7dbe..66bf0fdba2e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsState.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsState.cs @@ -1,4 +1,3 @@ -#if ENABLE_MARSHAL_METHODS using System; using System.Collections.Generic; @@ -14,4 +13,3 @@ public MarshalMethodsState (IDictionary> marsh } } } -#endif diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index b2ac9098779..ae4a7e2eb2e 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -22,10 +22,6 @@ $(NoWarn);CA1310 - - $(DefineConstants);ENABLE_MARSHAL_METHODS - - $(MicrosoftAndroidSdkOutDir)pdb2mdb.exe @@ -408,6 +404,9 @@ ApplicationRegistration.java + + JavaInteropTypeManager.java + diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index ff4261ab71c..dc0f72bf029 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -41,6 +41,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + @@ -331,6 +332,10 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. false true True + True + False + <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False + <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods) @@ -1350,7 +1355,7 @@ because xbuild doesn't support framework reference assemblies. <_AndroidJarAndDexDirectory Condition=" '$(UsingAndroidNETSdk)' != 'True' ">$(TargetFrameworkDirectory) <_AndroidJarAndDexDirectory Condition=" '$(UsingAndroidNETSdk)' == 'True' ">$(_XATargetFrameworkDirectories) - + @@ -1371,19 +1376,28 @@ because xbuild doesn't support framework reference assemblies. ResourceName="MonoRuntimeProvider.Bundled.20.java" OutputPath="$(_AndroidIntermediateJavaSourceDirectory)mono\MonoRuntimeProvider.java" /> + - + + - + @@ -1462,12 +1476,16 @@ because xbuild doesn't support framework reference assemblies. <_ManifestOutput Condition=" '$(AndroidManifestMerger)' == 'legacy' ">$(IntermediateOutputPath)android\AndroidManifest.xml <_ManifestOutput Condition=" '$(AndroidManifestMerger)' != 'legacy' ">$(IntermediateOutputPath)AndroidManifest.xml + <_LinkingEnabled Condition=" '$(AndroidLinkMode)' != 'None'">True + <_LinkingEnabled Condition=" '$(AndroidLinkMode)' == 'None'">False + <_HaveMultipleRIDs Condition=" '$(RuntimeIdentifiers)' != '' ">True + <_HaveMultipleRIDs Condition=" '$(RuntimeIdentifiers)' == '' ">False <_MergedManifestDocuments Condition=" '$(AndroidManifestMerger)' == 'legacy' " Include="@(ExtractedManifestDocuments)" /> @@ -1503,7 +1521,12 @@ because xbuild doesn't support framework reference assemblies. SupportedAbis="@(_BuildTargetAbis)" SupportedOSPlatformVersion="$(SupportedOSPlatformVersion)" SkipJniAddNativeMethodRegistrationAttributeScan="$(_SkipJniAddNativeMethodRegistrationAttributeScan)" - CheckedBuild="$(_AndroidCheckedBuild)"> + CheckedBuild="$(_AndroidCheckedBuild)" + EnableMarshalMethods="$(_AndroidUseMarshalMethods)" + LinkingEnabled="$(_LinkingEnabled)" + HaveMultipleRIDs="$(_HaveMultipleRIDs)" + IntermediateOutputDirectory="$(IntermediateOutputPath)" + Environments="@(AndroidEnvironment);@(LibraryEnvironments)"> @@ -1594,7 +1617,6 @@ because xbuild doesn't support framework reference assemblies. + + + + + + <_GeneratePackageManagerJavaDependsOn> _GenerateJavaStubs; + _RunAotForAllRIDs; _ManifestMerger; _ConvertCustomView; $(_AfterConvertCustomView); - _GenerateEnvironmentFiles; _AddStaticResources; $(_AfterAddStaticResources); _PrepareAssemblies; @@ -1710,6 +1743,7 @@ because xbuild doesn't support framework reference assemblies. RuntimeConfigBinFilePath="$(_BinaryRuntimeConfigPath)" UsingAndroidNETSdk="$(UsingAndroidNETSdk)" UseAssemblyStore="$(AndroidUseAssemblyStore)" + EnableMarshalMethods="$(_AndroidUseMarshalMethods)" > diff --git a/src/java-runtime/java-runtime.csproj b/src/java-runtime/java-runtime.csproj index 175397c9410..60a41f213a2 100644 --- a/src/java-runtime/java-runtime.csproj +++ b/src/java-runtime/java-runtime.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Mono.Android/java/mono/android/app/IntentService.java b/src/java-runtime/java/mono/android/app/IntentService.java similarity index 100% rename from src/Mono.Android/java/mono/android/app/IntentService.java rename to src/java-runtime/java/mono/android/app/IntentService.java diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 41577f4f55a..7f45848792b 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -50,8 +50,6 @@ option(STRIP_DEBUG "Strip debugging information when linking" ${STRIP_DEBUG_DEFA option(DISABLE_DEBUG "Disable the built-in debugging code" OFF) option(USE_CCACHE "Use ccache, if found, to speed up recompilation" ${CCACHE_OPTION_DEFAULT}) -set(ENABLE_MARSHAL_METHODS False) - if((MINGW OR NOT WIN32) AND USE_CCACHE) if(CMAKE_CXX_COMPILER MATCHES "/ccache/") message(STATUS "ccache: compiler already uses ccache") @@ -276,9 +274,6 @@ add_compile_definitions(HAVE_CONFIG_H) add_compile_definitions(_REENTRANT) add_compile_definitions(JI_DLL_EXPORT) add_compile_definitions(MONO_DLL_EXPORT) -if(ENABLE_MARSHAL_METHODS) - add_compile_definitions(ENABLE_MARSHAL_METHODS) -endif() if(ENABLE_NET) add_compile_definitions(NET) @@ -476,9 +471,6 @@ endif() set(XAMARIN_INTERNAL_API_LIB xa-internal-api${CHECKED_BUILD_INFIX}) set(XAMARIN_DEBUG_APP_HELPER_LIB xamarin-debug-app-helper${CHECKED_BUILD_INFIX}) set(XAMARIN_APP_STUB_LIB xamarin-app) -if(ENABLE_MARSHAL_METHODS) - set(XAMARIN_APP_MARSHALING_LIB xamarin-app-marshaling) -endif() string(TOLOWER ${CMAKE_BUILD_TYPE} XAMARIN_MONO_ANDROID_SUFFIX) set(XAMARIN_MONO_ANDROID_LIB "mono-android${CHECKED_BUILD_INFIX}.${XAMARIN_MONO_ANDROID_SUFFIX}") @@ -497,7 +489,6 @@ set(XAMARIN_MONODROID_SOURCES ${SOURCES_DIR}/logger.cc ${SOURCES_DIR}/jni-remapping.cc ${SOURCES_DIR}/monodroid-glue.cc - ${SOURCES_DIR}/mono-image-loader.cc ${SOURCES_DIR}/osbridge.cc ${SOURCES_DIR}/shared-constants.cc ${SOURCES_DIR}/timezones.cc @@ -515,7 +506,7 @@ if(ANDROID) ${LZ4_SOURCES} ) - if(NOT DEBUG_BUILD AND ENABLE_NET AND ENABLE_MARSHAL_METHODS) + if(NOT DEBUG_BUILD AND ENABLE_NET) list(APPEND XAMARIN_MONODROID_SOURCES ${SOURCES_DIR}/xamarin-android-app-context.cc ) @@ -577,12 +568,6 @@ set(XAMARIN_DEBUG_APP_HELPER_SOURCES ${SOURCES_DIR}/shared-constants.cc ) -if(ENABLE_MARSHAL_METHODS) - set(XAMARIN_APP_MARSHALING_LIB_SOURCES - ${SOURCES_DIR}/xamarin-app-marshaling.cc - ) -endif() - # Build configure_file(jni/host-config.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/host-config.h) @@ -622,20 +607,6 @@ target_link_options( ) if(ANDROID) - if(NOT DEBUG_BUILD AND ENABLE_NET AND ENABLE_MARSHAL_METHODS) - # The marshaling lib is used only when building for devices - add_library( - ${XAMARIN_APP_MARSHALING_LIB} - SHARED - ${XAMARIN_APP_MARSHALING_LIB_SOURCES} - ) - - target_link_libraries( - ${XAMARIN_APP_MARSHALING_LIB} - ${MONOSGEN_LIB_LINK} - ) - endif() - # Only Android builds need to go in separate directories, desktop builds have the same ABI set_target_properties( ${XAMARIN_APP_STUB_LIB} @@ -743,10 +714,3 @@ target_link_libraries( ${XAMARIN_MONO_ANDROID_LIB} ${LINK_LIBS} xamarin-app ) - -# if((NOT DEBUG_BUILD) AND ANDROID AND ENABLE_NET AND ENABLE_MARSHAL_METHODS) -# target_link_libraries( -# ${XAMARIN_MONO_ANDROID_LIB} -# ${XAMARIN_APP_MARSHALING_LIB} -# ) -# endif() diff --git a/src/monodroid/jni/embedded-assemblies.cc b/src/monodroid/jni/embedded-assemblies.cc index 0c4e0bf6986..de453cd2990 100644 --- a/src/monodroid/jni/embedded-assemblies.cc +++ b/src/monodroid/jni/embedded-assemblies.cc @@ -359,7 +359,6 @@ EmbeddedAssemblies::assembly_store_open_from_bundles (dynamic_local_string const& name, MonoAssemblyLoadContextGCHandle alc_gchandle, hash_t name_hash, uint8_t *assembly_data, uint32_t assembly_data_size) noexcept { - log_info (LOG_DEFAULT, "Loading assembly %s; hash 0x%zx", name.get (), name_hash); MonoImageOpenStatus status; MonoImage *image = mono_image_open_from_data_alc ( alc_gchandle, @@ -71,7 +70,6 @@ namespace xamarin::android::internal { force_inline static MonoImage* load (dynamic_local_string const& name, MonoAssemblyLoadContextGCHandle alc_gchandle, uint8_t *assembly_data, uint32_t assembly_data_size) noexcept { - return load (name, alc_gchandle, xxhash::hash (name.get (), name.length ()), assembly_data, assembly_data_size); } #endif // def NET @@ -114,23 +112,18 @@ namespace xamarin::android::internal { force_inline static MonoImage* stash_and_return (MonoImage *image, MonoImageOpenStatus status, [[maybe_unused]] hash_t hash) noexcept { -#if ENABLE_MARSHAL_METHODS - log_info (LOG_DEFAULT, "Stashing image %p; hash 0x%zx", image, hash); -#endif if (image == nullptr || status != MonoImageOpenStatus::MONO_IMAGE_OK) { - log_warn (LOG_ASSEMBLY, "Failed to open assembly image for '%s'. %s", mono_image_strerror (status)); + log_warn (LOG_ASSEMBLY, "Failed to open assembly image. %s", mono_image_strerror (status)); return nullptr; } #if defined (USE_CACHE) ssize_t index = find_index (hash); - log_info (LOG_DEFAULT, "Index matching the hash == %zd", index); if (index < 0) { - // TODO: Warn? + log_warn (LOG_ASSEMBLY, "Failed to look up image index for hash 0x%zx", hash); return image; } - log_info (LOG_DEFAULT, "Stashing"); // We don't need to worry about locking here. Even if we're overwriting an entry just set by another // thread, the image pointer is going to be the same (at least currently, it will change when we have // support for unloadable Assembly Load Contexts) and the actual write operation to the destination is @@ -141,7 +134,7 @@ namespace xamarin::android::internal { } #if defined (USE_CACHE) - static size_t number_of_cache_index_entries; + static inline size_t number_of_cache_index_entries = application_config.number_of_assemblies_in_apk * number_of_assembly_name_forms_in_image_cache;; #endif // def USE_CACHE }; } diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 122bda2c948..def9a263218 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -132,6 +132,7 @@ namespace xamarin::android::internal uint8_t boundExceptionType; int jniAddNativeMethodRegistrationAttributePresent; bool jniRemappingInUse; + bool marshalMethodsEnabled; }; #if defined (NET) diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 516c5ff75ae..590a4ec55cc 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -841,6 +841,10 @@ MonodroidRuntime::mono_runtime_init ([[maybe_unused]] dynamic_local_string (monodroid_debugger_unhandled_exception)); - mono_add_internal_call ("Android.Runtime.JNIEnv::monodroid_unhandled_exception", reinterpret_cast(monodroid_unhandled_exception)); + mono_add_internal_call ("Android.Runtime.RuntimeNativeMethods::monodroid_debugger_unhandled_exception", reinterpret_cast (monodroid_debugger_unhandled_exception)); + mono_add_internal_call ("Android.Runtime.RuntimeNativeMethods::monodroid_unhandled_exception", reinterpret_cast(monodroid_unhandled_exception)); #endif // def NET struct JnienvInitializeArgs init = {}; @@ -1090,6 +1094,7 @@ MonodroidRuntime::init_android_runtime ( init.boundExceptionType = application_config.bound_exception_type; init.jniAddNativeMethodRegistrationAttributePresent = application_config.jni_add_native_method_registration_attribute_present ? 1 : 0; init.jniRemappingInUse = application_config.jni_remapping_replacement_type_count > 0 || application_config.jni_remapping_replacement_method_index_entry_count > 0; + init.marshalMethodsEnabled = application_config.marshal_methods_enabled; // GC threshold is 90% of the max GREF count init.grefGcThreshold = static_cast(androidSystem.get_gref_gc_threshold ()); @@ -1100,22 +1105,22 @@ MonodroidRuntime::init_android_runtime ( Class_getName = env->GetMethodID (init.grefClass, "getName", "()Ljava/lang/String;"); init.Class_forName = env->GetStaticMethodID (init.grefClass, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); - MonoAssembly *assm; + MonoAssembly *mono_android_assembly; + #if defined (NET) - assm = utils.monodroid_load_assembly (default_alc, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); + mono_android_assembly = utils.monodroid_load_assembly (default_alc, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); #else // def NET - assm = utils.monodroid_load_assembly (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); + mono_android_assembly = utils.monodroid_load_assembly (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); #endif // ndef NET - MonoImage *image = mono_assembly_get_image (assm); + MonoImage *mono_android_assembly_image = mono_assembly_get_image (mono_android_assembly); uint32_t i = 0; - for ( ; i < OSBridge::NUM_XA_GC_BRIDGE_TYPES; ++i) { lookup_bridge_info ( #if !defined (NET) domain, #endif // ndef NET - image, + mono_android_assembly_image, &osBridge.get_java_gc_bridge_type (i), &osBridge.get_java_gc_bridge_info (i) ); @@ -1126,18 +1131,18 @@ MonodroidRuntime::init_android_runtime ( if constexpr (is_running_on_desktop) { #if defined (NET) - runtime = mono_class_from_name (image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME); + runtime = mono_class_from_name (mono_android_assembly_image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); #else - runtime = utils.monodroid_get_class_from_image (domain, image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME); + runtime = utils.monodroid_get_class_from_image (domain, mono_android_assembly_image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); #endif // def NET method = mono_class_get_method_from_name (runtime, "Initialize", 1); } else { - runtime = mono_class_get (image, application_config.android_runtime_jnienv_class_token); - method = mono_get_method (image, application_config.jnienv_initialize_method_token, runtime); + runtime = mono_class_get (mono_android_assembly_image, application_config.android_runtime_jnienv_class_token); + method = mono_get_method (mono_android_assembly_image, application_config.jnienv_initialize_method_token, runtime); } - abort_unless (runtime != nullptr, "INTERNAL ERROR: unable to find the Android.Runtime.JNIEnv class!"); - abort_unless (method != nullptr, "INTERNAL ERROR: Unable to find the Android.Runtime.JNIEnv.Initialize method!"); + abort_unless (runtime != nullptr, "INTERNAL ERROR: unable to find the Android.Runtime.JNIEnvInit class!"); + abort_unless (method != nullptr, "INTERNAL ERROR: Unable to find the Android.Runtime.JNIEnvInit.Initialize method!"); MonoAssembly *ji_assm; #if defined (NET) @@ -1158,6 +1163,7 @@ MonodroidRuntime::init_android_runtime ( ); } + MonoError error; /* If running on desktop, we may be swapping in a new Mono.Android image when calling this * so always make sure we have the freshest handle to the method. */ @@ -1165,21 +1171,13 @@ MonodroidRuntime::init_android_runtime ( if constexpr (is_running_on_desktop) { registerType = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5); } else { - registerType = mono_get_method (image, application_config.jnienv_registerjninatives_method_token, runtime); + registerType = mono_get_method (mono_android_assembly_image, application_config.jnienv_registerjninatives_method_token, runtime); #if defined (NET) && defined (ANDROID) - MonoError error; jnienv_register_jni_natives = reinterpret_cast(mono_method_get_unmanaged_callers_only_ftnptr (registerType, &error)); #endif // def NET && def ANDROID } } - abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnv.RegisterJniNatives!"); - - MonoClass *android_runtime_jnienv = runtime; - MonoClassField *bridge_processing_field = mono_class_get_field_from_name (runtime, const_cast ("BridgeProcessing")); - if (android_runtime_jnienv ==nullptr || bridge_processing_field == nullptr) { - log_fatal (LOG_DEFAULT, "INTERNAL_ERROR: Unable to find Android.Runtime.JNIEnv.BridgeProcessing"); - exit (FATAL_EXIT_CANNOT_FIND_JNIENV); - } + abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnvInit.RegisterJniNatives! %s", mono_error_get_message (&error)); jclass lrefLoaderClass = env->GetObjectClass (loader); init.Loader_loadClass = env->GetMethodID (lrefLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); @@ -1198,9 +1196,16 @@ MonodroidRuntime::init_android_runtime ( } #if defined (NET) && defined (ANDROID) - MonoError error; auto initialize = reinterpret_cast (mono_method_get_unmanaged_callers_only_ftnptr (method, &error)); - abort_unless (initialize != nullptr, "Failed to obtain unmanaged-callers-only pointer to the Android.Runtime.JNIEnv.Initialize method"); + if (initialize == nullptr) { + log_fatal (LOG_DEFAULT, "Failed to get pointer to Initialize. Mono error: %s", mono_error_get_message (&error)); + } + + abort_unless ( + initialize != nullptr, + "Failed to obtain unmanaged-callers-only pointer to the Android.Runtime.JNIEnvInit.Initialize method. %s", + mono_error_get_message (&error) + ); initialize (&init); #else // def NET && def ANDROID void *args [] = { @@ -2402,7 +2407,7 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl #endif // ndef NET } -#if defined (RELEASE) && defined (ANDROID) && defined (NET) && ENABLE_MARSHAL_METHODS +#if defined (RELEASE) && defined (ANDROID) && defined (NET) xamarin_app_init (get_function_pointer_at_runtime); #endif // def RELEASE && def ANDROID && def NET startup_in_progress = false; @@ -2495,18 +2500,6 @@ MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring manag total_time_index = internal_timing->start_event (TimingEventKind::RuntimeRegister); } -#if defined (ENABLE_MARSHAL_METHODS) - const char *mt_ptr = env->GetStringUTFChars (managedType, nullptr); - log_info (LOG_DEFAULT, "[TESTING] Registering managed type: '%s'", mt_ptr); - bool ignore = strcmp (mt_ptr, "HelloAndroid.MainActivity, HelloAndroid") == 0; - env->ReleaseStringUTFChars (managedType, mt_ptr); - - if (ignore) { - log_info (LOG_DEFAULT, "[TESTING] This type's registration is ignored"); - return; - } -#endif - jsize managedType_len = env->GetStringLength (managedType); const jchar *managedType_ptr = env->GetStringChars (managedType, nullptr); int methods_len = env->GetStringLength (methods); @@ -2530,7 +2523,7 @@ MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring manag domain = mono_domain_get (); if constexpr (is_running_on_desktop) { - MonoClass *runtime = utils.monodroid_get_class_from_name (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME); + MonoClass *runtime = utils.monodroid_get_class_from_name (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); register_jni_natives = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5); } diff --git a/src/monodroid/jni/osbridge.cc b/src/monodroid/jni/osbridge.cc index d05b0a61947..067e9019da4 100644 --- a/src/monodroid/jni/osbridge.cc +++ b/src/monodroid/jni/osbridge.cc @@ -1143,10 +1143,20 @@ OSBridge::add_monodroid_domain (MonoDomain *domain) * use GC API to allocate memory and thus can't be called from within the GC callback as it causes a deadlock * (the routine allocating the memory waits for the GC round to complete first) */ - MonoClass *jnienv = utils.monodroid_get_class_from_name (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME);; + MonoClass *runtime = utils.monodroid_get_class_from_name ( + domain, +#if defined (NET) + SharedConstants::MONO_ANDROID_RUNTIME_ASSEMBLY_NAME, +#else + SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, +#endif + SharedConstants::ANDROID_RUNTIME_NS_NAME, + SharedConstants::ANDROID_RUNTIME_INTERNAL_CLASS_NAME + ); + node->domain = domain; - node->bridge_processing_field = mono_class_get_field_from_name (jnienv, const_cast ("BridgeProcessing")); - node->jnienv_vtable = mono_class_vtable (domain, jnienv); + node->bridge_processing_field = mono_class_get_field_from_name (runtime, const_cast ("BridgeProcessing")); + node->jnienv_vtable = mono_class_vtable (domain, runtime); node->next = domains_list; domains_list = node; diff --git a/src/monodroid/jni/shared-constants.hh b/src/monodroid/jni/shared-constants.hh index 0f581b1cc04..a88dbcdf569 100644 --- a/src/monodroid/jni/shared-constants.hh +++ b/src/monodroid/jni/shared-constants.hh @@ -17,12 +17,17 @@ namespace xamarin::android::internal class SharedConstants { public: +#if defined (NET) + static constexpr char MONO_ANDROID_RUNTIME_ASSEMBLY_NAME[] = "Mono.Android.Runtime"; +#endif static constexpr char MONO_ANDROID_ASSEMBLY_NAME[] = "Mono.Android"; static constexpr char JAVA_INTEROP_ASSEMBLY_NAME[] = "Java.Interop"; static constexpr char ANDROID_RUNTIME_NS_NAME[] = "Android.Runtime"; + static constexpr char JNIENVINIT_CLASS_NAME[] = "JNIEnvInit"; static constexpr char JNIENV_CLASS_NAME[] = "JNIEnv"; static constexpr char ANDROID_ENVIRONMENT_CLASS_NAME[] = "AndroidEnvironment"; + static constexpr char ANDROID_RUNTIME_INTERNAL_CLASS_NAME[] = "AndroidRuntimeInternal"; static constexpr char DLL_EXTENSION[] = ".dll"; diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index 58eaa69ab82..e54ae912b06 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -1,4 +1,5 @@ #include +#include #include "monodroid-glue-internal.hh" #include "mono-image-loader.hh" @@ -38,7 +39,6 @@ template force_inline void MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { - log_warn (LOG_DEFAULT, __PRETTY_FUNCTION__); log_debug ( LOG_ASSEMBLY, "MM: Trying to look up pointer to method '%s' (token 0x%x) in class '%s' (index %u)", @@ -55,6 +55,9 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas abort (); } + // We need to do that, as Mono APIs cannot be invoked from threads that aren't attached to the runtime. + mono_thread_attach (mono_get_root_domain ()); + // We don't check for valid return values from image loader, class and method lookup because if any // of them fails to find the requested entity, they will return `null`. In consequence, we can pass // these pointers without checking all the way to `mono_method_get_unmanaged_callers_only_ftnptr`, after @@ -76,7 +79,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas target_ptr = ret; } - log_debug (LOG_ASSEMBLY, "Loaded pointer to method %s", mono_method_get_name (method)); + log_debug (LOG_ASSEMBLY, "Loaded pointer to method %s (%p) (mono_image_index == %u; class_index == %u; method_token == 0x%x)", mono_method_full_name (method, true), ret, mono_image_index, class_index, method_token); return; } diff --git a/src/monodroid/jni/xamarin-app-marshaling.cc b/src/monodroid/jni/xamarin-app-marshaling.cc deleted file mode 100644 index 58378b6c5fe..00000000000 --- a/src/monodroid/jni/xamarin-app-marshaling.cc +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include - -#include - -#include -#include -#include - -#include "xamarin-app.hh" - -static get_function_pointer_fn get_function_pointer; - -void xamarin_app_init (get_function_pointer_fn fn) noexcept -{ - get_function_pointer = fn; -} - -using android_app_activity_on_create_bundle_fn = void (*) (JNIEnv *env, jclass klass, jobject savedInstanceState); -static android_app_activity_on_create_bundle_fn android_app_activity_on_create_bundle = nullptr; - -extern "C" JNIEXPORT void -JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv *env, jclass klass, jobject savedInstanceState) noexcept -{ - // log_info (LOG_DEFAULT, "%s (%p, %p, %p)", __PRETTY_FUNCTION__, env, klass, savedInstanceState); - - if (android_app_activity_on_create_bundle == nullptr) { - // void *fn = get_function_pointer ( - // 16 /* Mono.Android.dll index */, - // 0 /* Android.App.Activity index */, - // 0x0600055B /* n_OnCreate_Landroid_os_Bundle_ */ - // ); - - // android_app_activity_on_create_bundle = reinterpret_cast(fn); - - get_function_pointer (16, 0, 0x0600055B, reinterpret_cast(android_app_activity_on_create_bundle)); - } - - android_app_activity_on_create_bundle (env, klass, savedInstanceState); -} - -using android_app_activity_on_create_view_fn = jobject (*) (JNIEnv *env, jclass klass, jobject view, jstring name, jobject context, jobject attrs); -static android_app_activity_on_create_view_fn android_app_activity_on_create_view = nullptr; - -extern "C" JNIEXPORT jobject -JNICALL Java_helloandroid_MainActivity_n_1onCreateView__Landroid_view_View_2Ljava_lang_String_2Landroid_content_Context_2Landroid_util_AttributeSet_2 (JNIEnv *env, jclass klass, jobject view, jstring name, jobject context, jobject attrs) noexcept -{ - // log_info (LOG_DEFAULT, "%s (%p, %p, %p, %p, %p, %p)", __PRETTY_FUNCTION__, env, klass, view, name, context, attrs); - - if (android_app_activity_on_create_view == nullptr) { - get_function_pointer ( - 16 /* Mono.Android.dll index */, - 0 /* Android.App.Activity index */, - 0x06000564 /* n_OnCreateView_Landroid_view_View_Ljava_lang_String_Landroid_content_Context_Landroid_util_AttributeSet_ */, - reinterpret_cast(android_app_activity_on_create_view) - ); - } - - return android_app_activity_on_create_view (env, klass, view, name, context, attrs); -} - -using onDoSomething_fn = jbyte (*) (JNIEnv *env, jclass klass, jint anInt, jlong aLong, jfloat aFloat, jboolean aBool, jchar aChar, jshort aShort, jdouble aDouble); -static onDoSomething_fn onDoSomething = nullptr; - -extern "C" JNIEXPORT jbyte -JNICALL Java_helloandroid_MainActivity_n_1onDoSomething (JNIEnv *env, jclass klass, jint anInt, jlong aLong, jfloat aFloat, jboolean aBool, jchar aChar, jshort aShort, jdouble aDouble) noexcept -{ - if (onDoSomething == nullptr) { - get_function_pointer ( - 16 /* Mono.Android.dll index */, - 0 /* Android.App.Activity index */, - 0x06000564 /* n_OnCreateView_Landroid_view_View_Ljava_lang_String_Landroid_content_Context_Landroid_util_AttributeSet_ */, - reinterpret_cast(onDoSomething) - ); - } - - return onDoSomething (env, klass, anInt, aLong, aFloat, aBool, aChar, aShort, aDouble); -} diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index 23ef37cba33..0843a17d270 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -4,6 +4,7 @@ #include +#include #include #include "monodroid.h" @@ -210,6 +211,7 @@ struct ApplicationConfig bool jni_add_native_method_registration_attribute_present; bool have_runtime_config_blob; bool have_assembly_store; + bool marshal_methods_enabled; uint8_t bound_exception_type; uint32_t package_naming_policy; uint32_t environment_variable_count; diff --git a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj index 07e28916acc..f9a8feab9a3 100644 --- a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj +++ b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj @@ -24,6 +24,7 @@ + diff --git a/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/AndroidManifest.xml b/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/AndroidManifest.xml index aae3d489b4b..4a0f610906d 100644 --- a/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/AndroidManifest.xml +++ b/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/AndroidManifest.xml @@ -1,6 +1,6 @@  - + diff --git a/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/Mono.Android.NET-Tests.csproj b/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/Mono.Android.NET-Tests.csproj index 4543d58f976..40c61020b59 100644 --- a/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/Mono.Android.NET-Tests.csproj +++ b/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/Mono.Android.NET-Tests.csproj @@ -5,6 +5,7 @@ $(DotNetAndroidTargetFramework) + 21 Xamarin.Android.RuntimeTests Exe true @@ -41,6 +42,7 @@ + diff --git a/tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/MainActivity.cs b/tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/MainActivity.cs index 9bfe8087a5d..06bdfccaf49 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/MainActivity.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/MainActivity.cs @@ -7,7 +7,7 @@ namespace Xamarin.Android.RuntimeTests { - [Activity (Label = "runtime", MainLauncher = true, + [Activity (Label = "Mono.Android Tests", MainLauncher = true, Name="xamarin.android.runtimetests.MainActivity")] public partial class MainActivity : TestSuiteActivity {