Skip to content

Commit f62858a

Browse files
committed
[Java.Interop.Dynamic] Support field lookup fallback.
System.Dynamic.DynamicMetaObject has a concept of "fallback" metaobjects: if the DynamicMetaObject can't perform a particular operation, the "host site" can be called upon to perform the operation instead. See §5.4.3.1 in: https://www.codeplex.com/Download?ProjectName=dlr&DownloadId=127512 For example, consider DynamicJavaClass.JniClassName: this is a public member (which perhaps shouldn't be?) which contains the JNI class name provided to the constructor: partial class DynamicJavaClass { public DynamicJavaClass (string jniClassName); public string JniClassName {get;} } It is thus "obvious" to want to access this member: var d = new DynamicJavaClass ("foo/Bar"); // d.JniClassName == "foo/Bar" Accessing such "real" members requires using the fallback mechanism; without it, attempting to access DynamicJavaClass.JniClassName results in a runtime exception from JniPeerStaticFields.GetValue()[0]. dynamic d = new DynamicJavaClass ("foo/Bar"); string s = d.JniClassName; // BOOM [0] The cause of the crash is a bug (lol): JniTypeInfo.ToString() was used, not JniTypeInfo.JniTypeReference. The latter always uses `L...;` for reference types; the former does not, and the latter is required for JNIEnv::GetStaticFieldID(). Since `java/lang/String` isn't in the correct format, it threw. Fixing this results in a different, expected, exception [1]. Because the Java type doesn't contain a JniClassName field, JNIEnv::GetStaticFieldID() raises an exception, stopping all progress. The fix is to take a page out of DynamicObject: https://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject(v=vs.110).aspx https://github.com/Microsoft/referencesource/blob/9da503f9ef21e8d1f2905c78d4e3e5cbb3d6f85a/System.Core/Microsoft/Scripting/Actions/DynamicObject.cs Instead of immediately performing the operation, rename DynamicJavaClass.GetStaticFieldValue() to DynamicJavaClass.TryGetStaticMemberValue(), which (1) returns `bool`, and (2) uses an `out` parameter to store the value. DynamicJavaClass.TryGetStaticMemberValue() internally catches the JavaException, and returns whether or not the lookup was successful. Consider: dynamic d = new DynamicJavaClass ("java/lang/Object"); string s = d.JniClassName; Instead of converting this into: string s = (string) d.GetStaticFieldValue ("JniClassName", typeof (string)); we instead generate: object __v; string s = d.TryGetStaticMemberValue ("JniClassName", typeof (string), out __v) ? (string) __v : (string) [[ Insert GetMemberBinder.FallbackGetMember() behavior here ]] which amounts to: object __v; string s = d.TryGetStaticMemberValue ("JniClassName", typeof (string), out __v) ? (string) __v : (string) d.JniClassName; This works as expected, and allows JNI lookup to be performed first, and when it fails to fallback on e.g. C# reflection. There is one flaw, however: we still require a conversion step. "Explicit" member access works: dynamic d = new DynamicJavaClass ("java/lang/Object"); string s = d.JniClassName; // Now works "Implicit" member access fails: dynamic d = new DynamicJavaClass ("java/lang/Object"); Assert.AreEqual ("java/lang/Object", d.JniClassName); // BOOM [2] The cause for this is that "GetMember" returns an intermediate DeferredConvert<T> instance instead of the member itself, because we need to know the final type to perform JNI lookup. When there is no "convert to the actual type" step, as above, we're left with the intermediate, and things blow up. I can't think of a good fix to this, though a "workaround" is to provide the conversion, e.g. through a cast: dynamic d = new DynamicJavaClass ("java/lang/Object"); Assert.AreEqual ("java/lang/Object", (string) d.JniClassName); // Works. Aside: We change the default BindingRestrictions for DynamicMetaObject<T> to (hopefully?) better support call-site caching. I'm still not sure how to even TEST this, much less "properly" fix it (is it broken?), but I *think* this would be "more correct." [0]: System.NotSupportedException : Unsupported argument type: java/lang/String at Java.Interop.JniPeerStaticFields.GetValue (string) [0x0012d] in .../src/Java.Interop/Java.Interop/JniPeerStaticFields.cs:49 at Java.Interop.Dynamic.DynamicJavaClass.GetStaticFieldValue (string,System.Type) [0x00058] in ...k/src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs:88 at (wrapper dynamic-method) object.CallSite.Target (System.Runtime.CompilerServices.Closure,System.Runtime.CompilerServices.CallSite,object) at System.Dynamic.UpdateDelegates.UpdateAndExecute1<object, string> (System.Runtime.CompilerServices.CallSite,object) at Java.Interop.DynamicTests.DynamicJavaClassTests.JniClassName () [0x0000c] in .../src/Java.Interop.Dynamic/Tests/Java.Interop.Dynamic/DynamicJavaClassTests.cs:29 at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) at System.Reflection.MonoMethod.Invoke (object,System.Reflection.BindingFlags,System.Reflection.Binder,object[],System.Globalization.CultureInfo) [0x00054] in /private/tmp/source-mono-mac-4.0.0-branch-c5sr4/bockbuild-mono-4.0.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.0.4/mcs/class/corlib/System.Reflection/MonoMethod.cs:230 [1]: Java.Interop.JavaException : JniClassName at Java.Interop.JniEnvironment/Members.GetStaticFieldID (Java.Interop.JniReferenceSafeHandle,string,string) [0x00086] in .../src/Java.Interop/Java.Interop/JniEnvironment.g.cs:2612 at Java.Interop.JniType.GetStaticField (string,string) [0x0000f] in .../src/Java.Interop/Java.Interop/JniType.cs:212 at Java.Interop.JniPeerStaticFields.GetFieldID (string) [0x0003f] in .../src/Java.Interop/Java.Interop/JniPeerStaticFields.cs:23 at Java.Interop.JniPeerStaticFields.GetObjectValue (string) [0x00003] in .../src/Java.Interop/Java.Interop/JniPeerFields.cs:268 at Java.Interop.JniPeerStaticFields.GetValue (string) [0x000f6] in .../src/Java.Interop/Java.Interop/JniPeerStaticFields.cs:44 at Java.Interop.Dynamic.DynamicJavaClass.GetStaticFieldValue (string,System.Type) [0x00052] in .../src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs:88 at (wrapper dynamic-method) object.CallSite.Target (System.Runtime.CompilerServices.Closure,System.Runtime.CompilerServices.CallSite,object) at System.Dynamic.UpdateDelegates.UpdateAndExecute1<object, string> (System.Runtime.CompilerServices.CallSite,object) at Java.Interop.DynamicTests.DynamicJavaClassTests.JniClassName () [0x0000c] in .../src/Java.Interop.Dynamic/Tests/Java.Interop.Dynamic/DynamicJavaClassTests.cs:29 at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) at System.Reflection.MonoMethod.Invoke (object,System.Reflection.BindingFlags,System.Reflection.Binder,object[],System.Globalization.CultureInfo) [0x00054] in /private/tmp/source-mono-mac-4.0.0-branch-c5sr4/bockbuild-mono-4.0.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.0.4/mcs/class/corlib/System.Reflection/MonoMethod.cs:230 --- End of managed Java.Interop.JavaException stack trace --- java.lang.NoSuchFieldError: JniClassName [2]: Test Failure : Java.Interop.DynamicTests.DynamicJavaClassTests.JniClassName Expected: "java/util/Arrays" But was: <Java.Interop.Dynamic.DeferredConvert`1[Java.Interop.Dynamic.DynamicJavaClass]>
1 parent 3b1a91a commit f62858a

