Skip to content

Commit

Permalink
[Java.Interop, Java.Interop.Export] Add JniValueMarshalerAttribute
Browse files Browse the repository at this point in the history
Mentioned in ff4053c was a feature to complete Issue #8:

> Need to permit use of custom attributes on return types, parameter
> types, "inline" into marshal methods.

Let's do that. :-)

The new Java.Interop.JniValueMarshalerAttribute custom attribute can
be applied to:

  * Types: classes, enums, interfaces, structs

This allows:

	[JniValueMarshaler (typeof (MyCustomMarshaler))]
	public class MySpecialClass /* NOT JavaObject! */ {
	}

	public partial class MyCustomMarshaler : JniValueMarshaler<MySpecialClass> {
		// ...
	}

JniRuntime.JniValueManager.GetValueMarshaler(Type) has been updated to
check for the JniValueMarshalerAttribute and, when present, will
return a new instance of the specified JniValueMarshaler instance:

	var marshaler = JniRuntime.CurrentRuntime.ValueManager.GetValueMarshaler (typeof (MySpecialClass));
	// marshaler ISA MyCustomMarshaler

JniValueMarshalerAttribute can also be applied to:

  * Method parameters
  * Method return types

Java.Interop.Export has been updated to check for the
JniValueMarshalerAttribute custom attribute to marshal parameters and
return types, allowing:

	// e.g. code from an existing library
	public class ExistingType {
	}

	public partial class ExistingTypeValueMarshaler : JniValueMarshaler<ExistingType> {
		// ...
	}

	[JavaCallable]
	public static
		[return: JniValueMarshaler (typeof (ExistingTypeValueMarshaler))] ExistingType
		Foo ([JniValueMarshaler (typeof (ExistingTypeValueMarshaler))] ExistingType value)
	{
		return value;
	}

This allows one-off specification or overriding of value marshalers
for method parameter and return types, particularly useful if you want
to marshal a type that you don't control, and thus can't alter to
contain a [JniValueMarshaler] declaration.
  • Loading branch information
jonpryor committed Dec 22, 2015
1 parent 96e0ecf commit da5d1b8
Show file tree
Hide file tree
Showing 11 changed files with 533 additions and 63 deletions.
41 changes: 29 additions & 12 deletions src/Java.Interop.Export/Java.Interop/ExportedMemberBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,27 @@ public virtual string GetJniMethodSignature (JavaCallableAttribute export, Metho

var signature = new StringBuilder ().Append ("(");
foreach (var p in method.GetParameters ()) {
var info = Runtime.TypeManager.GetTypeSignature (p.ParameterType);
if (info.SimpleReference == null)
throw new NotSupportedException ("Don't know how to determine JNI signature for parameter type: " + p.ParameterType.FullName + ".");
signature.Append (info.QualifiedReference);
signature.Append (GetTypeSignature (p));
}
signature.Append (")");
var ret = Runtime.TypeManager.GetTypeSignature (method.ReturnType);
if (ret.SimpleReference == null)
throw new NotSupportedException ("Don't know how to determine JNI signature for return type: " + method.ReturnType.FullName + ".");
signature.Append (ret.QualifiedReference);
signature.Append (GetTypeSignature (method.ReturnParameter));
return export.Signature = signature.ToString ();
}

string GetTypeSignature (ParameterInfo p)
{
var info = Runtime.TypeManager.GetTypeSignature (p.ParameterType);
if (info.IsValid)
return info.QualifiedReference;

var marshaler = GetValueMarshaler (p);
info = Runtime.TypeManager.GetTypeSignature (marshaler.MarshalType);
if (info.IsValid)
return info.QualifiedReference;

throw new NotSupportedException ("Don't know how to determine JNI signature for parameter type: " + p.ParameterType.FullName + ".");
}

