Skip to content

Commit

Permalink
[jnienv-gen] fix p/invoke usage for .NET framework
Browse files Browse the repository at this point in the history
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
jonathanpeppers committed Aug 9, 2019
1 parent fd774a5 commit 1cf210b
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 19 deletions.
55 changes: 37 additions & 18 deletions build-tools/jnienv-gen/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,11 @@ static void GenerateNativeMethods (TextWriter o, HandleStyle style)
o.WriteLine ();
o.WriteLine ("\t\t[DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)]");
o.WriteLine ("\t\tinternal static extern unsafe {0} {1} (IntPtr jnienv{2}{3}{4});",
entry.ReturnType.GetMarshalType (style, isReturn: true),
entry.ReturnType.GetMarshalType (style, isReturn: true, isPinvoke: true),
GetPinvokeName (entry.Name),
entry.Throws ? ", out IntPtr thrown" : "",
entry.Parameters.Length != 0 ? ", " : "",
string.Join (", ", entry.Parameters.Select (p => string.Format ("{0} {1}", p.Type.GetMarshalType (style, isReturn: false), Escape (p.Name)))));
string.Join (", ", entry.Parameters.Select (p => string.Format ("{0} {1}", p.Type.GetMarshalType (style, isReturn: false, isPinvoke: true), Escape (p.Name)))));
}
o.WriteLine ("\t}");
o.WriteLine ();
Expand Down Expand Up @@ -653,8 +653,8 @@ protected TypeInfo (string jniType)
JniType = jniType;
}

public abstract string GetMarshalType (HandleStyle style, bool isReturn);
public abstract string GetManagedType (HandleStyle style, bool isReturn);
public abstract string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke = false);
public abstract string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke = false);

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

class BuiltinTypeInfo : TypeInfo {

/// <summary>
/// NOTE: .NET framework can't marshal this
/// </summary>
const string JniArgumentValue = "JniArgumentValue*";
string managed;

public BuiltinTypeInfo (string jni, string managed)
Expand All @@ -689,16 +693,31 @@ public BuiltinTypeInfo (string jni, string managed)
this.managed = managed;
}

public override string GetMarshalType (HandleStyle style, bool isReturn)
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
{
if (isPinvoke && managed == JniArgumentValue) {
return "IntPtr";
}
return managed;
}

public override string GetManagedType (HandleStyle style, bool isReturn)
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
{
if (isPinvoke && managed == JniArgumentValue) {
return "IntPtr";
}
return managed;
}

public override string GetManagedToMarshalExpression (HandleStyle style, string variable)
{
var value = base.GetManagedToMarshalExpression (style, variable);
if (managed == JniArgumentValue) {
value = "(IntPtr) " + value;
}
return value;
}

public override string[] VerifyParameter (HandleStyle style, string variable)
{
if (managed != "IntPtr")
Expand All @@ -720,12 +739,12 @@ public BooleanTypeInfo (string jni)
{
}

public override string GetMarshalType (HandleStyle style, bool isReturn)
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "byte";
}

public override string GetManagedType (HandleStyle style, bool isReturn)
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "bool";
}
Expand All @@ -750,12 +769,12 @@ public StringTypeInfo (string jni)
{
}

public override string GetMarshalType (HandleStyle style, bool isReturn)
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "string";
}

public override string GetManagedType (HandleStyle style, bool isReturn)
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "string";
}
Expand Down Expand Up @@ -798,12 +817,12 @@ public JniReleaseArrayElementsModeTypeInfo ()
{
}

public override string GetMarshalType (HandleStyle style, bool isReturn)
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "int";
}

public override string GetManagedType (HandleStyle style, bool isReturn)
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "JniReleaseArrayElementsMode";
}
Expand Down Expand Up @@ -839,12 +858,12 @@ public IdTypeInfo (string jni, string type)
this.type = type;
}

public override string GetMarshalType (HandleStyle style, bool isReturn)
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "IntPtr";
}

public override string GetManagedType (HandleStyle style, bool isReturn)
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
{
switch (style) {
case HandleStyle.SafeHandle:
Expand Down Expand Up @@ -968,7 +987,7 @@ public ObjectReferenceTypeInfo (string jni, string safeType, string refType)
this.refType = refType;
}

public override string GetMarshalType (HandleStyle style, bool isReturn)
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
{
switch (style) {
case HandleStyle.SafeHandle:
Expand All @@ -981,7 +1000,7 @@ public override string GetMarshalType (HandleStyle style, bool isReturn)
return null;
}

public override string GetManagedType (HandleStyle style, bool isReturn)
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
{
switch (style) {
case HandleStyle.SafeHandle:
Expand Down Expand Up @@ -1088,12 +1107,12 @@ public JavaVMPointerTypeInfo (string jni)
{
}

public override string GetMarshalType (HandleStyle style, bool isReturn)
public override string GetMarshalType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "out IntPtr";
}

public override string GetManagedType (HandleStyle style, bool isReturn)
public override string GetManagedType (HandleStyle style, bool isReturn, bool isPinvoke)
{
return "out IntPtr";
}
Expand Down
2 changes: 1 addition & 1 deletion src/Java.Interop/Java.Interop/JniEnvironment.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static unsafe JniObjectReference FindClass (string classname)
__args [0] = new JniArgumentValue (java);

IntPtr ignoreThrown;
c = NativeMethods.java_interop_jnienv_call_object_method_a (info.EnvironmentPointer, out ignoreThrown, info.Runtime.ClassLoader.Handle, info.Runtime.ClassLoader_LoadClass.ID, __args);
c = NativeMethods.java_interop_jnienv_call_object_method_a (info.EnvironmentPointer, out ignoreThrown, info.Runtime.ClassLoader.Handle, info.Runtime.ClassLoader_LoadClass.ID, (IntPtr) __args);
JniObjectReference.Dispose (ref java);
if (ignoreThrown == IntPtr.Zero) {
JniObjectReference.Dispose (ref e);
Expand Down

0 comments on commit 1cf210b

Please sign in to comment.