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";