Delegate CreateJniMethodMarshaler (JavaCallableAttribute export, Type type, MethodInfo method)
{
var e = CreateMarshalFromJniMethodExpression (export, type, method);
Expand Down Expand Up @@ -137,7 +145,7 @@ public virtual LambdaExpression CreateMarshalFromJniMethodExpression (JavaCallab
var marshalParameters = new List<ParameterExpression> (methodParameters.Length);
var invokeParameters = new List<Expression> (methodParameters.Length);
for (int i = 0; i < methodParameters.Length; ++i) {
var marshaler = Runtime.ValueManager.GetValueMarshaler (methodParameters [i].ParameterType);
var marshaler = GetValueMarshaler (methodParameters [i]);
var np = Expression.Parameter (marshaler.MarshalType, methodParameters [i].Name);
var p = marshaler.CreateParameterToManagedExpression (marshalerContext, np, methodParameters [i].Attributes, methodParameters [i].ParameterType);
marshalParameters.Add (np);
Expand All @@ -160,7 +168,7 @@ public virtual LambdaExpression CreateMarshalFromJniMethodExpression (JavaCallab
CreateDisposeJniEnvironment (envp, marshalerContext.CleanupStatements),
CreateMarshalException (envp, null)));
} else {
var rmarshaler = Runtime.ValueManager.GetValueMarshaler (method.ReturnType);
var rmarshaler = GetValueMarshaler (method.ReturnParameter);
var jniRType = rmarshaler.MarshalType;
var exit = Expression.Label (jniRType, "__exit");
var mret = Expression.Variable (method.ReturnType, "__mret");
Expand Down Expand Up @@ -200,6 +208,15 @@ public virtual LambdaExpression CreateMarshalFromJniMethodExpression (JavaCallab
return Expression.Lambda (marshalerType, body, bodyParams);
}

JniValueMarshaler GetValueMarshaler (ParameterInfo parameter)
{
var attr = parameter.GetCustomAttribute<JniValueMarshalerAttribute> ();
if (attr != null) {
return (JniValueMarshaler) Activator.CreateInstance (attr.MarshalerType);
}
return Runtime.ValueManager.GetValueMarshaler (parameter.ParameterType);
}

void CheckMarshalTypesMatch (MethodInfo method, string signature, ParameterInfo[] methodParameters)
{
if (signature == null)
Expand All @@ -208,7 +225,7 @@ void CheckMarshalTypesMatch (MethodInfo method, string signature, ParameterInfo[
var mptypes = JniSignature.GetMarshalParameterTypes (signature).ToList ();
int len = Math.Min (methodParameters.Length, mptypes.Count);
for (int i = 0; i < len; ++i) {
var vm = Runtime.ValueManager.GetValueMarshaler (methodParameters [i].ParameterType);
var vm = GetValueMarshaler (methodParameters [i]);
var jni = vm.MarshalType;
if (mptypes [i] != jni)
throw new ArgumentException (
Expand All @@ -223,7 +240,7 @@ void CheckMarshalTypesMatch (MethodInfo method, string signature, ParameterInfo[
"signature");

var jrinfo = JniSignature.GetMarshalReturnType (signature);
var mrvm = Runtime.ValueManager.GetValueMarshaler (method.ReturnType);
var mrvm = GetValueMarshaler (method.ReturnParameter);
var mrinfo = mrvm.MarshalType;
if (mrinfo != jrinfo)
throw new ArgumentException (
Expand Down
119 changes: 119 additions & 0 deletions src/Java.Interop.Export/Tests/Java.Interop/ExportTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Linq.Expressions;
using System.Reflection;

using Java.Interop;
using Java.Interop.Expressions;

namespace Java.InteropTests
{
Expand Down Expand Up @@ -36,6 +39,12 @@ public static void StaticActionInt32String (int i, string v)
StaticActionInt32StringCalled = i == 1 && v == "2";
}

[JavaCallable ("staticFuncMyLegacyColorMyColor_MyColor")]
public static MyColor StaticFuncMyLegacyColorMyColor_MyColor ([JniValueMarshaler (typeof (MyLegacyColorValueMarshaler))] MyLegacyColor color1, MyColor color2)
{
return new MyColor (color1.Value + color2.Value);
}

[JavaCallable ("funcInt64", Signature = "()J")]
public long FuncInt64 ()
{
Expand All @@ -48,5 +57,115 @@ public JavaObject FuncIJavaObject ()
return this;
}
}

[JniValueMarshaler (typeof (MyColorValueMarshaler))]
public struct MyColor {

public readonly int Value;

public MyColor (int value)
{
Value = value;
}
}

// Note: no [JniValueMarshaler] type; we use a parameter custom attribute instead.
public struct MyLegacyColor {

public readonly int Value;

public MyLegacyColor (int value)
{
Value = value;
}
}

public class MyColorValueMarshaler : JniValueMarshaler<MyColor> {

public override Type MarshalType {
get {return typeof (int);}
}

public override MyColor CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType)
{
throw new NotImplementedException ();
}

public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (MyColor value, ParameterAttributes synchronize)
{
throw new NotImplementedException ();
}

public override void DestroyGenericArgumentState (MyColor value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
{
throw new NotImplementedException ();
}

public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type targetType)
{
var c = typeof (MyColor).GetConstructor (new[]{typeof (int)});
var v = Expression.Variable (typeof (MyColor), sourceValue.Name + "_val");
context.LocalVariables.Add (v);
context.CreationStatements.Add (Expression.Assign (v, Expression.New (c, sourceValue)));
return v;
}

public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize)
{
var r = Expression.Variable (MarshalType, sourceValue.Name + "_p");
context.LocalVariables.Add (r);
context.CreationStatements.Add (Expression.Assign (r, Expression.Field (sourceValue, "Value")));
return r;
}

public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue)
{
return CreateParameterFromManagedExpression (context, sourceValue, 0);
}
}

public class MyLegacyColorValueMarshaler : JniValueMarshaler<MyLegacyColor> {

public override Type MarshalType {
get {return typeof (int);}
}

public override MyLegacyColor CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType)
{
throw new NotImplementedException ();
}

public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (MyLegacyColor value, ParameterAttributes synchronize)
{
throw new NotImplementedException ();
}

public override void DestroyGenericArgumentState (MyLegacyColor value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
{
throw new NotImplementedException ();
}

public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type targetType)
{
var c = typeof (MyLegacyColor).GetConstructor (new[]{typeof (int)});
var v = Expression.Variable (typeof (MyLegacyColor), sourceValue.Name + "_val");
context.LocalVariables.Add (v);
context.CreationStatements.Add (Expression.Assign (v, Expression.New (c, sourceValue)));
return v;
}

public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize)
{
var r = Expression.Variable (MarshalType, sourceValue.Name + "_p");
context.LocalVariables.Add (r);
context.CreationStatements.Add (Expression.Assign (r, Expression.Field (sourceValue, "Value")));
return r;
}

public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue)
{
return CreateParameterFromManagedExpression (context, sourceValue, 0);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void AddExportMethods ()
var methods = CreateBuilder ()
.GetExportedMemberRegistrations (typeof (ExportTest))
.ToList ();
Assert.AreEqual (5, methods.Count);
Assert.AreEqual (6, methods.Count);

Assert.AreEqual ("action", methods [0].Name);
Assert.AreEqual ("()V", methods [0].Signature);
Expand Down Expand Up @@ -279,6 +279,46 @@ public void CreateMarshalFromJniMethodExpression_StaticActionInt32String ()
}");
}

[Test]
public void CreateMarshalFromJniMethodExpression_StaticFuncMyLegacyColorMyColor_MyColor ()
{
var t = typeof (ExportTest);
var m = ((Func<MyLegacyColor, MyColor, MyColor>) ExportTest.StaticFuncMyLegacyColorMyColor_MyColor);
var e = new JavaCallableAttribute () {
Signature = "(II)I",
};
CheckCreateInvocationExpression (e, t, m.Method, typeof (Func<IntPtr, IntPtr, int, int, int>),
@"int (IntPtr __jnienv, IntPtr __class, int color1, int color2)
{
JniTransition __envp;
JniRuntime __jvm;
MyColor __mret;
MyLegacyColor color1_val;
MyColor color2_val;
int __mret_p;
__envp = new JniTransition(__jnienv);
try
{
__jvm = JniEnvironment.Runtime;
color1_val = new MyLegacyColor(color1);
color2_val = new MyColor(color2);
__mret = ExportTest.StaticFuncMyLegacyColorMyColor_MyColor(color1_val, color2_val);
__mret_p = __mret.Value;
return __mret_p;
}
catch (Exception __e)
{
__envp.SetPendingException(__e);
return default(int);
}
finally
{
__envp.Dispose();
}
}");
}

[Test]
public void CreateMarshalFromJniMethodExpression_FuncInt64 ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ public class ExportType {
public static void testStaticMethods () {
staticAction ();
staticActionInt32String (1, "2");

int v = staticFuncMyLegacyColorMyColor_MyColor (1, 41);
if (v != 42)
throw new Error ("staticFuncMyEnum_MyEnum should return 42!");
}

public static native void staticAction ();
public static native void staticActionInt32String (int i, String s);
public static native int staticFuncMyLegacyColorMyColor_MyColor (int color1, int color2);

public void testMethods () {
action ();
Expand Down
1 change: 1 addition & 0 deletions src/Java.Interop/Java.Interop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
<Compile Include="Java.Interop\JniStringValueMarshaler.cs" />
<Compile Include="Java.Interop\JniSystem.cs" />
<Compile Include="Java.Interop\JniValueMarshaler.cs" />
<Compile Include="Java.Interop\JniValueMarshalerAttribute.cs" />
<Compile Include="Java.Interop\JniWeakGlobalReference.cs" />
<Compile Include="Java.Interop\ManagedPeer.cs" />
</ItemGroup>
Expand Down
19 changes: 19 additions & 0 deletions src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,10 @@ public JniValueMarshaler GetValueMarshaler (Type type)
if (info.ContainsGenericParameters)
throw new ArgumentException ("Generic type definitions are not supported.", "type");

var marshalerAttr = info.GetCustomAttribute<JniValueMarshalerAttribute> ();
if (marshalerAttr != null)
return (JniValueMarshaler) Activator.CreateInstance (marshalerAttr.MarshalerType);

if (typeof (IJavaPeerable) == type)
return JavaPeerableValueMarshaler.Instance;

Expand Down Expand Up @@ -680,6 +684,21 @@ public override void DestroyGenericArgumentState (T value, ref JniValueMarshaler
{
ValueMarshaler.DestroyArgumentState (value, ref state, synchronize);
}

public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize)
{
return ValueMarshaler.CreateParameterFromManagedExpression (context, sourceValue, synchronize);
}

public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type targetType)
{
return ValueMarshaler.CreateParameterToManagedExpression (context, sourceValue, synchronize, targetType);
}

public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue)
{
return ValueMarshaler.CreateReturnValueFromManagedExpression (context, sourceValue);
}
}

class ProxyValueMarshaler : JniValueMarshaler<object> {
Expand Down
4 changes: 2 additions & 2 deletions src/Java.Interop/Java.Interop/JniValueMarshaler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,13 @@ public virtual Expression CreateParameterFromManagedExpression

context.LocalVariables.Add (state);
context.LocalVariables.Add (ret);
context.CreationStatements.Add (Expression.Assign (state, Expression.Call (self, c.GetMethodInfo (), sourceValue, Expression.Constant (synchronize, typeof (ParameterAttributes)))));
context.CreationStatements.Add (Expression.Assign (state, Expression.Call (self, c.GetMethodInfo (), Expression.Convert (sourceValue, typeof (object)), Expression.Constant (synchronize, typeof (ParameterAttributes)))));
context.CreationStatements.Add (
Expression.Assign (ret,
Expression.Property (
Expression.Property (state, "ReferenceValue"),
"Handle")));
context.CleanupStatements.Add (Expression.Call (self, d.GetMethodInfo (), sourceValue, state, Expression.Constant (synchronize)));
context.CleanupStatements.Add (Expression.Call (self, d.GetMethodInfo (), Expression.Convert (sourceValue, typeof (object)), state, Expression.Constant (synchronize)));

return ret;
}
Expand Down
29 changes: 29 additions & 0 deletions src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Reflection;

namespace Java.Interop {

[AttributeUsage (Targets, AllowMultiple=false)]
public class JniValueMarshalerAttribute : Attribute {

const AttributeTargets Targets =
AttributeTargets.Class | AttributeTargets.Enum |
AttributeTargets.Interface | AttributeTargets.Struct |
AttributeTargets.Parameter | AttributeTargets.ReturnValue;

public JniValueMarshalerAttribute (Type marshalerType)
{
if (marshalerType == null)
throw new ArgumentNullException (nameof (marshalerType));
if (!typeof (JniValueMarshaler).GetTypeInfo ().IsAssignableFrom (marshalerType.GetTypeInfo ()))
throw new ArgumentException (
string.Format ("`{0}` must inherit from JniValueMarshaler!", marshalerType.FullName),
nameof (marshalerType));

MarshalerType = marshalerType;
}

public Type MarshalerType {get;}
}
}

1 change: 1 addition & 0 deletions src/Java.Interop/Tests/Interop-Tests.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniRuntimeTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniRuntime.JniValueManagerTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniTypeManagerTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniValueMarshalerAttributeTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniValueMarshalerContractTests.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Loading

0 comments on commit da5d1b8

Please sign in to comment.