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 ea0467a29..f443c6e5d 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")] 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"; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy