Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[jnienv-gen] fix p/invoke usage for .NET framework
I recently attempted to use Java.Interop from a full .NET framework console application on Windows. We don't currently build `java-interop.dll` for Windows, so I: * Took `C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Xamarin\Android\libmono-android.release.dll` and just renamed it to `java-interop.dll`. * Since this is a 64-bit binary, I made the .NET framework project targeting `x64` only (it was *not* `AnyCPU`). * I added `java-interop.dll` as a `Content` build action. My console app was attempting to run the `main` method of `r8.jar`: var builder = new JreRuntimeOptions { JvmLibraryPath = @"C:\Users\jopepper\android-toolchain\jdk\jre\bin\server\jvm.dll", MarshalMemberBuilder = new ProxyMarshalMemberBuilder (), ObjectReferenceManager = new ProxyObjectReferenceManager (), ValueManager = new ProxyValueManager (), TypeManager = new ProxyTypeManager (), }; builder.ClassPath.Add (@"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Xamarin\Android\r8.jar"); using (var jre = builder.CreateJreVM ()) { var @string = new JniType ("java/lang/String"); var swissArmyKnife = new JniType ("com.android.tools.r8.SwissArmyKnife"); var main = swissArmyKnife.GetStaticMethod ("main", "([Ljava/lang/String;)V"); var help = JniEnvironment.Strings.NewString ("--help"); var args = JniEnvironment.Arrays.NewObjectArray (1, @string.PeerReference, help); var __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (args); JniEnvironment.StaticMethods.CallStaticVoidMethod (swissArmyKnife.PeerReference, main, __args); } Unfortunately this code crashes at runtime with a cryptic error on any p/invoke using `JniArgumentValue*`: System.Runtime.InteropServices.MarshalDirectiveException: Cannot marshal 'parameter dotnet#5': Pointers cannot reference marshaled structures. Use ByRef instead. This seems like a limitation of .NET framework... However, it seems to work fine if we use `IntPtr` instead and just cast any `JniArgumentValue*` values to `IntPtr`. So for example, the p/invoke can change to: [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)] internal static extern unsafe jobject java_interop_jnienv_call_object_method_a (IntPtr jnienv, out IntPtr thrown, jobject instance, IntPtr method, IntPtr args); `args` used to be a `JniArgumentValue*`. Other generated methods need a cast, such as: public static unsafe JniObjectReference CallObjectMethod (JniObjectReference instance, JniMethodInfo method, JniArgumentValue* args) { ... IntPtr thrown; var tmp = NativeMethods.java_interop_jnienv_call_object_method_a (JniEnvironment.EnvironmentPointer, out thrown, instance.Handle, method.ID, (IntPtr) args); ... } After this, my .NET framework console app was able to start, and it printed `r8 --help` output.
- Loading branch information