-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Java.Interop] Optional "Standalone" Build Config #1049
Conversation
103b7d2
to
8290829
Compare
8290829
to
1ea4bf7
Compare
I think that once you are given pointer to JNIEnv you could copy the pointers to functions and save one indirection on each call. |
@pavelsavara while you theoretically can, that doesn't necessarily mean you should. JNI docs state that you should treat the
By my reading, there is no requirement that all threads get the same "JNIEnv runtime type"; thread A could get the "debugging" version while thread B gets the "efficient" version while thread C is using a In short, I do not believe that this is a safe optimization to perform unless you "know" more things about the JVM you're using than the JNI spec requires. I think this optimization is best avoided. |
I think we need an instance of C# JNIEnv per thread anyway ... |
42d2c3b
to
1f15986
Compare
On the whole "why does Behold! diff --git a/samples/Hello-Core/Program.cs b/samples/Hello-Core/Program.cs
index 157aa80b..ee13f609 100644
--- a/samples/Hello-Core/Program.cs
+++ b/samples/Hello-Core/Program.cs
@@ -1,8 +1,13 @@
-using Java.Interop;
+using System.Diagnostics;
+
+using Java.Interop;
using Mono.Options;
bool showHelp = false;
+bool inner = false;
+
+Console.WriteLine ($"# jonp: args: {string.Join (" ", args)}");
var jreOptions = new JreRuntimeOptions {
};
@@ -24,6 +29,12 @@ var options = new OptionSet {
"Show this message and exit.",
v => showHelp = v != null },
};
+options.Add (
+ prototype: "inner",
+ description: "directly use JNI, not via wrapper proc",
+ action: v => inner = v != null,
+ hidden: true
+);
options.Parse (args);
if (showHelp) {
@@ -36,6 +47,22 @@ if (string.IsNullOrEmpty (jreOptions.JvmLibraryPath) || !File.Exists (jreOptions
return;
}
+if (!inner) {
+ var psi = new ProcessStartInfo () {
+ FileName = "dotnet",
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ };
+ psi.ArgumentList.Add (System.Reflection.Assembly.GetExecutingAssembly ().Location);
+ psi.ArgumentList.Add ("--inner");
+ foreach (var a in args) {
+ psi.ArgumentList.Add (a);
+ }
+ var proc = Process.Start (psi);
+ proc?.WaitForExit ();
+ return;
+}
+
var jre = jreOptions.CreateJreVM ();
// We now have a JVM! It doesn't fail: % export JI_LOADER_TYPE=native-library
% dotnet run -- -jvm /Library/Java/JavaVirtualMachines/microsoft-11.jdk/Contents/Home/lib/jli/libjli.dylib
# jonp: args: -jvm /Library/Java/JavaVirtualMachines/microsoft-11.jdk/Contents/Home/lib/jli/libjli.dylib
# jonp: args: --inner -jvm /Library/Java/JavaVirtualMachines/microsoft-11.jdk/Contents/Home/lib/jli/libjli.dylib
# jonp: LoadJvmLibrary(/Library/Java/JavaVirtualMachines/microsoft-11.jdk/Contents/Home/lib/jli/libjli.dylib)=8599356000
# jonp: JNI_CreateJavaVM=4308816205; JNI_GetCreatedJavaVMs=4308816274
# jonp: executing JNI_CreateJavaVM=100d3514d
# jonp: r=0 javavm=105979c80 jnienv=7fbd2f009348
Object_class=0x7fbd2e804d28/L
Object_val=0x7fbd2e804d38/L
Object_val.toString()=java.lang.Object@5cbc508c
Object_val.toString()=java.lang.Object@3419866c I don't know why |
Given that the new Configurations will not be visible in Visual Studio, maybe there is no benefit to messing with Configurations in the first place, given that they complicate the build system. The Configuration name unfortunately drives a lot of things, which you had to add code to work around. Maybe we would be better off with just a property? (Feel free to pick a better name!)
|
Context: 16e1ecd Context: 312fbf4 It occurs to me that it would be easier for "external" developers to use `Java.Interop.dll` if it didn't require building and distributing any native libraries. Furthermore, as of commit 312fbf4 (C#9 function pointer backend), it's *plausible* to make that work. Let's do so. Add a new `$(Standalone)` "configuration" property to `src\Java.Interop\Java.Interop.csproj`; when True, the `FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS` compiler define is set instead of the `FEATURE_JNIENVIRONMENT_JI_PINVOKES` define. This enables `JniEnvironment` to use C#9 function pointers instead of P/Invokes to invoke the `JNIEnv` function pointers. % dotnet build src/Java.Interop/Java.Interop.csproj -p:Standalone=True % ikdasm src/Java.Interop/obj/Debug/net7.0/Java.Interop.dll | grep pinvoke # no matches # If `-p:Standalone=True` isn't set, then there are 188 matches. Update `Java.Interop.dll` to compile when `FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS` is set. !!ABI BREAK!! `[Obsolete]` the method `JniRuntime.GetAvailableInvocationPointers()`. In retrospect this never should have been exposed at this level of the stack, and its existence was responsible for "really really bizarre" .NET Android [app crashes][0] (due to static constructor orderings) when *sometimes* `JniRuntime.Current` wasn't set "early enough": D Mono : AOT: FOUND method Java.Interop.JniRuntime:GetAvailableInvocationPointers () [0x78e4da7960 - 0x78e4da7a7c 0x78e4de6840] D Mono : AOT: FOUND method Java.Interop.JniRuntime:GetCreatedJavaVMs (intptr[],int,int&) [0x78e4ddd2b0 - 0x78e4ddd300 0x78e4de6bcd] D Mono : AOT: NOT FOUND: Java.Interop.NativeMethods:java_interop_jvm_list (intptr[],int,int&). F monodroid-assembly: Internal p/invoke symbol 'java-interop @ java_interop_jvm_list' (hash: 0x58c48fc8b89cb484) not found in compile-time map. *Nobody* should be using this method, largely given that only Xamarin.Android and .NET Android apps currently use `Java.Interop.dll`, and neither use `JniRuntime.GetAvailableInvocationPointers()`. Furthermore, it *can't* work on Android, as Android doesn't provide a public [`JNI_GetCreatedJavaVMs()`][1] symbol. Update `build-tools/jnienv-gen` so that a `JniNativeMethods` class is created which contains "human usable" ways to invoke `JNIEnv` function pointers. (Nobody wants to copy the expression `(*((JNIEnv**)env))->ExceptionClear(env)` more than once, ever. `JniNativeMethods.ExceptionClear(env)` is much nicer to write.) Update `samples/Hello-Core` so that it sets `Standalone=True`. Verification: After building `samples/Hello-Core`, the contained `Java.Interop.dll` doesn't contain any `pinvokeimpl` methods: % dotnet build samples/Hello-Core % ikdasm samples/Hello-Core/bin/Debug/Java.Interop.dll | grep pinvoke # no matches TODO: I also attempted to reduce the number of P/Invokes in `Java.Runtime.Environment.dll`, with the hope that when *not* using MonoVM it could be used without a native `java-interop` library. This used [`System.Runtime.InteropServices.NativeLibrary`][2] to load `JniRuntime.CreationOptions.JvmLibraryPath` and invoke the `JNI_CreateJavaVM()` and `JNI_GetCreatedJavaVMs()` exports. Unfortunately, this new backend crashes inexplicably when using `dotnet test`. The backend can now be selected by setting the `JI_LOADER_TYPE` environment variable to one of: * `native-library`: the `NativeLibrary` backened, or * `java-interop`: the previous `java-interop` native lib backend. This allows testing to work and CI to succeed: % dotnet test bin/TestDebug-net7.0/Java.Interop-Tests.dll # all good while allowing us to separately explore why it crashes: % JI_LOADER_TYPE=native-library dotnet test bin/TestDebug-net7.0/Java.Interop-Tests.dll … # jonp: LoadJvmLibrary(…/libjli.dylib)=9056174496 # jonp: JNI_CreateJavaVM=4561133901; JNI_GetCreatedJavaVMs=4561133970 # jonp: executing JNI_CreateJavaVM=10fdd614d Error occurred during initialization of VM Could not reserve enough space in CodeHeap 'non-nmethods' (2496K) The active test run was aborted. Reason: Test host process crashed Test Run Aborted with error System.Exception: One or more errors occurred. ---> System.Exception: Unable to read beyond the end of the stream. at System.IO.BinaryReader.Read7BitEncodedInt() at System.IO.BinaryReader.ReadString() at Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.LengthPrefixCommunicationChannel.NotifyDataAvailable() at Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.TcpClientExtensions.MessageLoopAsync(TcpClient client, ICommunicationChannel channel, Action`1 errorHandler, CancellationToken cancellationToken) --- End of inner exception stack trace ---. [0]: https://discord.com/channels/732297728826277939/732297837953679412/979054761603125319 [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNI_GetCreatedJavaVMs [2]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativelibrary?view=net-7.0
1f15986
to
332fa65
Compare
@jpobst: updated to not abuse I'm not sure if that name is any better, either, but at least the Configuration values are sane again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would probably be a good idea to run a XA test bump against this before committing to ensure nothing broke.
Context: dotnet/java-interop#1049 Does It Build™?
@jpobst: Good suggestion. dotnet/android#7509 |
Context: dotnet/java-interop#1049 Does It Build™?
dotnet/android#7509 is "good enough". Merging. |
Changes: dotnet/java-interop@5318261...c6c487b * dotnet/java-interop@c6c487b6: [Java.Interop] Optional "Standalone" Build Config (dotnet/java-interop#1049) * dotnet/java-interop@5a0097b5: [ci] Enable CodeQL static analysis. (dotnet/java-interop#1057) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Context: 16e1ecd
Context: 312fbf4
It occurs to me that it would be easier for "external" developers to
use
Java.Interop.dll
if it didn't require building and distributingany native libraries. Furthermore, as of commit 312fbf4 (C#9
function pointer backend), it's plausible to make that work.
Let's do so.
Add new "pseudo"
Standalone-Debug
andStandalone-Release
build configurations to
Java.Interop.csproj
which set theFEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS
compiler define insteadof the
FEATURE_JNIENVIRONMENT_JI_PINVOKES
define. This enablesJniEnvironment
to use C#9 function pointers instead of P/Invokes toinvoke the
JNIEnv
function pointers. They're "pseudo"configurations because they don't actually exist within
Java.Interop.sln
, and thus can only be built via command line, orvia
<ProjectReference AdditionalProperties="…"/>
wonkery:Update
Java.Interop.dll
to compile whenFEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS
is set.!!ABI BREAK!!
[Obsolete]
the methodJniRuntime.GetAvailableInvocationPointers()
. In retrospect thisnever should have been exposed at this level of the stack, and its
existence was responsible for "really really bizarre" .NET Android
app crashes (due to static constructor orderings) when
sometimes
JniRuntime.Current
wasn't set "early enough":Nobody should be using this method, largely given that only
Xamarin.Android and .NET Android apps currently use
Java.Interop.dll
, and neither useJniRuntime.GetAvailableInvocationPointers()
. Furthermore,it can't work on Android, as Android doesn't provide a public
JNI_GetCreatedJavaVMs()
symbol.Update
build-tools/jnienv-gen
so that aJniNativeMethods
classis defined which contains "human usable" ways to invoke
JNIEnv
function pointers. (Nobody wants to copy the expression
(*((JNIEnv**)env))->ExceptionClear(env)
more than once, ever.JniNativeMethods.ExceptionClear(env)
is much nicer to write.)Update
samples/Hello-Core
so that it uses theStandalone-
pseudo configuration instead of the "regular" peer config.
Verification: After building
samples/Hello-Core
, the containedJava.Interop.dll
doesn't contain anypinvokeimpl
methods:TODO: I also attempted to reduce the number of P/Invokes in
Java.Runtime.Environment.dll
, with the hope that when not usingMonoVM it could be used without a native
java-interop
library.This used
System.Runtime.InteropServices.NativeLibrary
to loadJniRuntime.CreationOptions.JvmLibraryPath
and invoke theJNI_CreateJavaVM()
andJNI_GetCreatedJavaVMs()
exports.Unfortunately, this new backend crashes inexplicably when using
dotnet test
. The backend can now be selected by setting theJI_LOADER_TYPE
environment variable to one of:native-library
: theNativeLibrary
backened, orjava-interop
: the previousjava-interop
native lib backend.This allows testing to work and CI to succeed:
while allowing us to separately explore why it crashes: