Skip to content

Commit da5d1b8

Browse files
committed
[Java.Interop, Java.Interop.Export] Add JniValueMarshalerAttribute
Mentioned in ff4053c was a feature to complete Issue dotnet#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.
1 parent 96e0ecf commit da5d1b8

File tree

11 files changed

+533
-63
lines changed

11 files changed

+533
-63
lines changed

src/Java.Interop.Export/Java.Interop/ExportedMemberBuilder.cs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,27 @@ public virtual string GetJniMethodSignature (JavaCallableAttribute export, Metho
7676

7777
var signature = new StringBuilder ().Append ("(");
7878
foreach (var p in method.GetParameters ()) {
79-
var info = Runtime.TypeManager.GetTypeSignature (p.ParameterType);
80-
if (info.SimpleReference == null)
81-
throw new NotSupportedException ("Don't know how to determine JNI signature for parameter type: " + p.ParameterType.FullName + ".");
82-
signature.Append (info.QualifiedReference);
79+
signature.Append (GetTypeSignature (p));
8380
}
8481
signature.Append (")");
85-
var ret = Runtime.TypeManager.GetTypeSignature (method.ReturnType);
86-
if (ret.SimpleReference == null)
87-
throw new NotSupportedException ("Don't know how to determine JNI signature for return type: " + method.ReturnType.FullName + ".");
88-
signature.Append (ret.QualifiedReference);
82+
signature.Append (GetTypeSignature (method.ReturnParameter));
8983
return export.Signature = signature.ToString ();
9084
}
9185

