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 version = JniEnvironment.Strings.NewString ("--help");
        var empty = JniEnvironment.Arrays.NewObjectArray (1, @string.PeerReference, version);
        var __args = stackalloc JniArgumentValue [1];
        __args [0] = new JniArgumentValue (empty);
        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`.
  • Loading branch information
jonathanpeppers committed Aug 9, 2019
1 parent fd774a5 commit 5117ccb
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 21 deletions.
61 changes: 41 additions & 20 deletions build-tools/jnienv-gen/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ partial class Generator

public static int Main (string [] args)
{
//System.Diagnostics.Debugger.Launch ();
jnienv_g_c = "JniEnvironment.g.c";
jnienv_g_cs = "JniEnvironment.g.cs";
if (args.Length > 0)
Expand Down Expand Up @@ -308,11 +309,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 @@ -354,9 +355,10 @@ static void GenerateJniEnv (TextWriter o, string type, string visibility, Handle
default:
bool is_void = entry.ReturnType.JniType == "void";
for (int i = 0; i < entry.Parameters.Length; i++) {
var p = entry.Parameters [i];
if (i > 0)
o.Write (", ");
o.Write ("{0} {1}", entry.Parameters [i].Type.GetManagedType (style, isReturn: false), Escape (entry.Parameters [i].Name));
o.Write ("{0} {1}", p.Type.GetManagedType (style, isReturn: false), Escape (p.Name));
}
o.WriteLine (")");
o.WriteLine ("\t\t{");
Expand All @@ -382,7 +384,7 @@ static void GenerateJniEnv (TextWriter o, string type, string visibility, Handle
o.Write (", ");
if (p.Type.GetManagedType (style, isReturn: false).StartsWith ("out ", StringComparison.Ordinal))
o.Write ("out ");
o.Write (p.Type.GetManagedToMarshalExpression (style, Escape (entry.Parameters [i].Name)));
o.Write (p.Type.GetManagedToMarshalExpression (style, Escape (p.Name)));
}
o.WriteLine (");");
RaiseException (o, entry, style);
Expand Down Expand Up @@ -653,8 +655,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 +683,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 +695,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 +741,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 +771,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 +819,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 +860,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 +989,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 +1002,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 +1109,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 5117ccb

Please sign in to comment.