diff --git a/src/Java.Interop.Export/Java.Interop/ExportedMemberBuilder.cs b/src/Java.Interop.Export/Java.Interop/ExportedMemberBuilder.cs index 3ef966bcd..30c9a58d8 100644 --- a/src/Java.Interop.Export/Java.Interop/ExportedMemberBuilder.cs +++ b/src/Java.Interop.Export/Java.Interop/ExportedMemberBuilder.cs @@ -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); @@ -137,7 +145,7 @@ public virtual LambdaExpression CreateMarshalFromJniMethodExpression (JavaCallab var marshalParameters = new List (methodParameters.Length); var invokeParameters = new List (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); @@ -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"); @@ -200,6 +208,15 @@ public virtual LambdaExpression CreateMarshalFromJniMethodExpression (JavaCallab return Expression.Lambda (marshalerType, body, bodyParams); } + JniValueMarshaler GetValueMarshaler (ParameterInfo parameter) + { + var attr = parameter.GetCustomAttribute (); + 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) @@ -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 ( @@ -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 ( diff --git a/src/Java.Interop.Export/Tests/Java.Interop/ExportTest.cs b/src/Java.Interop.Export/Tests/Java.Interop/ExportTest.cs index 69ca8e949..6e3d80abc 100644 --- a/src/Java.Interop.Export/Tests/Java.Interop/ExportTest.cs +++ b/src/Java.Interop.Export/Tests/Java.Interop/ExportTest.cs @@ -1,6 +1,9 @@ using System; +using System.Linq.Expressions; +using System.Reflection; using Java.Interop; +using Java.Interop.Expressions; namespace Java.InteropTests { @@ -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 () { @@ -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 { + + 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 { + + 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); + } + } } diff --git a/src/Java.Interop.Export/Tests/Java.Interop/ExportedMemberBuilderTest.cs b/src/Java.Interop.Export/Tests/Java.Interop/ExportedMemberBuilderTest.cs index c4f6bb016..ee6c00eb4 100644 --- a/src/Java.Interop.Export/Tests/Java.Interop/ExportedMemberBuilderTest.cs +++ b/src/Java.Interop.Export/Tests/Java.Interop/ExportedMemberBuilderTest.cs @@ -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); @@ -279,6 +279,46 @@ public void CreateMarshalFromJniMethodExpression_StaticActionInt32String () }"); } + [Test] + public void CreateMarshalFromJniMethodExpression_StaticFuncMyLegacyColorMyColor_MyColor () + { + var t = typeof (ExportTest); + var m = ((Func) ExportTest.StaticFuncMyLegacyColorMyColor_MyColor); + var e = new JavaCallableAttribute () { + Signature = "(II)I", + }; + CheckCreateInvocationExpression (e, t, m.Method, typeof (Func), + @"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 () { diff --git a/src/Java.Interop.Export/Tests/java/com/xamarin/interop/export/ExportType.java b/src/Java.Interop.Export/Tests/java/com/xamarin/interop/export/ExportType.java index fd8d4afd7..55313441f 100644 --- a/src/Java.Interop.Export/Tests/java/com/xamarin/interop/export/ExportType.java +++ b/src/Java.Interop.Export/Tests/java/com/xamarin/interop/export/ExportType.java @@ -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 (); diff --git a/src/Java.Interop/Java.Interop.csproj b/src/Java.Interop/Java.Interop.csproj index bd3aa339e..17d34d4c5 100644 --- a/src/Java.Interop/Java.Interop.csproj +++ b/src/Java.Interop/Java.Interop.csproj @@ -128,6 +128,7 @@ + diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index e140eb416..acfa99903 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -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 (); + if (marshalerAttr != null) + return (JniValueMarshaler) Activator.CreateInstance (marshalerAttr.MarshalerType); + if (typeof (IJavaPeerable) == type) return JavaPeerableValueMarshaler.Instance; @@ -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 { diff --git a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs index 72f6d52e4..71d976306 100644 --- a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs +++ b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs @@ -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; } diff --git a/src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs b/src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs new file mode 100644 index 000000000..0c8b267db --- /dev/null +++ b/src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs @@ -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;} + } +} + diff --git a/src/Java.Interop/Tests/Interop-Tests.projitems b/src/Java.Interop/Tests/Interop-Tests.projitems index d7f5dc412..c60366425 100644 --- a/src/Java.Interop/Tests/Interop-Tests.projitems +++ b/src/Java.Interop/Tests/Interop-Tests.projitems @@ -50,6 +50,7 @@ + diff --git a/src/Java.Interop/Tests/Java.Interop/JniValueMarshalerAttributeTests.cs b/src/Java.Interop/Tests/Java.Interop/JniValueMarshalerAttributeTests.cs new file mode 100644 index 000000000..97397fc70 --- /dev/null +++ b/src/Java.Interop/Tests/Java.Interop/JniValueMarshalerAttributeTests.cs @@ -0,0 +1,23 @@ +using System; + +using Java.Interop; + +using NUnit.Framework; + +namespace Java.InteropTests { + + [TestFixture] + public class JniValueMarshalerAttributeTests { + + [Test] + public void Constructor () + { + Assert.Throws (() => new JniValueMarshalerAttribute (null)); + Assert.Throws (() => new JniValueMarshalerAttribute (typeof(int))); + + var a = new JniValueMarshalerAttribute (typeof (DemoValueTypeValueMarshaler)); + Assert.AreEqual (a.MarshalerType, typeof (DemoValueTypeValueMarshaler)); + } + } +} + diff --git a/src/Java.Interop/Tests/Java.Interop/JniValueMarshalerContractTests.cs b/src/Java.Interop/Tests/Java.Interop/JniValueMarshalerContractTests.cs index 52fc35f21..4a4c9c31c 100644 --- a/src/Java.Interop/Tests/Java.Interop/JniValueMarshalerContractTests.cs +++ b/src/Java.Interop/Tests/Java.Interop/JniValueMarshalerContractTests.cs @@ -209,59 +209,189 @@ public void DestroyGenericArgumentState () var s = new JniValueMarshalerState (); marshaler.DestroyGenericArgumentState (default (T), ref s); } + + [Test] + public void CreateReturnValueFromManagedExpression () + { + var runtime = Expression.Variable (typeof (JniRuntime), "__jvm"); + var value = Expression.Variable (typeof (T), "__value"); + var context = new JniValueMarshalerContext (runtime) { + LocalVariables = { + runtime, + value, + }, + }; + var ret = marshaler.CreateReturnValueFromManagedExpression (context, value); + CheckExpression (context, GetExpectedReturnValueFromManagedExpression (runtime.Name, value.Name, ret), ret); + } + + protected virtual string GetExpectedReturnValueFromManagedExpression (string jvm, string value, Expression ret) + { + var valueType = GetTypeName (typeof (T)); + var marshalerType = marshaler.GetType ().Name; + return $@"{{ + JniRuntime {jvm}; + {valueType} {value}; + {marshalerType} {value}_marshaler; + JniValueMarshalerState {value}_state; + IntPtr {value}_val; + IntPtr {value}_rtn; + + try + {{ + {value}_marshaler = new {marshalerType}(); + {value}_state = {value}_marshaler.CreateArgumentState((object){value}, ParameterAttributes.None); + {value}_val = {value}_state.ReferenceValue.Handle; + {value}_rtn = References.NewReturnToJniRef({value}_state.ReferenceValue); + return {value}_rtn; + }} + finally + {{ + {value}_marshaler.DestroyArgumentState((object){value}, {value}_state, ParameterAttributes.None); + }} +}}"; + } + + void CheckExpression (JniValueMarshalerContext context, string expected, Expression ret) + { + var body = Expression.Block (context.CreationStatements.Concat (new[]{ ret })); + var cleanup = context.CleanupStatements.Any () + ? (Expression) Expression.Block (context.CleanupStatements.Reverse ()) + : (Expression) Expression.Empty (); + var expr = Expression.TryFinally (body, cleanup); + var block = Expression.Block (context.LocalVariables, expr); + Console.WriteLine ("# jonp: expected: {0}", GetType ().Name); + Console.WriteLine (block.ToCSharpCode ()); + Assert.AreEqual (expected, block.ToCSharpCode ()); + } + + protected static string GetTypeName (Type type) + { + switch (type.Name) { + case "Boolean": + return "bool"; + case "Char": + return "char"; + case "Double": + return "double"; + case "Int16": + return "short"; + case "Int32": + return "int"; + case "Int64": + return "long"; + case "Object": + return "object"; + case "SByte": + return "sbyte"; + case "Single": + return "float"; + } + if (type.IsArray) { + return GetTypeName (type.GetElementType ()) + "[]"; + } + if (!type.IsGenericType) { + return type.Name; + } + var n = new System.Text.StringBuilder (); + n.Append (type.Name); + var b = type.Name.IndexOf ('`'); + n.Remove (b, type.Name.Length - b); + n.Append ("<"); + n.Append (string.Join (", ", type.GenericTypeArguments.Select (tp => GetTypeName (tp)))); + n.Append (">"); + return n.ToString (); + } } [TestFixture] public class JniValueMarshaler_String_ContractTests : JniValueMarshalerContractTests { protected override string Value {get {return "value";}} + + protected override string GetExpectedReturnValueFromManagedExpression (string jvm, string value, Expression ret) + { + var rname = ((ParameterExpression) ret).Name; + return $@"{{ + JniRuntime {jvm}; + string {value}; + JniObjectReference {value}_ref; + IntPtr {value}_rtn; + + try + {{ + {value}_ref = Strings.NewString({value}); + {value}_rtn = References.NewReturnToJniRef({value}_ref); + return {rname}; + }} + finally + {{ + JniObjectReference.Dispose(__value_ref); + }} +}}"; + } + } + + public abstract class JniValueMarshaler_BuiltinType_ContractTests : JniValueMarshalerContractTests { + protected override bool IsJniValueType {get {return true;}} + + protected override string GetExpectedReturnValueFromManagedExpression (string jvm, string value, Expression ret) + { + var valueType = GetTypeName (typeof (T)); + var rname = ((ParameterExpression) ret).Name; + return $@"{{ + JniRuntime {jvm}; + {valueType} {value}; + + try + {{ + return {rname}; + }} + finally + {{ + default(void); + }} +}}"; + } } [TestFixture] - public class JniValueMarshaler_Boolean_ContractTests : JniValueMarshalerContractTests { + public class JniValueMarshaler_Boolean_ContractTests : JniValueMarshaler_BuiltinType_ContractTests { protected override bool Value {get {return true;}} - protected override bool IsJniValueType {get {return true;}} } [TestFixture] - public class JniValueMarshaler_SByte_ContractTests : JniValueMarshalerContractTests { + public class JniValueMarshaler_SByte_ContractTests : JniValueMarshaler_BuiltinType_ContractTests { protected override sbyte Value {get {return (sbyte) 2;}} - protected override bool IsJniValueType {get {return true;}} } [TestFixture] - public class JniValueMarshaler_Char_ContractTests : JniValueMarshalerContractTests { + public class JniValueMarshaler_Char_ContractTests : JniValueMarshaler_BuiltinType_ContractTests { protected override char Value {get {return '3';}} - protected override bool IsJniValueType {get {return true;}} } [TestFixture] - public class JniValueMarshaler_Int16_ContractTests : JniValueMarshalerContractTests { + public class JniValueMarshaler_Int16_ContractTests : JniValueMarshaler_BuiltinType_ContractTests { protected override short Value {get {return (short) 4;}} - protected override bool IsJniValueType {get {return true;}} } [TestFixture] - public class JniValueMarshaler_Int32_ContractTests : JniValueMarshalerContractTests { + public class JniValueMarshaler_Int32_ContractTests : JniValueMarshaler_BuiltinType_ContractTests { protected override int Value {get {return 5;}} - protected override bool IsJniValueType {get {return true;}} } [TestFixture] - public class JniValueMarshaler_Int64_ContractTests : JniValueMarshalerContractTests { + public class JniValueMarshaler_Int64_ContractTests : JniValueMarshaler_BuiltinType_ContractTests { protected override long Value {get {return 6;}} - protected override bool IsJniValueType {get {return true;}} } [TestFixture] - public class JniValueMarshaler_Single_ContractTests : JniValueMarshalerContractTests { + public class JniValueMarshaler_Single_ContractTests : JniValueMarshaler_BuiltinType_ContractTests { protected override float Value {get {return 7F;}} - protected override bool IsJniValueType {get {return true;}} } [TestFixture] - public class JniValueMarshaler_Double_ContractTests : JniValueMarshalerContractTests { + public class JniValueMarshaler_Double_ContractTests : JniValueMarshaler_BuiltinType_ContractTests { protected override double Value {get {return 8D;}} - protected override bool IsJniValueType {get {return true;}} } [TestFixture] @@ -308,6 +438,7 @@ public abstract class JniInt32ArrayValueMarshalerContractTests : JniValueMars where T : IEnumerable { protected abstract T CreateArray (int[] values); + protected abstract string ValueMarshalerSourceType {get;} protected override T Value { get {return CreateArray (new[]{ 1, 2, 3 });} @@ -337,12 +468,39 @@ protected override void Dispose (T value) d.Dispose (); } } + + protected override string GetExpectedReturnValueFromManagedExpression (string jvm, string value, Expression ret) + { + return $@"{{ + JniRuntime __jvm; + {ValueMarshalerSourceType} __value; + ValueMarshaler __value_marshaler; + JniValueMarshalerState __value_state; + IntPtr __value_val; + IntPtr __value_rtn; + + try + {{ + __value_marshaler = new ValueMarshaler(); + __value_state = __value_marshaler.CreateArgumentState((object)__value, ParameterAttributes.None); + __value_val = __value_state.ReferenceValue.Handle; + __value_rtn = References.NewReturnToJniRef(__value_state.ReferenceValue); + return __value_rtn; + }} + finally + {{ + __value_marshaler.DestroyArgumentState((object)__value, __value_state, ParameterAttributes.None); + }} +}}"; + } } [TestFixture] public class JniValueMarshaler_Int32Array_ContractTests : JniInt32ArrayValueMarshalerContractTests { protected override int[] CreateArray (int[] values) {return values;} + protected override string ValueMarshalerSourceType {get {return "int[]";}} + [Test] public unsafe void CreateGenericObjectReferenceArgumentState_OutParameterDoesNotCopy () { @@ -370,21 +528,29 @@ public unsafe void DestroyGenericArgumentState_InParameterDoesNotUpdatesSource ( [TestFixture] public class JniValueMarshaler_ListOfInt32_ContractTests : JniInt32ArrayValueMarshalerContractTests> { protected override IList CreateArray (int[] values) {return values;} + + protected override string ValueMarshalerSourceType {get {return "IList";}} } [TestFixture] public class JniValueMarshaler_JavaArray_Int32_ContractTests : JniInt32ArrayValueMarshalerContractTests> { protected override JavaArray CreateArray (int[] values) {return new JavaInt32Array (values);} + + protected override string ValueMarshalerSourceType {get {return "JavaArray";}} } [TestFixture] public class JniValueMarshaler_JavaPrimitiveArray_Int32_ContractTests : JniInt32ArrayValueMarshalerContractTests> { protected override JavaPrimitiveArray CreateArray (int[] values) {return new JavaInt32Array (values);} + + protected override string ValueMarshalerSourceType {get {return "JavaPrimitiveArray";}} } [TestFixture] public class JniValueMarshaler_JavaInt32Array_ContractTests : JniInt32ArrayValueMarshalerContractTests { protected override JavaInt32Array CreateArray (int[] values) {return new JavaInt32Array (values);} + + protected override string ValueMarshalerSourceType {get {return "JavaInt32Array";}} } [TestFixture] @@ -418,45 +584,95 @@ public class JniValueMarshaler_IJavaPeerable_ContractTests : JniValueMarshalerCo protected override IJavaPeerable Value {get {return value;}} - [Test] - public void CreateReturnValueFromManagedExpression () + protected override string GetExpectedReturnValueFromManagedExpression (string jvm, string value, Expression ret) { - var runtime = Expression.Variable (typeof (JniRuntime), "__jvm"); - var value = Expression.Variable (typeof (IJavaPeerable), "__value"); - var context = new JniValueMarshalerContext (runtime) { - LocalVariables = { - runtime, - value, - }, - }; - marshaler.CreateReturnValueFromManagedExpression (context, value); - var expected = @"{ - JniRuntime __jvm; - IJavaPeerable __value; - JniObjectReference __value_ref; - IntPtr __value_handle; - IntPtr __value_rtn; - - if (null == __value) - { - return __value_ref = new JniObjectReference(); + var pret = (ParameterExpression) ret; + return $@"{{ + JniRuntime {jvm}; + IJavaPeerable {value}; + JniObjectReference {value}_ref; + IntPtr {value}_handle; + IntPtr {value}_rtn; + + try + {{ + if (null == {value}) + {{ + return {value}_ref = new JniObjectReference(); + }} + else + {{ + return {value}_ref = {value}.PeerReference; + }} + {value}_handle = {value}_ref.Handle; + {value}_rtn = References.NewReturnToJniRef({value}_ref); + return {pret.Name}; + }} + finally + {{ + JniObjectReference.Dispose({value}_ref); + }} +}}"; + } } - else - { - return __value_ref = __value.PeerReference; + + [JniValueMarshaler (typeof (DemoValueTypeValueMarshaler))] + struct DemoValueType { + public int Value {get;} + + public DemoValueType (int value) + { + Value = value; + } } - __value_handle = __value_ref.Handle; - __value_rtn = References.NewReturnToJniRef(__value_ref); - JniObjectReference.Dispose(__value_ref); -}"; - CheckExpression (context, expected); + + class DemoValueTypeValueMarshaler : JniValueMarshaler { + + JniValueMarshaler Int32Marshaler; + + public override bool IsJniValueType { + get { + return Int32Marshaler.IsJniValueType; + } } - static void CheckExpression (JniValueMarshalerContext context, string expected) + public DemoValueTypeValueMarshaler () { - var block = Expression.Block (context.LocalVariables, context.CreationStatements.Concat (context.CleanupStatements)); - Assert.AreEqual (expected, block.ToCSharpCode ()); + Int32Marshaler = JniRuntime.CurrentRuntime.ValueManager.GetValueMarshaler (); + } + + public override DemoValueType CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType) + { + var v = Int32Marshaler.CreateGenericValue (ref reference, options, targetType); + return new DemoValueType (v); + } + + public override JniValueMarshalerState CreateGenericArgumentState (DemoValueType value, ParameterAttributes synchronize) + { + return Int32Marshaler.CreateGenericArgumentState (value.Value, synchronize); + } + + public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (DemoValueType value, ParameterAttributes synchronize) + { + return Int32Marshaler.CreateGenericObjectReferenceArgumentState (value.Value, synchronize); + } + + public override void DestroyArgumentState (object value, ref JniValueMarshalerState state, ParameterAttributes synchronize) + { + Int32Marshaler.DestroyArgumentState ((value as DemoValueType?)?.Value, ref state, synchronize); + } + + public override void DestroyGenericArgumentState (DemoValueType value, ref JniValueMarshalerState state, ParameterAttributes synchronize) + { + Int32Marshaler.DestroyGenericArgumentState (value.Value, ref state, synchronize); } } + + [TestFixture] + class JniValueMarshaler_DemoValueType_ContractTests : JniValueMarshalerContractTests { + + protected override DemoValueType Value {get {return new DemoValueType (42);}} + protected override bool IsJniValueType {get {return true;}} + } }