Skip to content

Commit

Permalink
[jnienv-gen] fix p/invoke usage for .NET framework (#460)
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 #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 authored and jonpryor committed Aug 12, 2019
1 parent 285a32b commit 44ccd13
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 267 deletions.
59 changes: 39 additions & 20 deletions build-tools/jnienv-gen/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,11 @@ static void CreateDelegate (TextWriter o, JniFunction entry, HandleStyle style)
if (i >= 0) {
builder.Append (", ");
builder.AppendFormat ("{0} {1}",
entry.Parameters [i].Type.GetMarshalType (style, isReturn: false),
entry.Parameters [i].Type.GetMarshalType (style, isReturn: false, isPinvoke: true),
Escape (entry.Parameters [i].Name));
}

var ptype = entry.Parameters [i].Type.GetManagedType (style, isReturn: false);
var ptype = entry.Parameters [i].Type.GetManagedType (style, isReturn: false, isPinvoke: true);
if (ptype == "va_list")
return;
if (ptype == "char[]")
Expand Down 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
Loading

0 comments on commit 44ccd13

Please sign in to comment.