Skip to content

Commit 1cf210b

Browse files
[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.
1 parent fd774a5 commit 1cf210b

File tree

2 files changed

+38
-19
lines changed

2 files changed

+38
-19
lines changed

build-tools/jnienv-gen/Generator.cs

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,11 @@ static void GenerateNativeMethods (TextWriter o, HandleStyle style)
308308
o.WriteLine ();
309309
o.WriteLine ("\t\t[DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)]");
310310
o.WriteLine ("\t\tinternal static extern unsafe {0} {1} (IntPtr jnienv{2}{3}{4});",
311-
entry.ReturnType.GetMarshalType (style, isReturn: true),
311+
entry.ReturnType.GetMarshalType (style, isReturn: true, isPinvoke: true),
312312
GetPinvokeName (entry.Name),
313313
entry.Throws ? ", out IntPtr thrown" : "",
314314
entry.Parameters.Length != 0 ? ", " : "",
315-
string.Join (", ", entry.Parameters.Select (p => string.Format ("{0} {1}", p.Type.GetMarshalType (style, isReturn: false), Escape (p.Name)))));
315+
string.Join (", ", entry.Parameters.Select (p => string.Format ("{0} {1}", p.Type.GetMarshalType (style, isReturn: false, isPinvoke: true), Escape (p.Name)))));
316316
}
317317
o.WriteLine ("\t}");
318318
o.WriteLine ();
@@ -653,8 +653,8 @@ protected TypeInfo (string jniType)
653653
JniType = jniType;
654654
}
655655

656-
public abstract string GetMarshalType (HandleStyle style, bool isReturn);
657-
public abstract string GetManagedType (HandleStyle style, bool isReturn);
656+
public abstract string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke = false);
657+
public abstract string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke = false);
658658

659659
public virtual string[] GetHandleCreationLogStatements (HandleStyle style, string method, string variable)
660660
{
@@ -681,6 +681,10 @@ public virtual string[] VerifyParameter (HandleStyle style, string variable)
681681

682682
class BuiltinTypeInfo : TypeInfo {
683683

684+
/// <summary>
685+
/// NOTE: .NET framework can't marshal this
686+
/// </summary>
687+
const string JniArgumentValue = "JniArgumentValue*";
684688
string managed;
685689

686690
public BuiltinTypeInfo (string jni, string managed)
@@ -689,16 +693,31 @@ public BuiltinTypeInfo (string jni, string managed)
689693
this.managed = managed;
690694
}
691695

692-
public override string GetMarshalType (HandleStyle style, bool isReturn)
696+
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
693697
{
698+
if (isPinvoke && managed == JniArgumentValue) {
699+
return "IntPtr";
700+
}
694701
return managed;
695702
}
696703

697-
public override string GetManagedType (HandleStyle style, bool isReturn)
704+
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
698705
{
706+
if (isPinvoke && managed == JniArgumentValue) {
707+
return "IntPtr";
708+
}
699709
return managed;
700710
}
701711

712+
public override string GetManagedToMarshalExpression (HandleStyle style, string variable)
713+
{
714+
var value = base.GetManagedToMarshalExpression (style, variable);
715+
if (managed == JniArgumentValue) {
716+
value = "(IntPtr) " + value;
717+
}
718+
return value;
719+
}
720+
702721
public override string[] VerifyParameter (HandleStyle style, string variable)
703722
{
704723
if (managed != "IntPtr")
@@ -720,12 +739,12 @@ public BooleanTypeInfo (string jni)
720739
{
721740
}
722741

723-
public override string GetMarshalType (HandleStyle style, bool isReturn)
742+
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
724743
{
725744
return "byte";
726745
}
727746

728-
public override string GetManagedType (HandleStyle style, bool isReturn)
747+
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
729748
{
730749
return "bool";
731750
}
@@ -750,12 +769,12 @@ public StringTypeInfo (string jni)
750769
{
751770
}
752771

753-
public override string GetMarshalType (HandleStyle style, bool isReturn)
772+
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
754773
{
755774
return "string";
756775
}
757776

758-
public override string GetManagedType (HandleStyle style, bool isReturn)
777+
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
759778
{
760779
return "string";
761780
}
@@ -798,12 +817,12 @@ public JniReleaseArrayElementsModeTypeInfo ()
798817
{
799818
}
800819

801-
public override string GetMarshalType (HandleStyle style, bool isReturn)
820+
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
802821
{
803822
return "int";
804823
}
805824

806-
public override string GetManagedType (HandleStyle style, bool isReturn)
825+
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
807826
{
808827
return "JniReleaseArrayElementsMode";
809828
}
@@ -839,12 +858,12 @@ public IdTypeInfo (string jni, string type)
839858
this.type = type;
840859
}
841860

842-
public override string GetMarshalType (HandleStyle style, bool isReturn)
861+
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
843862
{
844863
return "IntPtr";
845864
}
846865

847-
public override string GetManagedType (HandleStyle style, bool isReturn)
866+
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
848867
{
849868
switch (style) {
850869
case HandleStyle.SafeHandle:
@@ -968,7 +987,7 @@ public ObjectReferenceTypeInfo (string jni, string safeType, string refType)
968987
this.refType = refType;
969988
}
970989

971-
public override string GetMarshalType (HandleStyle style, bool isReturn)
990+
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
972991
{
973992
switch (style) {
974993
case HandleStyle.SafeHandle:
@@ -981,7 +1000,7 @@ public override string GetMarshalType (HandleStyle style, bool isReturn)
9811000
return null;
9821001
}
9831002

984-
public override string GetManagedType (HandleStyle style, bool isReturn)
1003+
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
9851004
{
9861005
switch (style) {
9871006
case HandleStyle.SafeHandle:
@@ -1088,12 +1107,12 @@ public JavaVMPointerTypeInfo (string jni)
10881107
{
10891108
}
10901109

1091-
public override string GetMarshalType (HandleStyle style, bool isReturn)
1110+
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
10921111
{
10931112
return "out IntPtr";
10941113
}
10951114

1096-
public override string GetManagedType (HandleStyle style, bool isReturn)
1115+
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
10971116
{
10981117
return "out IntPtr";
10991118
}

src/Java.Interop/Java.Interop/JniEnvironment.Types.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static unsafe JniObjectReference FindClass (string classname)
5353
__args [0] = new JniArgumentValue (java);
5454

5555
IntPtr ignoreThrown;
56-
c = NativeMethods.java_interop_jnienv_call_object_method_a (info.EnvironmentPointer, out ignoreThrown, info.Runtime.ClassLoader.Handle, info.Runtime.ClassLoader_LoadClass.ID, __args);
56+
c = NativeMethods.java_interop_jnienv_call_object_method_a (info.EnvironmentPointer, out ignoreThrown, info.Runtime.ClassLoader.Handle, info.Runtime.ClassLoader_LoadClass.ID, (IntPtr) __args);
5757
JniObjectReference.Dispose (ref java);
5858
if (ignoreThrown == IntPtr.Zero) {
5959
JniObjectReference.Dispose (ref e);

0 commit comments

Comments
 (0)