From 2197579478152fbc815eb15195977f808cd6bde4 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 22 Feb 2024 15:01:33 -0500 Subject: [PATCH] [Hello-NativeAOTFromJNI] Add NativeAOT sample (#1153) Context: 28849ec1e6b2400576f4f2a0d1fe7aabfbda2269 Context: bc5bcf4f0ef07aab898f2643d2a25f66512d98ed Context: 25850ba741b9e3d9dbda353c03b80f7ccb4def42 Context: 56955d9ad3952070de3bb1718375b368437f7427 Context: https://github.com/xamarin/java.interop/issues/1157 Context: c6c487b62dab4ffec45e61b09dd43afc89898caf Context: https://github.com/xamarin/xamarin-android/commit/180dd5205ab270bb74bb853754665db9cb5d65f1 Commit 28849ec1 noted: > [JNI][0] supports *two* modes of operation: > > 1. Native code creates the JVM, e.g. via [`JNI_CreateJavaVM()`][1] > > 2. The JVM already exists, and when Java code calls > [`System.loadLibrary()`][3], the JVM calls the > [`JNI_OnLoad()`][2] function on the specified library. > > [0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html > [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#creating_the_vm > [2]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runtime.html#loadLibrary(java.lang.String) > [3]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad Our existing unit tests, e.g. `tests/Java.Interop-Tests`, exercise the first mode of operation. The second mode of operation is how .NET Android executes. Add `samples/Hello-NativeAOTFromJNI`, a new sample which exercises the second mode of operation: 1. `Hello-NativeAOTFromJNI` contains C# methods which use [`UnmanagedCallersOnlyAttribute`][4] to export `JNI_OnLoad()` and various other native symbols for JNI to execute. 2. `dotnet publish Hello-NativeAOTFromJNI.csproj` will use [NativeAOT][5] to produce a native library which can be loaded and used by a desktop JVM. 3. `Hello-NativeAOTFromJNI` references `Java.Base.dll` (bc5bcf4f), and contains a C# `Java.Lang.Object` subclass, which contains a `[JavaCallable]` method. 4. `jcw-gen` is called during the build process to generate Java Callable Wrappers for (3), containing the `[JavaCallable]` method. 5. `jnimarshalmethod-gen` (25850ba7) is called during the build process to generate marshal methods for (3), as `generator` output for JavaInterop1 doesn't contain marshal methods (unlike XAJavaInterop1, used by .NET Android), and NativeAOT doesn't support System.Reflection.Emit. 6. The type in (3) is instantiated *from Java*, and Java invokes the `[JavaCallable]` method. 7. `Hello-NativeAOTFromJNI` also contains Java code including a `main()` method (to start execution from Java) Conceptually straightforward! See `samples/Hello-NativeAOTFromJNI/README.md` for more details. ~~ `$(Standalone)`=true is now the default build config ~~ The `$(Standalone)` build config (c6c487b6) is now enabled by default. This means that neither `Java.Interop.dll` nor `Java.Runtime.Environment.dll` *require* a `java-interop` native library, which (1) was already the case for xamarin-android as of xamarin/xamarin-android@180dd520, and (2) simplifies NativeAOT support immensely. ~~ Avoid Adding Public API ~~ In order to avoid adding new public API to `Java.Interop.dll`, update `Java.Interop.dll` to have `[InternalsVisibleTo]` for `Java.Runtime.Environment.dll`. This allows `JreTypeManager` to use `JniRuntime.UseMarshalMemberBuilder`, without needing to worry about `try`/`catch` blocks. This in turn requires renaming `NativeMethods` within `Java.Runtime.Environment.dll` to `JreNativeMethods`, as `Java.Interop.dll` *also* has a `NativeMethods` type. ~~ NativeAOT Integration ~~ Conceptually straightforward, but not without lots of pitfalls. In order for Java code to call into C# code, there must be a Java Callable Wrapper, which contains Java `native` methods, and those Java `native` methods must be *registered with Java* before they are executed. Method registration involves lots of delegates, which need to be stored in the `JniNativeMethodRegistration.Delegate` field, of type `System.Delegate`. NativeAOT doesn't like this by default. In order to convince NativeAOT to support this, every delegate instance provided to `JniNativeMethodRegistration.Delegate` must be of a delegate type which has [`UnmanagedFunctionPointerAttribute`][6]. This coerces NativeAOT to emit "stubs" for the referenced method, allowing things to work with fewer changes. Linux builds require `-Wl,-soname` to be set manually in order for the resulting `.so` to work properly. ~~ Trimmer Warnings ~~ Fix additional trimmer warnings, a'la xamarin/java.interop#1157. `Type.MakeGenericType()` needs to ensure constructors are preserved: src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs(378): Trim analysis warning IL2068: Java.Interop.JniRuntime.JniValueManager.g__MakeGenericType|31_1(Type,Type[]): 'Java.Interop.JniRuntime.JniValueManager.g__MakeGenericType|31_1(Type,Type[])' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors', 'DynamicallyAccessedMemberTypes.NonPublicConstructors' requirements. The parameter 'type' of method 'Java.Interop.JniRuntime.JniValueManager.g__MakeGenericType|31_1(Type,Type[])' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. `Expression.New()` requires the constructor be kept, so "fake it" by wrapping `Type.GetType()` w/ `[DynamicallyAccessedMembers]`: src/Java.Interop/Java.Interop/JniValueMarshaler.cs(175): Trim analysis warning IL2072: Java.Interop.JniValueMarshaler.CreateSelf(JniValueMarshalerContext,ParameterExpression): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Linq.Expressions.Expression.New(Type)'. The return value of method 'System.Object.GetType()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. `[DynamicallyAccessedMembers]` should be on properties, not getters: src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs(33): Trim analysis warning IL2078: Java.Interop.JniValueMarshalerAttribute.MarshalerType.get: 'Java.Interop.JniValueMarshalerAttribute.MarshalerType.get' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor', 'DynamicallyAccessedMemberTypes.Interfaces' requirements. The field 'Java.Interop.JniValueMarshalerAttribute.k__BackingField' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. Silence use of `Assembly.Location`, when it's optional: src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs(106): warning IL3000: Java.Interop.JreRuntime.CreateJreVM(JreRuntimeOptions): 'System.Reflection.Assembly.Location.get' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs(323): warning IL3000: Java.Interop.JreNativeMethods..cctor(): 'System.Reflection.Assembly.Location.get' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. ~~ Future Work ~~ Make `JniRuntime.UseMarshalMemberBuilder` public for .NET 9, and remove the `InternalsVisibleTo` from `Java.Interop.dll` to `Java.Runtime.Environment.dll`. `generator` should be updated to apply `UnmanagedFunctionPointerAttribute` onto all `_JniMarshal_*` delegate type declarations (56955d9a). [4]: https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-8.0 [5]: https://learn.microsoft.com/dotnet/core/deploying/native-aot [6]: https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedfunctionpointerattribute?view=net-8.0 --- Java.Interop.sln | 7 + build-tools/automation/azure-pipelines.yaml | 2 + .../automation/templates/core-tests.yaml | 14 ++ samples/Hello-NativeAOTFromJNI/App.cs | 31 ++++ .../Hello-NativeAOTFromJNI.csproj | 52 ++++++ .../Hello-NativeAOTFromJNI.targets | 106 +++++++++++ .../JavaInteropRuntime.cs | 40 +++++ samples/Hello-NativeAOTFromJNI/ManagedType.cs | 22 +++ .../NativeAotTypeManager.cs | 37 ++++ samples/Hello-NativeAOTFromJNI/README.md | 164 ++++++++++++++++++ .../java/net/dot/jni/hello/App.java | 18 ++ .../net/dot/jni/hello/JavaInteropRuntime.java | 12 ++ src/Java.Interop/Java.Interop.csproj | 1 + .../JniRuntime.JniMarshalMemberBuilder.cs | 2 + .../JniRuntime.JniValueManager.cs | 5 +- .../Java.Interop/JniValueMarshaler.cs | 5 +- .../JniValueMarshalerAttribute.cs | 2 +- src/Java.Interop/Java.Interop/ManagedPeer.cs | 3 + src/Java.Interop/Properties/AssemblyInfo.cs | 7 + .../Java.Interop/JreRuntime.cs | 35 ++-- .../Java.Interop/JreTypeManager.cs | 4 + .../Java.Interop/ManagedValueManager.cs | 35 +++- .../MonoRuntimeObjectReferenceManager.cs | 36 ++-- .../Java.Interop/MonoRuntimeValueManager.cs | 22 +-- 24 files changed, 613 insertions(+), 49 deletions(-) create mode 100644 samples/Hello-NativeAOTFromJNI/App.cs create mode 100644 samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj create mode 100644 samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.targets create mode 100644 samples/Hello-NativeAOTFromJNI/JavaInteropRuntime.cs create mode 100644 samples/Hello-NativeAOTFromJNI/ManagedType.cs create mode 100644 samples/Hello-NativeAOTFromJNI/NativeAotTypeManager.cs create mode 100644 samples/Hello-NativeAOTFromJNI/README.md create mode 100644 samples/Hello-NativeAOTFromJNI/java/net/dot/jni/hello/App.java create mode 100644 samples/Hello-NativeAOTFromJNI/java/net/dot/jni/hello/JavaInteropRuntime.java diff --git a/Java.Interop.sln b/Java.Interop.sln index d662692ca..3af4b28fb 100644 --- a/Java.Interop.sln +++ b/Java.Interop.sln @@ -113,6 +113,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Expressi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Expressions-Tests", "tests\Java.Interop.Tools.Expressions-Tests\Java.Interop.Tools.Expressions-Tests.csproj", "{211BAA88-66B1-41B2-88B2-530DBD8DF702}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello-NativeAOTFromJNI", "samples\Hello-NativeAOTFromJNI\Hello-NativeAOTFromJNI.csproj", "{8DB3842B-73D7-491C-96F9-EBC863E2C917}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Java.Interop.NamingCustomAttributes\Java.Interop.NamingCustomAttributes.projitems*{58b564a1-570d-4da2-b02d-25bddb1a9f4f}*SharedItemsImports = 5 @@ -320,6 +322,10 @@ Global {211BAA88-66B1-41B2-88B2-530DBD8DF702}.Debug|Any CPU.Build.0 = Debug|Any CPU {211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.ActiveCfg = Release|Any CPU {211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.Build.0 = Release|Any CPU + {8DB3842B-73D7-491C-96F9-EBC863E2C917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DB3842B-73D7-491C-96F9-EBC863E2C917}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DB3842B-73D7-491C-96F9-EBC863E2C917}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DB3842B-73D7-491C-96F9-EBC863E2C917}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -374,6 +380,7 @@ Global {CB05E11B-B96F-4179-A4E9-5D6BDE29A8FC} = {271C9F30-F679-4793-942B-0D9527CB3E2F} {1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A} = {0998E45F-8BCE-4791-A944-962CD54E2D80} {211BAA88-66B1-41B2-88B2-530DBD8DF702} = {271C9F30-F679-4793-942B-0D9527CB3E2F} + {8DB3842B-73D7-491C-96F9-EBC863E2C917} = {D5A93398-AEB1-49F3-89DC-3904A47DB0C7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {29204E0C-382A-49A0-A814-AD7FBF9774A5} diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index 654db943e..df6f1bf06 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -57,6 +57,7 @@ jobs: - template: templates\core-tests.yaml parameters: runNativeDotnetTests: true + nativeAotRid: win-x64 platformName: .NET - Windows - template: templates\fail-on-issue.yaml @@ -87,6 +88,7 @@ jobs: - template: templates\core-tests.yaml parameters: runNativeTests: true + nativeAotRid: osx-x64 platformName: .NET - MacOS - template: templates\fail-on-issue.yaml diff --git a/build-tools/automation/templates/core-tests.yaml b/build-tools/automation/templates/core-tests.yaml index 1f11013ed..5c886152b 100644 --- a/build-tools/automation/templates/core-tests.yaml +++ b/build-tools/automation/templates/core-tests.yaml @@ -2,6 +2,7 @@ parameters: condition: succeeded() runNativeTests: false platformName: + nativeAotRid: steps: - task: DotNetCoreCLI@2 @@ -173,6 +174,19 @@ steps: arguments: -c $(Build.Configuration) tools/java-source-utils/java-source-utils.csproj -t:RunTests continueOnError: true +- powershell: > + dotnet publish -c $(Build.Configuration) -r ${{ parameters.nativeAotRid }} + samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj + displayName: 'Tests: publish Hello-NativeAOTFromJNI' + continueOnError: true + +- powershell: > + dotnet build -c $(Build.Configuration) -r ${{ parameters.nativeAotRid }} + -t:RunJavaSample + samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj + displayName: 'Tests: run Hello-NativeAOTFromJNI' + continueOnError: true + - task: PublishTestResults@2 displayName: Publish JUnit Test Results inputs: diff --git a/samples/Hello-NativeAOTFromJNI/App.cs b/samples/Hello-NativeAOTFromJNI/App.cs new file mode 100644 index 000000000..c2e326921 --- /dev/null +++ b/samples/Hello-NativeAOTFromJNI/App.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +using Java.Interop; + +namespace Hello_NativeAOTFromJNI; + +static class App { + + // symbol name from `$(IntermediateOutputPath)obj/Release/osx-x64/h-classes/net_dot_jni_hello_App.h` + [UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_hello_App_sayHello")] + static IntPtr sayHello (IntPtr jnienv, IntPtr klass) + { + var envp = new JniTransition (jnienv); + try { + var s = $"Hello from .NET NativeAOT!"; + Console.WriteLine (s); + var h = JniEnvironment.Strings.NewString (s); + var r = JniEnvironment.References.NewReturnToJniRef (h); + JniObjectReference.Dispose (ref h); + return r; + } + catch (Exception e) { + Console.Error.WriteLine ($"Error in App.sayHello(): {e.ToString ()}"); + envp.SetPendingException (e); + } + finally { + envp.Dispose (); + } + return nint.Zero; + } +} diff --git a/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj b/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj new file mode 100644 index 000000000..53822ff34 --- /dev/null +++ b/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj @@ -0,0 +1,52 @@ + + + + $(DotNetTargetFramework) + + + + + + Hello_NativeAOTFromJNI + enable + enable + true + true + Shared + + AnyCPU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.targets b/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.targets new file mode 100644 index 000000000..695c22c18 --- /dev/null +++ b/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.targets @@ -0,0 +1,106 @@ + + + + "$(DOTNET_HOST_PATH)" + + + + + + + + <_JcwGenRefAsmDirs Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" /> + + + <_JcwGen>"$(UtilityOutputFullPath)/jcw-gen.dll" + <_Target>--codegen-target JavaInterop1 + <_Output>-o "$(IntermediateOutputPath)/java" + <_Libpath>@(_JcwGenRefAsmDirs->'-L "%(Identity)"', ' ') + + + + + + + + + <_JnimmRefAsmDirs Include="@(RuntimePackAsset->'%(RootDir)%(Directory).'->Distinct())" /> + + + <_JnimarshalmethodGen>"$(UtilityOutputFullPath)/jnimarshalmethod-gen.dll" + <_Verbosity>-v -v --keeptemp + <_Libpath>-L "$(TargetDir)" @(_JnimmRefAsmDirs->'-L "%(Identity)"', ' ') + + + + + + + + + + + + + + + <_JcwSource Include="$(IntermediateOutputPath)java\**\*.java" /> + + + <_Source Include="@(_JcwSource->Replace('%5c', '/'))" /> + <_Source Include="@(HelloNativeAOTFromJNIJar->Replace('%5c', '/'))" /> + + + + <_JavacOpt Include="$(_JavacSourceOptions)" /> + <_JavacOpt Include="-d "$(IntermediateOutputPath)h-classes" " /> + <_JavacOpt Include="-classpath "$(OutputPath)java-interop.jar" " /> + <_JavacOpt Include=""@$(IntermediateOutputPath)_java_sources.txt"" /> + <_JavacOpt Include="-h "$(IntermediateOutputPath)h-classes" " /> + + + + + + + + + + + + + + <_Classpath Include="hello-from-java.jar" /> + <_Classpath Include="java-interop.jar" /> + + + <_CPSep Condition=" '$(OS)' == 'Windows_NT' ">; + <_CPSep Condition=" '$(_CPSep)' == '' ">: + <_CP>@(_Classpath, '$(_CPSep)') + + + + diff --git a/samples/Hello-NativeAOTFromJNI/JavaInteropRuntime.cs b/samples/Hello-NativeAOTFromJNI/JavaInteropRuntime.cs new file mode 100644 index 000000000..c956536df --- /dev/null +++ b/samples/Hello-NativeAOTFromJNI/JavaInteropRuntime.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +using Java.Interop; + +namespace Hello_NativeAOTFromJNI; + +static class JavaInteropRuntime +{ + static JniRuntime? runtime; + + [UnmanagedCallersOnly (EntryPoint="JNI_OnLoad")] + static int JNI_OnLoad (IntPtr vm, IntPtr reserved) + { + return (int) JniVersion.v1_6; + } + + [UnmanagedCallersOnly (EntryPoint="JNI_OnUnload")] + static void JNI_OnUnload (IntPtr vm, IntPtr reserved) + { + runtime?.Dispose (); + } + + // symbol name from `$(IntermediateOutputPath)obj/Release/osx-arm64/h-classes/net_dot_jni_hello_JavaInteropRuntime.h` + [UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_hello_JavaInteropRuntime_init")] + static void init (IntPtr jnienv, IntPtr klass) + { + Console.WriteLine ($"C# init()"); + try { + var options = new JreRuntimeOptions { + EnvironmentPointer = jnienv, + TypeManager = new NativeAotTypeManager (), + UseMarshalMemberBuilder = false, + }; + runtime = options.CreateJreVM (); + } + catch (Exception e) { + Console.Error.WriteLine ($"JavaInteropRuntime.init: error: {e}"); + } + } +} diff --git a/samples/Hello-NativeAOTFromJNI/ManagedType.cs b/samples/Hello-NativeAOTFromJNI/ManagedType.cs new file mode 100644 index 000000000..3d21f7163 --- /dev/null +++ b/samples/Hello-NativeAOTFromJNI/ManagedType.cs @@ -0,0 +1,22 @@ +namespace Example; + +using Java.Interop; + +[JniTypeSignature (JniTypeName)] +class ManagedType : Java.Lang.Object { + internal const string JniTypeName = "example/ManagedType"; + + [JavaCallableConstructor(SuperConstructorExpression="")] + public ManagedType (int value) + { + this.value = value; + } + + int value; + + [JavaCallable ("getString")] + public Java.Lang.String GetString () + { + return new Java.Lang.String ($"Hello from C#, via Java.Interop! Value={value}"); + } +} diff --git a/samples/Hello-NativeAOTFromJNI/NativeAotTypeManager.cs b/samples/Hello-NativeAOTFromJNI/NativeAotTypeManager.cs new file mode 100644 index 000000000..7d21d95e1 --- /dev/null +++ b/samples/Hello-NativeAOTFromJNI/NativeAotTypeManager.cs @@ -0,0 +1,37 @@ +using Java.Interop; + +namespace Hello_NativeAOTFromJNI; + +class NativeAotTypeManager : JniRuntime.JniTypeManager { + +#pragma warning disable IL2026 + Dictionary typeMappings = new () { + [Example.ManagedType.JniTypeName] = typeof (Example.ManagedType), + }; +#pragma warning restore IL2026 + + + protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) + { + if (typeMappings.TryGetValue (jniSimpleReference, out var target)) + yield return target; + foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) + yield return t; + } + + protected override IEnumerable GetSimpleReferences (Type type) + { + return base.GetSimpleReferences (type) + .Concat (CreateSimpleReferencesEnumerator (type)); + } + + IEnumerable CreateSimpleReferencesEnumerator (Type type) + { + if (typeMappings == null) + yield break; + foreach (var e in typeMappings) { + if (e.Value == type) + yield return e.Key; + } + } +} diff --git a/samples/Hello-NativeAOTFromJNI/README.md b/samples/Hello-NativeAOTFromJNI/README.md new file mode 100644 index 000000000..3494607bf --- /dev/null +++ b/samples/Hello-NativeAOTFromJNI/README.md @@ -0,0 +1,164 @@ +# Hello From JNI + +[JNI][0] supports *two* modes of operation: + + 1. Native code creates the JVM, e.g. via [`JNI_CreateJavaVM()`][1], or + 2. The JVM already exists, and calls [`JNI_OnLoad()`][2] when loading a native library. + +Java.Interop samples and unit tests rely on the first approach. + +.NET Android / neé Xamarin.Android is the second approach. + +Bring an example of the latter into a Java.Interop sample, using [NativeAOT][3]. + +## Building + +Building a native library with NativeAOT requires a Release configuration build. +For in-repo use, that means that xamarin/Java.Interop itself needs to be built in +Release configuration: + +```sh +% dotnet build -c Release -t:Prepare +% dotnet build -c Release +``` + +Once Java.Interop itself is built, you can *publish* the sample: + +```sh +% cd samples/Hello-NativeAOTFromJNI +% dotnet publish -c Release -r osx-x64 +``` + +The resulting native library contains the desired symbols: + +```sh +% nm bin/Release/osx-x64/publish/Hello-NativeAOTFromJNI.dylib | grep ' S ' +00000000000ef880 S _JNI_OnLoad +00000000000ef8b0 S _JNI_OnUnload +00000000000ef5d0 S _Java_net_dot_jni_hello_App_sayHello +00000000000ef900 S _Java_net_dot_jni_hello_JavaInteropRuntime_init +``` + +Use the `RunJavaSample` target to run Java, which will run +`System.loadLibrary("Hello-NativeAOTFromJNI")`, which will cause the +NativeAOT-generated `libHello-NativeAOTFromJNI.dylib` to be run: + +```sh +% dotnet build -c Release -r osx-x64 -t:RunJavaSample -v m --nologo --no-restore + Hello from Java! + C# init() + Hello from .NET NativeAOT! + String returned to Java: Hello from .NET NativeAOT! + # jonp: called `Example.ManagedType/__<$>_jni_marshal_methods.__RegisterNativeMembers()` w/ 1 methods to register. + mt.getString()=Hello from C#, via Java.Interop! Value=42 + +Build succeeded. + 0 Warning(s) + 0 Error(s) + +Time Elapsed 00:00:01.04 + +% (cd bin/Release/osx-x64/publish ; java -cp hello-from-java.jar:java-interop.jar net/dot/jni/hello/App) +Hello from Java! +C# init() +Hello from .NET NativeAOT! +String returned to Java: Hello from .NET NativeAOT! +# jonp: called `Example.ManagedType/__<$>_jni_marshal_methods.__RegisterNativeMembers()` w/ 1 methods to register. +mt.getString()=Hello from C#, via Java.Interop! Value=42 +``` + +Note the use of `(cd …; java …)` so that `libHello-NativeAOTFromJNI.dylib` is +in the current working directory, so that it can be found. + +# Notes + +To support cross-compilation, the project should set +`$(PlatformTarget)`=AnyCPU. + +# Known Knowns? + +With this sample "done" (-ish), there are some +"potentially solved, if not ideally" used to make NativeAOT + Java *viable*. + +## `Type.GetType()` + +Commit +[xamarin/java.interop@005c9141](https://github.com/xamarin/java.interop/commit/005c914170a0af9069ff18fd4dd9d45463dd5dc6) +uses JNI Type Signatures to avoid `Type.GetType()` invocations, which continue +to be used in .NET Android. + +```Java +/* partial */ class JavaCallableWrapper +{ + public static final String __md_methods; + static { + __md_methods = + "n_GetString:()Ljava/lang/String;:__export__\n" + + ""; + net.dot.jni.ManagedPeer.registerNativeMembers ( + /* nativeClass */ ManagedType.class, + /* methods */ __md_methods); + } + + public ManagedType (int p0) + { + super (); + if (getClass () == ManagedType.class) { + net.dot.jni.ManagedPeer.construct ( + /* self */ this, + /* constructorSignature */ "(I)V", + /* arguments */ new java.lang.Object[] { p0 }); + } + } +} +``` + +This requires the use of JNI method signatures within the constructor +to lookup the corresponding managed constructor to invoke. While this +works, it requires additional work to lookup the constructor, as there +may not be a 1:1 relation between types within the JNI method signature +and managed code. In particular, Java *arrays* may have multiple types +which can be used from managed code. + + +# Known Unknowns + +With this sample "done" (-ish), there are several "future research directions" to +make NativeAOT + Java *viable*. + +## GC + +Firstly, there's the open GC question: NativeAOT doesn't provide a "GC Bridge" +like MonoVM does, so how do we support cross-VM object references? + + * [Collecting Cyclic Garbage across Foreign Function Interfaces: Who Takes the Last Piece of Cake?](https://pldi23.sigplan.org/details/pldi-2023-pldi/25/Collecting-Cyclic-Garbage-across-Foreign-Function-Interfaces-Who-Takes-the-Last-Piec) + * [`JavaScope`?](https://github.com/jonpryor/java.interop/commits/jonp-registration-scope) + (Less a "solution" and more a "Glorious Workaround".) + + +## Type Maps + +A "derivative" of the `Type.GetType()` problem is that Java.Interop needs a way +to associate a Java type to a .NET `System.Type` instance, for all manner of +reasons. (One such reason: `JniRuntime.JniValueManager.GetValue()` needs to +know the associated type so that it can create a "peer wrapper", if needed.) + +Java.Interop unit tests "hack" around this by using a dictionary in TestJVM, +and `Hello-NativeAOTFromJNI` follows suite. This isn't a "real" answer, though. + +.NET Android has a very complicated typemap mechanism that involves a table +between the Java JNI name and an { assembly name, type token } pair, along with +copious use of MonoVM embedding API such as `mono_class_get()`. ***A Lot*** +of effort has gone into making type maps performant. + +How do we "do" type maps in NativeAOT? We may need to consider some equivalent +to the iOS "static registrar", and this also needs to support getting `Type` +instances for non-`public` types. There are also concerns about initialization +overhead; a `Dictionary` will require loading and resolving +*all* the `Type` instances as part of startup, which *can't* be good for +reducing startup time. What other data structure could be used? + +[0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html +[1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#creating_the_vm +[2]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad +[3]: https://github.com/dotnet/samples/blob/main/core/nativeaot/NativeLibrary/README.md \ No newline at end of file diff --git a/samples/Hello-NativeAOTFromJNI/java/net/dot/jni/hello/App.java b/samples/Hello-NativeAOTFromJNI/java/net/dot/jni/hello/App.java new file mode 100644 index 000000000..427016263 --- /dev/null +++ b/samples/Hello-NativeAOTFromJNI/java/net/dot/jni/hello/App.java @@ -0,0 +1,18 @@ +package net.dot.jni.hello; + +import net.dot.jni.hello.JavaInteropRuntime; +import example.ManagedType; + +class App { + + public static void main(String[] args) { + System.out.println("Hello from Java!"); + JavaInteropRuntime.init(); + String s = sayHello(); + System.out.println("String returned to Java: " + s); + ManagedType mt = new ManagedType(42); + System.out.println("mt.getString()=" + mt.getString()); + } + + static native String sayHello(); +} diff --git a/samples/Hello-NativeAOTFromJNI/java/net/dot/jni/hello/JavaInteropRuntime.java b/samples/Hello-NativeAOTFromJNI/java/net/dot/jni/hello/JavaInteropRuntime.java new file mode 100644 index 000000000..aa7f1b0d7 --- /dev/null +++ b/samples/Hello-NativeAOTFromJNI/java/net/dot/jni/hello/JavaInteropRuntime.java @@ -0,0 +1,12 @@ +package net.dot.jni.hello; + +public class JavaInteropRuntime { + static { + System.loadLibrary("Hello-NativeAOTFromJNI"); + } + + private JavaInteropRuntime() { + } + + public static native void init(); +} diff --git a/src/Java.Interop/Java.Interop.csproj b/src/Java.Interop/Java.Interop.csproj index 80eb91aee..3a7dd4325 100644 --- a/src/Java.Interop/Java.Interop.csproj +++ b/src/Java.Interop/Java.Interop.csproj @@ -32,6 +32,7 @@ 9.0 8.0 $(JICoreLibVersion) + true FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS;$(DefineConstants) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniMarshalMemberBuilder.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniMarshalMemberBuilder.cs index 77be210f0..0d8ee1a3a 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniMarshalMemberBuilder.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniMarshalMemberBuilder.cs @@ -27,6 +27,8 @@ public JniMarshalMemberBuilder MarshalMemberBuilder { } } + internal bool UseMarshalMemberBuilder => marshalMemberBuilder != null; + [DynamicDependency (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, "Java.Interop.MarshalMemberBuilder", "Java.Interop.Export")] [UnconditionalSuppressMessage ("Trimming", "IL2035", Justification = "Java.Interop.Export.dll is not always present.")] partial void SetMarshalMemberBuilder (CreationOptions options) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 181469f6d..b26e3b614 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -374,7 +374,10 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) [UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)] [UnconditionalSuppressMessage ("AOT", "IL3050", Justification = makeGenericTypeMessage)] [return: DynamicallyAccessedMembers (Constructors)] - static Type MakeGenericType (Type type, Type [] arguments) => + static Type MakeGenericType ( + [DynamicallyAccessedMembers (Constructors)] + Type type, + Type [] arguments) => type.MakeGenericType (arguments); Type[] arguments = type.GetGenericArguments (); diff --git a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs index ae8e096c2..8b5b08cb5 100644 --- a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs +++ b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs @@ -172,10 +172,13 @@ Expression CreateSelf (JniValueMarshalerContext context, ParameterExpression sou { var self = Expression.Variable (GetType (), sourceValue.Name + "_marshaler"); context.LocalVariables.Add (self); - context.CreationStatements.Add (Expression.Assign (self, Expression.New (GetType ()))); + context.CreationStatements.Add (Expression.Assign (self, Expression.New (_GetType ()))); return self; } + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type _GetType () => GetType (); + [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] public virtual Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue) { diff --git a/src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs b/src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs index b674094be..03bb442b5 100644 --- a/src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs +++ b/src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs @@ -29,8 +29,8 @@ public JniValueMarshalerAttribute ( MarshalerType = marshalerType; } + [DynamicallyAccessedMembers (ParameterlessConstructorsInterfaces)] public Type MarshalerType { - [return: DynamicallyAccessedMembers (ParameterlessConstructorsInterfaces)] get; } } diff --git a/src/Java.Interop/Java.Interop/ManagedPeer.cs b/src/Java.Interop/Java.Interop/ManagedPeer.cs index 763e439f2..6a9834954 100644 --- a/src/Java.Interop/Java.Interop/ManagedPeer.cs +++ b/src/Java.Interop/Java.Interop/ManagedPeer.cs @@ -8,6 +8,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; @@ -54,6 +55,7 @@ public override JniPeerMembers JniPeerMembers { const string ConstructSignature = "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)V"; // TODO: Keep in sync with the code generated by ExportedMemberBuilder + [UnmanagedFunctionPointer (CallingConvention.Winapi)] delegate void ConstructMarshalMethod (IntPtr jnienv, IntPtr klass, IntPtr n_self, @@ -254,6 +256,7 @@ static List[] GetConstructorCandidateParameterTypes (string signature) const string RegisterNativeMembersSignature = "(Ljava/lang/Class;Ljava/lang/String;)V"; + [UnmanagedFunctionPointer (CallingConvention.Winapi)] delegate void RegisterMarshalMethod (IntPtr jnienv, IntPtr klass, IntPtr n_nativeClass, diff --git a/src/Java.Interop/Properties/AssemblyInfo.cs b/src/Java.Interop/Properties/AssemblyInfo.cs index 01afd8e26..bff1cb28c 100644 --- a/src/Java.Interop/Properties/AssemblyInfo.cs +++ b/src/Java.Interop/Properties/AssemblyInfo.cs @@ -14,6 +14,13 @@ "814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0" + "d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b" + "2c9733db")] +[assembly: InternalsVisibleTo ( + "Java.Runtime.Environment, PublicKey=" + + "0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf1" + + "6cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2" + + "814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0" + + "d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b" + + "2c9733db")] [assembly: InternalsVisibleTo ( "Java.Interop-Tests, PublicKey=" + "0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf1" + diff --git a/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs b/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs index d7cc0f648..811523b35 100644 --- a/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs +++ b/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; @@ -103,7 +104,7 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder) builder.LibraryHandler.LoadJvmLibrary (builder.JvmLibraryPath!); if (!builder.ClassPath.Any (p => p.EndsWith ("java-interop.jar", StringComparison.OrdinalIgnoreCase))) { - var loc = typeof (JreRuntimeOptions).Assembly.Location; + var loc = GetAssemblyLocation (typeof (JreRuntimeOptions).Assembly); var dir = string.IsNullOrEmpty (loc) ? null : Path.GetDirectoryName (loc); var jij = string.IsNullOrEmpty (dir) ? null : Path.Combine (dir, "java-interop.jar"); if (!File.Exists (jij)) { @@ -146,6 +147,15 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder) } } + [UnconditionalSuppressMessage ("Trimming", "IL3000", Justification = "We check for a null Assembly.Location value!")] + internal static string? GetAssemblyLocation (Assembly assembly) + { + var location = assembly.Location; + if (!string.IsNullOrEmpty (location)) + return location; + return null; + } + JvmLibraryHandler LibraryHandler; internal protected JreRuntime (JreRuntimeOptions builder) @@ -189,11 +199,15 @@ public static JvmLibraryHandler Create () { var handler = Environment.GetEnvironmentVariable ("JI_LOADER_TYPE"); switch (handler?.ToLowerInvariant ()) { +#if !NET case "": case null: +#endif // NET case "java-interop": return new JavaInteropLibJvmLibraryHandler (); #if NET + case "": + case null: case "native-library": return new NativeLibraryJvmLibraryHandler (); #endif // NET @@ -281,10 +295,10 @@ static JavaInteropLibJvmLibraryHandler () public override void LoadJvmLibrary (string path) { IntPtr errorPtr = IntPtr.Zero; - int r = NativeMethods.java_interop_jvm_load_with_error_message (path, out errorPtr); + int r = JreNativeMethods.java_interop_jvm_load_with_error_message (path, out errorPtr); if (r != 0) { string? error = Marshal.PtrToStringAnsi (errorPtr); - NativeMethods.java_interop_free (errorPtr); + JreNativeMethods.java_interop_free (errorPtr); if (r == JAVA_INTEROP_JVM_FAILED_ALREADY_LOADED) { return; } @@ -294,17 +308,17 @@ public override void LoadJvmLibrary (string path) public override int CreateJavaVM (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args) { - return NativeMethods.java_interop_jvm_create (out javavm, out jnienv, ref args); + return JreNativeMethods.java_interop_jvm_create (out javavm, out jnienv, ref args); } public override IEnumerable GetAvailableInvocationPointers () { int nVMs; - int r = NativeMethods.java_interop_jvm_list (null, 0, out nVMs); + int r = JreNativeMethods.java_interop_jvm_list (null, 0, out nVMs); if (r != 0) throw new NotSupportedException ("JNI_GetCreatedJavaVMs() returned: " + r.ToString ()); var handles = new IntPtr [nVMs]; - r = NativeMethods.java_interop_jvm_list (handles, handles.Length, out nVMs); + r = JreNativeMethods.java_interop_jvm_list (handles, handles.Length, out nVMs); if (r != 0) throw new InvalidOperationException ("JNI_GetCreatedJavaVMs() [take 2!] returned: " + r.ToString ()); return handles; @@ -315,14 +329,15 @@ public override void Dispose () } } - partial class NativeMethods { + partial class JreNativeMethods { - static NativeMethods () + static JreNativeMethods () { if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - var baseDir = Path.GetDirectoryName (typeof (JreRuntime).Assembly.Location) ?? throw new NotSupportedException (); + var loc = JreRuntime.GetAssemblyLocation (typeof (JreRuntime).Assembly) ?? throw new NotSupportedException (); + var baseDir = Path.GetDirectoryName (loc) ?? throw new NotSupportedException (); var newDir = Path.Combine (baseDir, Environment.Is64BitProcess ? "win-x64" : "win-x86"); - NativeMethods.AddDllDirectory (newDir); + JreNativeMethods.AddDllDirectory (newDir); } } diff --git a/src/Java.Runtime.Environment/Java.Interop/JreTypeManager.cs b/src/Java.Runtime.Environment/Java.Interop/JreTypeManager.cs index 358d009c8..4912b6877 100644 --- a/src/Java.Runtime.Environment/Java.Interop/JreTypeManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/JreTypeManager.cs @@ -23,6 +23,10 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, Read return; } + if (!Runtime.UseMarshalMemberBuilder) { + throw new NotSupportedException ("JniRuntime.MarshalMemberBuilder is required and not present."); + } + var toRegister = new JniMethodMap (); AddInterfaceMethods (toRegister, type); diff --git a/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs index 7ff510a7a..4016909b2 100644 --- a/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs @@ -22,7 +22,7 @@ public override void WaitForGCBridgeProcessing () public override void CollectPeers () { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); var peers = new List (); @@ -51,7 +51,7 @@ public override void CollectPeers () public override void AddPeer (IJavaPeerable value) { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); var r = value.PeerReference; if (!r.IsValid) @@ -116,7 +116,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal public override IJavaPeerable? PeekPeer (JniObjectReference reference) { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); if (!reference.IsValid) return null; @@ -142,7 +142,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal public override void RemovePeer (IJavaPeerable value) { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); if (value == null) throw new ArgumentNullException (nameof (value)); @@ -204,8 +204,11 @@ public override void ActivatePeer (IJavaPeerable? self, JniObjectReference refer var runtime = JniEnvironment.Runtime; try { - var f = runtime.MarshalMemberBuilder.CreateConstructActivationPeerFunc (cinfo); - f (cinfo, reference, argumentValues); + if (runtime.UseMarshalMemberBuilder) { + ActivateViaMarshalMemberBuilder (runtime.MarshalMemberBuilder, reference, cinfo, argumentValues); + return; + } + ActivateViaReflection (reference, cinfo, argumentValues); } catch (Exception e) { var m = string.Format ("Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", reference, @@ -218,10 +221,28 @@ public override void ActivatePeer (IJavaPeerable? self, JniObjectReference refer } } + void ActivateViaMarshalMemberBuilder (JniRuntime.JniMarshalMemberBuilder builder, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) + { + var f = builder.CreateConstructActivationPeerFunc (cinfo); + f (cinfo, reference, argumentValues); + } + + void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) + { + var declType = cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!"); + +#pragma warning disable IL2072 + var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType); +#pragma warning restore IL2072 + self.SetPeerReference (reference); + + cinfo.Invoke (self, argumentValues); + } + public override List GetSurfacedPeers () { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); lock (RegisteredInstances) { var peers = new List (RegisteredInstances.Count); diff --git a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeObjectReferenceManager.cs b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeObjectReferenceManager.cs index 82b4961ae..47812f237 100644 --- a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeObjectReferenceManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeObjectReferenceManager.cs @@ -13,19 +13,19 @@ class MonoRuntimeObjectReferenceManager : JniRuntime.JniObjectReferenceManager { public override void OnSetRuntime (JniRuntime runtime) { base.OnSetRuntime (runtime); - bridge = NativeMethods.java_interop_gc_bridge_get_current (); + bridge = JreNativeMethods.java_interop_gc_bridge_get_current (); if (bridge != IntPtr.Zero) { - logLocalRefs = NativeMethods.java_interop_gc_bridge_lref_get_log_file (bridge) != IntPtr.Zero; - logGlobalRefs = NativeMethods.java_interop_gc_bridge_gref_get_log_file (bridge) != IntPtr.Zero; + logLocalRefs = JreNativeMethods.java_interop_gc_bridge_lref_get_log_file (bridge) != IntPtr.Zero; + logGlobalRefs = JreNativeMethods.java_interop_gc_bridge_gref_get_log_file (bridge) != IntPtr.Zero; } } public override int GlobalReferenceCount { - get {return NativeMethods.java_interop_gc_bridge_get_gref_count (bridge);} + get {return JreNativeMethods.java_interop_gc_bridge_get_gref_count (bridge);} } public override int WeakGlobalReferenceCount { - get {return NativeMethods.java_interop_gc_bridge_get_weak_gref_count (bridge);} + get {return JreNativeMethods.java_interop_gc_bridge_get_weak_gref_count (bridge);} } public override bool LogLocalReferenceMessages { @@ -36,8 +36,8 @@ public override void WriteLocalReferenceLine (string format, params object[] arg { if (!LogLocalReferenceMessages) return; - NativeMethods.java_interop_gc_bridge_lref_log_message (bridge, 0, string.Format (format, args)); - NativeMethods.java_interop_gc_bridge_lref_log_message (bridge, 0, "\n"); + JreNativeMethods.java_interop_gc_bridge_lref_log_message (bridge, 0, string.Format (format, args)); + JreNativeMethods.java_interop_gc_bridge_lref_log_message (bridge, 0, "\n"); } public override JniObjectReference CreateLocalReference (JniObjectReference reference, ref int localReferenceCount) @@ -46,7 +46,7 @@ public override JniObjectReference CreateLocalReference (JniObjectReference refe return reference; var r = base.CreateLocalReference (reference, ref localReferenceCount); - NativeMethods.java_interop_gc_bridge_lref_log_new (bridge, + JreNativeMethods.java_interop_gc_bridge_lref_log_new (bridge, localReferenceCount, reference.Handle, ToByte (reference.Type), @@ -76,7 +76,7 @@ public override void DeleteLocalReference (ref JniObjectReference reference, ref { if (!reference.IsValid) return; - NativeMethods.java_interop_gc_bridge_lref_log_delete (bridge, + JreNativeMethods.java_interop_gc_bridge_lref_log_delete (bridge, localReferenceCount, reference.Handle, ToByte (reference.Type), @@ -91,7 +91,7 @@ public override void CreatedLocalReference (JniObjectReference reference, ref in if (!reference.IsValid) return; base.CreatedLocalReference (reference, ref localReferenceCount); - NativeMethods.java_interop_gc_bridge_lref_log_new (bridge, + JreNativeMethods.java_interop_gc_bridge_lref_log_new (bridge, localReferenceCount, reference.Handle, ToByte (reference.Type), @@ -106,7 +106,7 @@ public override IntPtr ReleaseLocalReference (ref JniObjectReference reference, { if (!reference.IsValid) return IntPtr.Zero; - NativeMethods.java_interop_gc_bridge_lref_log_delete (bridge, + JreNativeMethods.java_interop_gc_bridge_lref_log_delete (bridge, localReferenceCount, reference.Handle, ToByte (reference.Type), @@ -124,8 +124,8 @@ public override void WriteGlobalReferenceLine (string format, params object?[]? { if (!LogGlobalReferenceMessages) return; - NativeMethods.java_interop_gc_bridge_gref_log_message (bridge, 0, string.Format (format, args!)); - NativeMethods.java_interop_gc_bridge_gref_log_message (bridge, 0, "\n"); + JreNativeMethods.java_interop_gc_bridge_gref_log_message (bridge, 0, string.Format (format, args!)); + JreNativeMethods.java_interop_gc_bridge_gref_log_message (bridge, 0, "\n"); } public override JniObjectReference CreateGlobalReference (JniObjectReference reference) @@ -133,7 +133,7 @@ public override JniObjectReference CreateGlobalReference (JniObjectReference ref if (!reference.IsValid) return reference; var n = base.CreateGlobalReference (reference); - NativeMethods.java_interop_gc_bridge_gref_log_new (bridge, + JreNativeMethods.java_interop_gc_bridge_gref_log_new (bridge, reference.Handle, ToByte (reference.Type), n.Handle, @@ -148,7 +148,7 @@ public override void DeleteGlobalReference (ref JniObjectReference reference) { if (!reference.IsValid) return; - NativeMethods.java_interop_gc_bridge_gref_log_delete (bridge, + JreNativeMethods.java_interop_gc_bridge_gref_log_delete (bridge, reference.Handle, ToByte (reference.Type), GetCurrentManagedThreadName (LogGlobalReferenceMessages), @@ -162,7 +162,7 @@ public override JniObjectReference CreateWeakGlobalReference (JniObjectReference if (!reference.IsValid) return reference; var n = base.CreateWeakGlobalReference (reference); - NativeMethods.java_interop_gc_bridge_weak_gref_log_new (bridge, + JreNativeMethods.java_interop_gc_bridge_weak_gref_log_new (bridge, reference.Handle, ToByte (reference.Type), n.Handle, @@ -177,7 +177,7 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference reference { if (!reference.IsValid) return; - NativeMethods.java_interop_gc_bridge_weak_gref_log_delete (bridge, + JreNativeMethods.java_interop_gc_bridge_weak_gref_log_delete (bridge, reference.Handle, ToByte (reference.Type), GetCurrentManagedThreadName (LogGlobalReferenceMessages), @@ -202,7 +202,7 @@ static byte ToByte (JniObjectReferenceType type) } } - partial class NativeMethods { + partial class JreNativeMethods { [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] internal static extern int java_interop_gc_bridge_get_gref_count (IntPtr bridge); diff --git a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs index 223d03f4b..f0edf42d3 100644 --- a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs @@ -27,32 +27,32 @@ public override void OnSetRuntime (JniRuntime runtime) { base.OnSetRuntime (runtime); - bridge = NativeMethods.java_interop_gc_bridge_get_current (); + bridge = JreNativeMethods.java_interop_gc_bridge_get_current (); if (bridge != IntPtr.Zero) return; - bridge = NativeMethods.java_interop_gc_bridge_new (runtime.InvocationPointer); + bridge = JreNativeMethods.java_interop_gc_bridge_new (runtime.InvocationPointer); if (bridge == IntPtr.Zero) throw new NotSupportedException ("Could not initialize JNI::Mono GC Bridge!"); try { - if (NativeMethods.java_interop_gc_bridge_set_bridge_processing_field (bridge, typeof (MonoRuntimeValueManager).TypeHandle, nameof (GCBridgeProcessingIsActive)) < 0) + if (JreNativeMethods.java_interop_gc_bridge_set_bridge_processing_field (bridge, typeof (MonoRuntimeValueManager).TypeHandle, nameof (GCBridgeProcessingIsActive)) < 0) throw new NotSupportedException ("Could not set bridge processing field!"); foreach (var t in new[]{typeof (JavaObject), typeof (JavaException)}) { - if (NativeMethods.java_interop_gc_bridge_register_bridgeable_type (bridge, t.TypeHandle) < 0) + if (JreNativeMethods.java_interop_gc_bridge_register_bridgeable_type (bridge, t.TypeHandle) < 0) throw new NotSupportedException ("Could not register type " + t.FullName + "!"); } - if (NativeMethods.java_interop_gc_bridge_add_current_app_domain (bridge) < 0) + if (JreNativeMethods.java_interop_gc_bridge_add_current_app_domain (bridge) < 0) throw new NotSupportedException ("Could not register current AppDomain!"); - if (NativeMethods.java_interop_gc_bridge_set_current_once (bridge) < 0) + if (JreNativeMethods.java_interop_gc_bridge_set_current_once (bridge) < 0) throw new NotSupportedException ("Could not set GC Bridge instance!"); } catch (Exception) { - NativeMethods.java_interop_gc_bridge_free (bridge); + JreNativeMethods.java_interop_gc_bridge_free (bridge); bridge = IntPtr.Zero; throw; } - if (NativeMethods.java_interop_gc_bridge_register_hooks (bridge, GCBridgeUseWeakReferenceKind.Jni) < 0) + if (JreNativeMethods.java_interop_gc_bridge_register_hooks (bridge, GCBridgeUseWeakReferenceKind.Jni) < 0) throw new NotSupportedException ("Could not register GC Bridge with Mono!"); } @@ -60,7 +60,7 @@ public override void WaitForGCBridgeProcessing () { if (!GCBridgeProcessingIsActive) return; - NativeMethods.java_interop_gc_bridge_wait_for_bridge_processing (bridge); + JreNativeMethods.java_interop_gc_bridge_wait_for_bridge_processing (bridge); } public override void CollectPeers () @@ -92,7 +92,7 @@ protected override void Dispose (bool disposing) } if (bridge != IntPtr.Zero) { - NativeMethods.java_interop_gc_bridge_remove_current_app_domain (bridge); + JreNativeMethods.java_interop_gc_bridge_remove_current_app_domain (bridge); bridge = IntPtr.Zero; } } @@ -382,7 +382,7 @@ internal static void Collect () } } - partial class NativeMethods { + partial class JreNativeMethods { const string JavaInteropLib = "java-interop";