86+
string GetTypeSignature (ParameterInfo p)
87+
{
88+
var info = Runtime.TypeManager.GetTypeSignature (p.ParameterType);
89+
if (info.IsValid)
90+
return info.QualifiedReference;
91+
92+
var marshaler = GetValueMarshaler (p);
93+
info = Runtime.TypeManager.GetTypeSignature (marshaler.MarshalType);
94+
if (info.IsValid)
95+
return info.QualifiedReference;
96+
97+
throw new NotSupportedException ("Don't know how to determine JNI signature for parameter type: " + p.ParameterType.FullName + ".");
98+
}
99+
92100
Delegate CreateJniMethodMarshaler (JavaCallableAttribute export, Type type, MethodInfo method)
93101
{
94102
var e = CreateMarshalFromJniMethodExpression (export, type, method);
@@ -137,7 +145,7 @@ public virtual LambdaExpression CreateMarshalFromJniMethodExpression (JavaCallab
137145
var marshalParameters = new List<ParameterExpression> (methodParameters.Length);
138146
var invokeParameters = new List<Expression> (methodParameters.Length);
139147
for (int i = 0; i < methodParameters.Length; ++i) {
140-
var marshaler = Runtime.ValueManager.GetValueMarshaler (methodParameters [i].ParameterType);
148+
var marshaler = GetValueMarshaler (methodParameters [i]);
141149
var np = Expression.Parameter (marshaler.MarshalType, methodParameters [i].Name);
142150
var p = marshaler.CreateParameterToManagedExpression (marshalerContext, np, methodParameters [i].Attributes, methodParameters [i].ParameterType);
143151
marshalParameters.Add (np);
@@ -160,7 +168,7 @@ public virtual LambdaExpression CreateMarshalFromJniMethodExpression (JavaCallab
160168
CreateDisposeJniEnvironment (envp, marshalerContext.CleanupStatements),
161169
CreateMarshalException (envp, null)));
162170
} else {
163-
var rmarshaler = Runtime.ValueManager.GetValueMarshaler (method.ReturnType);
171+
var rmarshaler = GetValueMarshaler (method.ReturnParameter);
164172
var jniRType = rmarshaler.MarshalType;
165173
var exit = Expression.Label (jniRType, "__exit");
166174
var mret = Expression.Variable (method.ReturnType, "__mret");
@@ -200,6 +208,15 @@ public virtual LambdaExpression CreateMarshalFromJniMethodExpression (JavaCallab
200208
return Expression.Lambda (marshalerType, body, bodyParams);
201209
}
202210

211+
JniValueMarshaler GetValueMarshaler (ParameterInfo parameter)
212+
{
213+
var attr = parameter.GetCustomAttribute<JniValueMarshalerAttribute> ();
214+
if (attr != null) {
215+
return (JniValueMarshaler) Activator.CreateInstance (attr.MarshalerType);
216+
}
217+
return Runtime.ValueManager.GetValueMarshaler (parameter.ParameterType);
218+
}
219+
203220
void CheckMarshalTypesMatch (MethodInfo method, string signature, ParameterInfo[] methodParameters)
204221
{
205222
if (signature == null)
@@ -208,7 +225,7 @@ void CheckMarshalTypesMatch (MethodInfo method, string signature, ParameterInfo[
208225
var mptypes = JniSignature.GetMarshalParameterTypes (signature).ToList ();
209226
int len = Math.Min (methodParameters.Length, mptypes.Count);
210227
for (int i = 0; i < len; ++i) {
211-
var vm = Runtime.ValueManager.GetValueMarshaler (methodParameters [i].ParameterType);
228+
var vm = GetValueMarshaler (methodParameters [i]);
212229
var jni = vm.MarshalType;
213230
if (mptypes [i] != jni)
214231
throw new ArgumentException (
@@ -223,7 +240,7 @@ void CheckMarshalTypesMatch (MethodInfo method, string signature, ParameterInfo[
223240
"signature");
224241

225242
var jrinfo = JniSignature.GetMarshalReturnType (signature);
226-
var mrvm = Runtime.ValueManager.GetValueMarshaler (method.ReturnType);
243+
var mrvm = GetValueMarshaler (method.ReturnParameter);
227244
var mrinfo = mrvm.MarshalType;
228245
if (mrinfo != jrinfo)
229246
throw new ArgumentException (

src/Java.Interop.Export/Tests/Java.Interop/ExportTest.cs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System;
2+
using System.Linq.Expressions;
3+
using System.Reflection;
24

35
using Java.Interop;
6+
using Java.Interop.Expressions;
47

58
namespace Java.InteropTests
69
{
@@ -36,6 +39,12 @@ public static void StaticActionInt32String (int i, string v)
3639
StaticActionInt32StringCalled = i == 1 && v == "2";
3740
}
3841

42+
[JavaCallable ("staticFuncMyLegacyColorMyColor_MyColor")]
43+
public static MyColor StaticFuncMyLegacyColorMyColor_MyColor ([JniValueMarshaler (typeof (MyLegacyColorValueMarshaler))] MyLegacyColor color1, MyColor color2)
44+
{
45+
return new MyColor (color1.Value + color2.Value);
46+
}
47+
3948
[JavaCallable ("funcInt64", Signature = "()J")]
4049
public long FuncInt64 ()
4150
{
@@ -48,5 +57,115 @@ public JavaObject FuncIJavaObject ()
4857
return this;
4958
}
5059
}
60+
61+
[JniValueMarshaler (typeof (MyColorValueMarshaler))]
62+
public struct MyColor {
63+
64+
public readonly int Value;
65+
66+
public MyColor (int value)
67+
{
68+
Value = value;
69+
}
70+
}
71+
72+
// Note: no [JniValueMarshaler] type; we use a parameter custom attribute instead.
73+
public struct MyLegacyColor {
74+
75+
public readonly int Value;
76+
77+
public MyLegacyColor (int value)
78+
{
79+
Value = value;
80+
}
81+
}
82+
83+
public class MyColorValueMarshaler : JniValueMarshaler<MyColor> {
84+
85+
public override Type MarshalType {
86+
get {return typeof (int);}
87+
}
88+
89+
public override MyColor CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType)
90+
{
91+
throw new NotImplementedException ();
92+
}
93+
94+
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (MyColor value, ParameterAttributes synchronize)
95+
{
96+
throw new NotImplementedException ();
97+
}
98+
99+
public override void DestroyGenericArgumentState (MyColor value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
100+
{
101+
throw new NotImplementedException ();
102+
}
103+
104+
public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type targetType)
105+
{
106+
var c = typeof (MyColor).GetConstructor (new[]{typeof (int)});
107+
var v = Expression.Variable (typeof (MyColor), sourceValue.Name + "_val");
108+
context.LocalVariables.Add (v);
109+
context.CreationStatements.Add (Expression.Assign (v, Expression.New (c, sourceValue)));
110+
return v;
111+
}
112+
113+
public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize)
114+
{
115+
var r = Expression.Variable (MarshalType, sourceValue.Name + "_p");
116+
context.LocalVariables.Add (r);
117+
context.CreationStatements.Add (Expression.Assign (r, Expression.Field (sourceValue, "Value")));
118+
return r;
119+
}
120+
121+
public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue)
122+
{
123+
return CreateParameterFromManagedExpression (context, sourceValue, 0);
124+
}
125+
}
126+
127+
public class MyLegacyColorValueMarshaler : JniValueMarshaler<MyLegacyColor> {
128+
129+
public override Type MarshalType {
130+
get {return typeof (int);}
131+
}
132+
133+
public override MyLegacyColor CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType)
134+
{
135+
throw new NotImplementedException ();
136+
}
137+
138+
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (MyLegacyColor value, ParameterAttributes synchronize)
139+
{
140+
throw new NotImplementedException ();
141+
}
142+
143+
public override void DestroyGenericArgumentState (MyLegacyColor value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
144+
{
145+
throw new NotImplementedException ();
146+
}
147+
148+
public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type targetType)
149+
{
150+
var c = typeof (MyLegacyColor).GetConstructor (new[]{typeof (int)});
151+
var v = Expression.Variable (typeof (MyLegacyColor), sourceValue.Name + "_val");
152+
context.LocalVariables.Add (v);
153+
context.CreationStatements.Add (Expression.Assign (v, Expression.New (c, sourceValue)));
154+
return v;
155+
}
156+
157+
public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize)
158+
{
159+
var r = Expression.Variable (MarshalType, sourceValue.Name + "_p");
160+
context.LocalVariables.Add (r);
161+
context.CreationStatements.Add (Expression.Assign (r, Expression.Field (sourceValue, "Value")));
162+
return r;
163+
}
164+
165+
public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue)
166+
{
167+
return CreateParameterFromManagedExpression (context, sourceValue, 0);
168+
}
169+
}
51170
}
52171

src/Java.Interop.Export/Tests/Java.Interop/ExportedMemberBuilderTest.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void AddExportMethods ()
2222
var methods = CreateBuilder ()
2323
.GetExportedMemberRegistrations (typeof (ExportTest))
2424
.ToList ();
25-
Assert.AreEqual (5, methods.Count);
25+
Assert.AreEqual (6, methods.Count);
2626

2727
Assert.AreEqual ("action", methods [0].Name);
2828
Assert.AreEqual ("()V", methods [0].Signature);
@@ -279,6 +279,46 @@ public void CreateMarshalFromJniMethodExpression_StaticActionInt32String ()
279279
}");
280280
}
281281