File tree

3 files changed

+70
-49
lines changed

3 files changed

+70
-49
lines changed

src/Java.Interop.Dynamic/Java.Interop.Dynamic.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,9 @@
4141
<Project>{94BD81F7-B06F-4295-9636-F8A3B6BDC762}</Project>
4242
<Name>Java.Interop</Name>
4343
</ProjectReference>
44+
<ProjectReference Include="..\..\lib\mono.linq.expressions\Mono.Linq.Expressions.csproj">
45+
<Project>{0C001D50-4176-45AE-BDC8-BA626508B0CC}</Project>
46+
<Name>Mono.Linq.Expressions</Name>
47+
</ProjectReference>
4448
</ItemGroup>
4549
</Project>

src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs

Lines changed: 52 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
using Java.Interop;
1111

12+
using Mono.Linq.Expressions;
13+
1214
namespace Java.Interop.Dynamic {
1315

1416
public class DynamicJavaClass : IDynamicMetaObjectProvider
@@ -75,32 +77,35 @@ static string GetEncodedJniSignature (InvokeMemberBinder binder, DynamicMetaObje
7577
sb.Append (typeInfo.ToString ());
7678
}
7779
sb.Append (")");
78-
sb.Append (JniEnvironment.Current.JavaVM.GetJniTypeInfoForType (returnType).ToString ());
80+
sb.Append (JniEnvironment.Current.JavaVM.GetJniTypeInfoForType (returnType).JniTypeReference);
7981

8082
return sb.ToString ();
8183
}
8284

