-
Notifications
You must be signed in to change notification settings - Fork 533
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Xamarin.Android.Build.Tasks] jnimarshalmethod-gen.exe integration (#…
…2153) Context: https://github.com/xamarin/xamarin-android/projects/1 Context: #2138 A "JNI Marshal Method" is a method which the JVM eventually executes when a Java `native` method is invoked. JNI Marshal Methods are currently contained within binding assemblies for all `virtual` methods, e.g. `Java.Lang.Object` contains: partial class Object { static int n_GetHashCode (IntPtr jnienv, IntPtr native__this) { var __this = Object.GetObject<Object> (jnienv, native__this, JniHandleOwnership.DoNotTransfer); return __this.GetHashCode (); } } If a C# class overrides `Java.Lang.Object.GetHashCode()`, then `Object.n_GetHashCode()` will be invoked when Java code calls `value.hashCode()` on an instance of that C# class. JNI Marshal Methods are responsible for marshaling parameters and return types. However, there is one problem with JNI Marshal Methods as currently constructed: they *require* the use of `System.Reflection.Emit`, via `Android.Runtime.JNINativeWrapper.CreateDelegate()`. `Object.n_GetHashCode()` isn't *directly* registered with JNI. Instead, a "wrapper" is generated at runtime, and it's the wrapper which is registered with JNI. The wrapper effectively does: int wrapper_n_GetHashCode (IntPtr jnienv, IntPtr native__this) { try { JNIEnv.WaitForBridgeProcessing(); return n_GetHashCode (jnienv, native__this); } catch (Exception e) when Debugger.IsAttached || !JNIEnv.PropagateExceptions { AndroidEnvironment.UnhandledException (e); } } Previously, the use of `DynamicMethod` and `System.Reflection.Emit` was unavoidable, as we needed to ensure that all exceptions were handled appropriately, no matter which `generator` version was used to generate the JNI Marshal Methods. Enter `jnimarshalmethod-gen.exe`, which is a utility which will generate "complete" JNI Marshal Methods that can be registered directly with JNI, *without* using `JNINativeWrapper` or requiring use of `DynamicMethod`. `jnimarshalmethod-gen.exe` would process the hypothetical C# `GetHashCode()` override and generate the JNI Marshal Method: partial class ExampleObjectSubclass : Java.Lang.Object { public override int GetHashCode () {return 42;} /* generated by `jnimarshalmethod-gen.exe` */ partial class '__<$>_jni_marshal_methods' { public int GetHashCode (IntPtr __jnienv, IntPtr native__this) { var jniTransition = new JniTransition (__jnienv); JniRuntime runtime = default; try { runtime = JniEnvironment.Runtime; var valueManager = runtime.ValueManager; valueManager.WaitForGCBridgeProcessing (); var __this = valueManager.GetValue<ExampleObjectSubclass>(native__this); return __this.GetHashCode (); } catch (Exception e) when (runtime.ExceptionShouldTransitionToJni (ex)) { jniTransition.SetPendingException (ex); } finally { jniTransition.Dispose (); } } } } The eventual hope and intent is that this will improve process startup times, as we'll need to do less work. This is an initial effort, and not yet complete. `jnimarshalmethod-gen.exe` is invoked from the new `_GenerateJniMarshalMethods` target, which uses *xamarin-android*'s mono, *not* a system mono or other managed runtime. This is done so that `$MONO_PATH` can be overridden, allowing "normal" use of System.Reflection *by `JniValueMarshaler` instances* during *build* time, *not* runtime. The `$(AndroidGenerateJniMarshalMethodsAdditionalArguments)` MSBuild property can be used to add additional parameters to the `jnimarshalmethod-gen.exe` invocation. This is useful for debugging, so that options such as `-v`, `-d`, or `--keeptemp` can be used. Enable use of `jnimarshalmethod-gen.exe` for user assemblies and `Mono.Android.dll` by setting `$(AndroidGenerateJniMarshalMethods)` to True. This is currently only supported on non-Windows platforms. Additionally, remove the `NotImplementedException` throws from the `Android.Runtime.AndroidValueManager`. `AndroidValueManager` is now used from the generated marshal methods, which handle primitive arrays. In order to make this whole process work, some additional custom value marshalers are needed. Add custom JNI value marshalers for `Android.Graphics.Color` and `Android.Runtime.IJavaObject`. Additional examples `jnimarshalmethod-gen.exe` output: using System; using Java.Interop; using Android.Runtime; public static void n_setAdapter_Landroid_widget_ListAdapter_ (IntPtr __jnienv, IntPtr __this, IntPtr value) { JniTransition jniTransition = new JniTransition (__jnienv); JniRuntime runtime = default(JniRuntime); try { runtime = JniEnvironment.Runtime; JniRuntime.JniValueManager valueManager = runtime.ValueManager; valueManager.WaitForGCBridgeProcessing (); AbsListView value2 = valueManager.GetValue<AbsListView> (__this); IListAdapter listAdapter2 = value2.Adapter = Java.Interop.JavaConvert.FromJniHandle<IListAdapter> (value, JniHandleOwnership.DoNotTransfer); } catch (Exception ex) when (runtime.ExceptionShouldTransitionToJni (ex)) { jniTransition.SetPendingException (ex); } finally { jniTransition.Dispose (); } } public static IntPtr n_getAdapter (IntPtr __jnienv, IntPtr __this) { JniTransition jniTransition = new JniTransition (__jnienv); JniRuntime runtime = default(JniRuntime); try { runtime = JniEnvironment.Runtime; JniRuntime.JniValueManager valueManager = runtime.ValueManager; valueManager.WaitForGCBridgeProcessing (); AbsListView value = valueManager.GetValue<AbsListView> (__this); IListAdapter adapter = value.Adapter; return JNIEnv.ToLocalJniHandle (adapter); } catch (Exception ex) when (runtime.ExceptionShouldTransitionToJni (ex)) { jniTransition.SetPendingException (ex); return default(IntPtr); } finally { jniTransition.Dispose (); } IntPtr intPtr = default(IntPtr); return intPtr; } Profiling results of the Xamarin.Forms Integration Test running on Pixel 2 XL phone: Old marshaling: 133 1 15 Android.Runtime.JNIEnv:RegisterJniNatives (intptr,int,intptr,intptr,int) 288 2 1 Android.Runtime.JNIEnv:Initialize (Android.Runtime.JnienvInitializeArgs*) New marshaling: 68 1 15 Android.Runtime.JNIEnv:RegisterJniNatives (intptr,int,intptr,intptr,int) 264 2 1 Android.Runtime.JNIEnv:Initialize (Android.Runtime.JnienvInitializeArgs*) Native member registration for a type is ~2x faster and `JNIEnv.Initialize()` is ~20ms faster. Finally, `RunJavaInteropTests` was moved from `@(_RunParallelTestTarget)` item group to the `@(_RunTestTarget)` group, because it overwrites `Java.Runtime.Environment.dll.config`, which is required by `jnimarshalmethod-gen.exe` to run. We need to run these tests after the `.apk` tests. This should be fixed in the future, hopefully by adding and/or fixing the Inputs/Outputs of the relevant targets.
- Loading branch information
1 parent
0d6e65a
commit 1a2eb95
Showing
15 changed files
with
208 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<UsingTask AssemblyFile="$(MSBuildThisFileDirectory)..\..\bin\Build$(Configuration)\xa-prep-tasks.dll" TaskName="Xamarin.Android.BuildTools.PrepTasks.ReplaceFileContents" /> | ||
<Target Name="_CreateJavaInteropDllConfigs" | ||
Inputs="$(XAInstallPrefix)xbuild\Xamarin\Android\Java.Interop.dll;$(JavaInteropSourceDirectory)\src\Java.Runtime.Environment\Java.Runtime.Environment.dll.config" | ||
Outputs="$(XAInstallPrefix)xbuild\Xamarin\Android\Java.Interop.dll.config;$(XAInstallPrefix)xbuild\Xamarin\Android\Java.Runtime.Environment.dll.config"> | ||
<ReadLinesFromFile | ||
File="$(MSBuildThisFileDirectory)java-interop.dllmap"> | ||
<Output TaskParameter="Lines" ItemName="_JavaInteropDllMapContent" /> | ||
</ReadLinesFromFile> | ||
<WriteLinesToFile | ||
File="$(XAInstallPrefix)xbuild\Xamarin\Android\Java.Interop.dll.config" | ||
Lines="<configuration>;@(_JavaInteropDllMapContent);</configuration>" | ||
Overwrite="True" | ||
/> | ||
<PropertyGroup> | ||
<_DllMaps>@(_JavaInteropDllMapContent->'%(Identity)', '%0a ')</_DllMaps> | ||
</PropertyGroup> | ||
<ReplaceFileContents | ||
Condition="Exists('$(JavaInteropSourceDirectory)\src\Java.Runtime.Environment\Java.Runtime.Environment.dll.config')" | ||
SourceFile="$(JavaInteropSourceDirectory)\src\Java.Runtime.Environment\Java.Runtime.Environment.dll.config" | ||
DestinationFile="$(XAInstallPrefix)xbuild\Xamarin\Android\Java.Runtime.Environment.dll.config" | ||
Replacements="<configuration>=<configuration>%0a $(_DllMaps)" | ||
/> | ||
</Target> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule Java.Interop
updated
from 8ee34a to ec2813
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
src/Mono.Android/Android.Runtime/IJavaObjectValueMarshaler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
|
||
using Java.Interop; | ||
using Java.Interop.Expressions; | ||
|
||
namespace Android.Runtime | ||
{ | ||
sealed class IJavaObjectValueMarshaler : JniValueMarshaler<IJavaObject> { | ||
|
||
internal static IJavaObjectValueMarshaler Instance = new IJavaObjectValueMarshaler (); | ||
|
||
public override IJavaObject CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType) | ||
{ | ||
throw new NotImplementedException (); | ||
} | ||
|
||
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (IJavaObject value, ParameterAttributes synchronize) | ||
{ | ||
throw new NotImplementedException (); | ||
} | ||
|
||
public override void DestroyGenericArgumentState (IJavaObject value, ref JniValueMarshalerState state, ParameterAttributes synchronize) | ||
{ | ||
throw new NotImplementedException (); | ||
} | ||
|
||
public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue) | ||
{ | ||
return Expression.Call ( | ||
typeof (JNIEnv), | ||
"ToLocalJniHandle", | ||
null, | ||
sourceValue); | ||
} | ||
|
||
public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type targetType) | ||
{ | ||
var r = Expression.Variable (targetType, sourceValue.Name + "_val"); | ||
context.LocalVariables.Add (r); | ||
context.CreationStatements.Add ( | ||
Expression.Assign (r, | ||
Expression.Call ( | ||
typeof (JavaConvert), | ||
"FromJniHandle", | ||
new[]{targetType}, | ||
sourceValue, | ||
Expression.Field (null, typeof (JniHandleOwnership), "DoNotTransfer")))); | ||
return r; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.