282+
[Test]
283+
public void CreateMarshalFromJniMethodExpression_StaticFuncMyLegacyColorMyColor_MyColor ()
284+
{
285+
var t = typeof (ExportTest);
286+
var m = ((Func<MyLegacyColor, MyColor, MyColor>) ExportTest.StaticFuncMyLegacyColorMyColor_MyColor);
287+
var e = new JavaCallableAttribute () {
288+
Signature = "(II)I",
289+
};
290+
CheckCreateInvocationExpression (e, t, m.Method, typeof (Func<IntPtr, IntPtr, int, int, int>),
291+
@"int (IntPtr __jnienv, IntPtr __class, int color1, int color2)
292+
{
293+
JniTransition __envp;
294+
JniRuntime __jvm;
295+
MyColor __mret;
296+
MyLegacyColor color1_val;
297+
MyColor color2_val;
298+
int __mret_p;
299+
300+
__envp = new JniTransition(__jnienv);
301+
try
302+
{
303+
__jvm = JniEnvironment.Runtime;
304+
color1_val = new MyLegacyColor(color1);
305+
color2_val = new MyColor(color2);
306+
__mret = ExportTest.StaticFuncMyLegacyColorMyColor_MyColor(color1_val, color2_val);
307+
__mret_p = __mret.Value;
308+
return __mret_p;
309+
}
310+
catch (Exception __e)
311+
{
312+
__envp.SetPendingException(__e);
313+
return default(int);
314+
}
315+
finally
316+
{
317+
__envp.Dispose();
318+
}
319+
}");
320+
}
321+
282322
[Test]
283323
public void CreateMarshalFromJniMethodExpression_FuncInt64 ()
284324
{

src/Java.Interop.Export/Tests/java/com/xamarin/interop/export/ExportType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ public class ExportType {
55
public static void testStaticMethods () {
66
staticAction ();
77
staticActionInt32String (1, "2");
8+
9+
int v = staticFuncMyLegacyColorMyColor_MyColor (1, 41);
10+
if (v != 42)
11+
throw new Error ("staticFuncMyEnum_MyEnum should return 42!");
812
}
913

1014
public static native void staticAction ();
1115
public static native void staticActionInt32String (int i, String s);
16+
public static native int staticFuncMyLegacyColorMyColor_MyColor (int color1, int color2);
1217

1318
public void testMethods () {
1419
action ();

src/Java.Interop/Java.Interop.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
<Compile Include="Java.Interop\JniStringValueMarshaler.cs" />
129129
<Compile Include="Java.Interop\JniSystem.cs" />
130130
<Compile Include="Java.Interop\JniValueMarshaler.cs" />
131+
<Compile Include="Java.Interop\JniValueMarshalerAttribute.cs" />
131132
<Compile Include="Java.Interop\JniWeakGlobalReference.cs" />
132133
<Compile Include="Java.Interop\ManagedPeer.cs" />
133134
</ItemGroup>

src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,10 @@ public JniValueMarshaler GetValueMarshaler (Type type)
523523
if (info.ContainsGenericParameters)
524524
throw new ArgumentException ("Generic type definitions are not supported.", "type");
525525

526+
var marshalerAttr = info.GetCustomAttribute<JniValueMarshalerAttribute> ();
527+
if (marshalerAttr != null)
528+
return (JniValueMarshaler) Activator.CreateInstance (marshalerAttr.MarshalerType);
529+
526530
if (typeof (IJavaPeerable) == type)
527531
return JavaPeerableValueMarshaler.Instance;
528532

@@ -680,6 +684,21 @@ public override void DestroyGenericArgumentState (T value, ref JniValueMarshaler
680684
{
681685
ValueMarshaler.DestroyArgumentState (value, ref state, synchronize);
682686
}
687+
688+
public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize)
689+
{
690+
return ValueMarshaler.CreateParameterFromManagedExpression (context, sourceValue, synchronize);
691+
}
692+
693+
public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type targetType)
694+
{
695+
return ValueMarshaler.CreateParameterToManagedExpression (context, sourceValue, synchronize, targetType);
696+
}
697+
698+
public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue)
699+
{
700+
return ValueMarshaler.CreateReturnValueFromManagedExpression (context, sourceValue);
701+
}
683702
}
684703

685704
class ProxyValueMarshaler : JniValueMarshaler<object> {

src/Java.Interop/Java.Interop/JniValueMarshaler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,13 @@ public virtual Expression CreateParameterFromManagedExpression
181181

182182
context.LocalVariables.Add (state);
183183
context.LocalVariables.Add (ret);
184-
context.CreationStatements.Add (Expression.Assign (state, Expression.Call (self, c.GetMethodInfo (), sourceValue, Expression.Constant (synchronize, typeof (ParameterAttributes)))));
184+
context.CreationStatements.Add (Expression.Assign (state, Expression.Call (self, c.GetMethodInfo (), Expression.Convert (sourceValue, typeof (object)), Expression.Constant (synchronize, typeof (ParameterAttributes)))));
185185
context.CreationStatements.Add (
186186
Expression.Assign (ret,
187187
Expression.Property (
188188
Expression.Property (state, "ReferenceValue"),
189189
"Handle")));
190-
context.CleanupStatements.Add (Expression.Call (self, d.GetMethodInfo (), sourceValue, state, Expression.Constant (synchronize)));
190+
context.CleanupStatements.Add (Expression.Call (self, d.GetMethodInfo (), Expression.Convert (sourceValue, typeof (object)), state, Expression.Constant (synchronize)));
191191

192192
return ret;
193193
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace Java.Interop {
5+
6+
[AttributeUsage (Targets, AllowMultiple=false)]
7+
public class JniValueMarshalerAttribute : Attribute {
8+
9+
const AttributeTargets Targets =
10+
AttributeTargets.Class | AttributeTargets.Enum |
11+
AttributeTargets.Interface | AttributeTargets.Struct |
12+
AttributeTargets.Parameter | AttributeTargets.ReturnValue;
13+
14+
public JniValueMarshalerAttribute (Type marshalerType)
15+
{
16+
if (marshalerType == null)
17+
throw new ArgumentNullException (nameof (marshalerType));
18+
if (!typeof (JniValueMarshaler).GetTypeInfo ().IsAssignableFrom (marshalerType.GetTypeInfo ()))
19+
throw new ArgumentException (
20+
string.Format ("`{0}` must inherit from JniValueMarshaler!", marshalerType.FullName),
21+
nameof (marshalerType));
22+
23+
MarshalerType = marshalerType;
24+
}
25+
26+
public Type MarshalerType {get;}
27+
}
28+
}
29+

src/Java.Interop/Tests/Interop-Tests.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniRuntimeTest.cs" />
5151
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniRuntime.JniValueManagerTests.cs" />
5252
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniTypeManagerTests.cs" />
53+
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniValueMarshalerAttributeTests.cs" />
5354
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\JniValueMarshalerContractTests.cs" />
5455
</ItemGroup>
5556
<ItemGroup>

0 commit comments

Comments
 (0)