83-
public object GetStaticFieldValue (string fieldName, Type fieldType)
85+
internal bool TryGetStaticMemberValue (string fieldName, Type fieldType, out object value)
8486
{
8587
Debug.WriteLine ("# DynamicJavaClass({0}).field({1}) as {2}", JniClassName, fieldName, fieldType);
8688
var typeInfo = JniEnvironment.Current.JavaVM.GetJniTypeInfoForType (fieldType);
87-
var encoded = fieldName + "\u0000" + typeInfo.ToString ();
88-
return members.StaticFields.GetValue (encoded);
89+
var encoded = fieldName + "\u0000" + typeInfo.JniTypeReference;
90+
try {
91+
value = members.StaticFields.GetValue (encoded);
92+
return true;
93+
}
94+
catch (JavaException e) {
95+
value = null;
96+
e.Dispose ();
97+
return false;
98+
}
8999
}
90100

91101
public void SetStaticFieldValue (string fieldName, Type fieldType, object value)
92102
{
93103
Debug.WriteLine ("# DynamicJavaClass({0}).field({1}) as {2} = {3}", JniClassName, fieldName, fieldType, value);
94104
var typeInfo = JniEnvironment.Current.JavaVM.GetJniTypeInfoForType (fieldType);
95-
var encoded = fieldName + "\u0000" + typeInfo.ToString ();
105+
var encoded = fieldName + "\u0000" + typeInfo.JniTypeReference;
96106
members.StaticFields.SetValue (encoded, value);
97107
}
98108

99-
internal StaticFieldAccess GetStaticFieldAccess (string fieldName)
100-
{
101-
return new StaticFieldAccess (this, fieldName);
102-
}
103-
104109
#if false
105110
Type CreateManagedPeerType ()
106111
{
@@ -131,13 +136,15 @@ public Expression ExpressionAsT {
131136
}
132137

133138
public DynamicMetaObject (Expression parameter, T value)
134-
: base (parameter, BindingRestrictions.Empty, value)
139+
: base (parameter, BindingRestrictions.GetInstanceRestriction (parameter, value), value)
135140
{
136141
}
137142
}
138143

139144
class MetaStaticMemberAccessObject : DynamicMetaObject<DynamicJavaClass>
140145
{
146+
delegate bool TryGetStaticMemberValue (string fieldName, Type fieldType, out object value);
147+
141148
public MetaStaticMemberAccessObject (Expression parameter, DynamicJavaClass value)
142149
: base (parameter, value)
143150
{
@@ -196,16 +203,15 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM
196203
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
197204
{
198205
// Console.WriteLine ("GetMember: Expression={0} [{1}]; property={2}", Expression.ToCSharpCode (), Expression.Type, binder.Name);
199-
var expr =
200-
Expression.Call (
201-
ExpressionAsT,
202-
typeof (DynamicJavaClass).GetMethod ("GetStaticFieldAccess", BindingFlags.Instance | BindingFlags.NonPublic),
203-
Expression.Constant (binder.Name));
204-
return new DynamicMetaObject (
205-
expr,
206-
BindingRestrictions.GetTypeRestriction (
207-
expr,
208-
typeof (StaticFieldAccess)));
206+
TryGetStaticMemberValue m = Value.TryGetStaticMemberValue;
207+
var access = new DeferredConvert<DynamicJavaClass> {
208+
Arguments = new[]{Expression.Constant (binder.Name)},
209+
Instance = Value,
210+
FallbackCreator = binder.FallbackGetMember,
211+
Method = m.Method,
212+
};
213+
var accessE = Expression.Constant (access);
214+
return new DynamicMetaObject (accessE, BindingRestrictions.GetInstanceRestriction (accessE, access));
209215
}
210216
}
211217

@@ -257,45 +263,45 @@ public override DynamicMetaObject BindConvert (ConvertBinder binder)
257263
}
258264
}
259265

260-
class StaticFieldAccess : IDynamicMetaObjectProvider {
266+
class DeferredConvert<T> : IDynamicMetaObjectProvider {
261267

262-
public string FieldName {get; private set;}
263-
public DynamicJavaClass JavaClass {get; private set;}
264-
265-
public StaticFieldAccess (DynamicJavaClass klass, string fieldName)
266-
{
267-
JavaClass = klass;
268-
FieldName = fieldName;
269-
}
268+
public T Instance;
269+
public MethodInfo Method;
270+
public Expression[] Arguments;
271+
public Func<DynamicMetaObject, DynamicMetaObject> FallbackCreator;
270272

271-
public DynamicMetaObject GetMetaObject(Expression parameter)
273+
public DynamicMetaObject GetMetaObject (Expression parameter)
272274
{
273-
// Console.WriteLine ("# FieldAccessInfo.GetMetaObject: parameter={0}", parameter);
274-
return new MetaStaticFieldAccessObject(parameter, this);
275+
return new DeferredConvertMetaObject<T> (parameter, this);
275276
}
276277
}
277278

278-
class MetaStaticFieldAccessObject : DynamicMetaObject<StaticFieldAccess> {
279+
class DeferredConvertMetaObject<T> : DynamicMetaObject<DeferredConvert<T>> {
279280

280-
public MetaStaticFieldAccessObject (Expression e, StaticFieldAccess value)
281+
public DeferredConvertMetaObject (Expression e, DeferredConvert<T> value)
281282
: base (e, value)
282283
{
283-
// Console.WriteLine ("MyHelperObject: e={0}", e.ToCSharpCode ());
284+
Debug.WriteLine ("DeferredConvertMetaObject<{0}>: e={1}", typeof (T).Name, e.ToCSharpCode ());
284285
}
285286

286287
public override DynamicMetaObject BindConvert (ConvertBinder binder)
287288
{
288-
// Console.WriteLine ("MetaStaticFieldAccessObject.Convert: Expression={0} [{1}]", Expression.ToCSharpCode (), Expression.Type);
289-
290-
var field = ExpressionAsT;
291-
var instance = Expression.Property (field, "JavaClass");
292-
var fieldName = Expression.Property (field, "FieldName");
293-
var gsfv = typeof(DynamicJavaClass).GetMethod ("GetStaticFieldValue");
294-
var expr = Expression.Convert (
295-
Expression.Call (instance, gsfv, fieldName, Expression.Constant (binder.Type)),
296-
binder.Type);
289+
Debug.WriteLine ("DeferredConvertMetaObject<{0}>.BindConvert: Expression='{1}'; Expression.Type={2}", typeof (T).Name, Expression.ToCSharpCode (), Expression.Type);
290+
291+
var instance = Expression.Constant (Value.Instance);
292+
var instanceMO = new DynamicMetaObject (instance, BindingRestrictions.GetInstanceRestriction (instance, typeof (T)));
293+
var value = Expression.Parameter (typeof (object), "value");
294+
Debug.WriteLine ("DeferredConvertMetaObject<{0}>.BindConvert: Fallback={1}", typeof (T).Name, Value.FallbackCreator (instanceMO).Expression.ToCSharpCode ());
295+
var call = Expression.Block (
296+
new[]{value},
297+
Expression.Condition (
298+
test: Expression.Call (instance, Value.Method, Value.Arguments.Concat (new Expression[]{Expression.Constant (binder.Type), value})),
299+
ifTrue: Expression.Convert (value, binder.Type),
300+
ifFalse: Expression.Convert (Value.FallbackCreator (instanceMO).Expression, binder.Type))
301+
);
302+
Debug.WriteLine ("MetaStaticFieldAccessObject.Convert: call={0}", call.ToCSharpCode ());
297303
return new DynamicMetaObject (
298-
expr,
304+
call,
299305
BindingRestrictions.GetInstanceRestriction (Expression, Value));
300306
}
301307
}

src/Java.Interop.Dynamic/Tests/Java.Interop.Dynamic/DynamicJavaClassTests.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,27 @@ namespace Java.Interop.DynamicTests {
1616
[TestFixture]
1717
class DynamicJavaClassTests : Java.InteropTests.JavaVMFixture
1818
{
19+
const string Arrays_class = "java/util/Arrays";
20+
const string Integer_class = "java/lang/Integer";
21+
1922
[Test]
2023
public void Constructor ()
2124
{
2225
Assert.Throws<ArgumentNullException> (() => new DynamicJavaClass (null));
2326
}
2427

28+
[Test]
29+
public void JniClassName ()
30+
{
31+
dynamic Arrays = new DynamicJavaClass (Arrays_class);
32+
string name = Arrays.JniClassName;
33+
Assert.AreEqual (Arrays_class, name);
34+
}
35+
2536
[Test]
2637
public void CallStaticMethod ()
2738
{
28-
dynamic Arrays = new DynamicJavaClass ("java/util/Arrays");
39+
dynamic Arrays = new DynamicJavaClass (Arrays_class);
2940
var array = new int[]{ 1, 2, 3, 4 };
3041
int value = 3;
3142
int index = Arrays.binarySearch (array, value);
@@ -35,15 +46,15 @@ public void CallStaticMethod ()
3546
[Test]
3647
public void ReadStaticMember ()
3748
{
38-
dynamic Integer = new DynamicJavaClass ("java/lang/Integer");
49+
dynamic Integer = new DynamicJavaClass (Integer_class);
3950
int max = Integer.MAX_VALUE;
4051
Assert.AreEqual (int.MaxValue, max);
4152
}
4253

4354
[Test]
4455
public void WriteStaticMember ()
4556
{
46-
dynamic Integer = new DynamicJavaClass ("java/lang/Integer");
57+
dynamic Integer = new DynamicJavaClass (Integer_class);
4758
int cur = Integer.MAX_VALUE;
4859
Console.WriteLine ("# MAX_VALUE={0}", cur);
4960
Integer.MAX_VALUE = 42;

0 commit comments

Comments
 (0)