Skip to content

Commit 9057654

Browse files
committed
[Java.Interop.Dynamic] Support accessing static int fields.
(I now understand the DLR enough to be slightly dangerous.) Implement enough DLR voodoo so that static int fields can be accessed and read; from a unit test: dynamic Integer = new DynamicJavaClass ("java/lang/Integer"); int max = Integer.MAX_VALUE; Yes, that works, and yes, it is awesome. It reads the Integer.MAX_VALUE field. Doubly awesome -- and a huge part of the complication! -- is that it uses DynamicMetaObject.BindConvert() to detect that the field type is `int`, as field type information is required in order to lookup the field, and thus we need to delay the actual JNI field lookup and access until *after* we know the desired type. Figuring out how to actually support this took a fair bit of banging my head against several walls... ...and since the DLR documentation is horribly inadequate, requiring that I go spelunking through their source... A Short DLR Primer: The DLR is ~wholly based upon Systme.Linq.Expressions. A type may participate in the DLR by implementing the IDynamicMetaObjectProvider interface and subclassing the DynamicMetaObject class. DynamicMetaObject does the "heavy lifting" of Expression manipulation in order to perform the desired actions. The problem, sanity wise, is understanding BindingRestrictions use, which is likewise underdocumented: return new DynamicMetaObject( Expression, BindingRestrictions.GetTypeRestriction( Expression, typeof (DynamicJavaClass))); AFAICT, BindingRestrictions is used by the DLR to assist in "invalidating" the cached callsite information (as LOTS of information is cached at the callsite in order to assist performance). The above states that `Expression` will be of type `DynamicJavaClass`, via BindingRestrictions.Get*Type*Restriction(), and thus: 1. If the cached type is NOT DynamicJavaClass, then it's the cached value, and nothing more need be done. 2. If the cached type IS DynamicJavaClass, then DynamicJavaClass.GetMetaObject() should be invoked for future DLR operations. The problem regarding the above "don't do anything until we get a BindConvert() request!" logic is that BindConvert() is *last*. Furthermore, DynamicMetaObject instances are recreated ALL THE TIME, so accessing a static field: var value = Integer.MAX_VALUE; Results in (paraphrasing greatly!): var metaObject = Integer.GetMetaObject(); var valueObject = metaObject.BindGetMember(... "MAX_VALUE" ... ); var value = valueObject.Compile().Invoke (...); At this point `value` is compared against the BindingRestrictions; if BindingRestrictions is set improperly, then `value` is used AS-IS, there is no later BindConvert() call, and there's no way to determine the intended field type. (Exceptions soon follow.) So to make this work the MetaStaticMemberAccessObject.BindGetMember() method needs to restrict the value to a StaticFieldAccess, which is created via System.Linq.Expressions, so that the StaticFieldAccess can participate in the DLR binding logic. StaticFieldAccess holds the accessed field and, in turn, implements the IDynamicMetaObjectProvider to return a MetaStaticFieldAccessObject, which overrides BindConvert() and delegates the call to DynamicJavaClass.GetStaticFieldValue(), which allows providing both the accessed field AND the desired return type, allowing this miracle to actually work. Huzzah!
1 parent 1c90502 commit 9057654

File tree

3 files changed

+141
-7
lines changed

3 files changed

+141
-7
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,14 @@
3737
<Compile Include="Java.Interop.Dynamic\DynamicJavaClass.cs" />
3838
</ItemGroup>
3939
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
40+
<ItemGroup>
41+
<ProjectReference Include="..\..\lib\mono.linq.expressions\Mono.Linq.Expressions.csproj">
42+
<Project>{0C001D50-4176-45AE-BDC8-BA626508B0CC}</Project>
43+
<Name>Mono.Linq.Expressions</Name>
44+
</ProjectReference>
45+
<ProjectReference Include="..\Java.Interop\Java.Interop.csproj">
46+
<Project>{94BD81F7-B06F-4295-9636-F8A3B6BDC762}</Project>
47+
<Name>Java.Interop</Name>
48+
</ProjectReference>
49+
</ItemGroup>
4050
</Project>

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

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,89 @@
44
using System.Dynamic;
55
using System.Linq;
66
using System.Linq.Expressions;
7+
using System.Reflection;
8+
using System.Reflection.Emit;
79
using System.Text;
810

11+
using Mono.Linq.Expressions;
12+
913
using Java.Interop;
1014

1115
namespace Java.Interop.Dynamic {
1216

1317
public class DynamicJavaClass : IDynamicMetaObjectProvider
1418
{
15-
public DynamicMetaObject GetMetaObject(Expression parameter)
19+
public string JniClassName {get; private set;}
20+
21+
JniPeerMembers members;
22+
23+
public DynamicJavaClass (string jniClassName)
24+
{
25+
if (jniClassName == null)
26+
throw new ArgumentNullException ("jniClassName");
27+
28+
JniClassName = jniClassName;
29+
members = new JniPeerMembers (jniClassName, CreateManagedPeerType ());
30+
}
31+
32+
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject (Expression parameter)
33+
{
34+
return new MetaStaticMemberAccessObject (parameter, this);
35+
}
36+
37+
public object GetStaticFieldValue (string fieldName, Type fieldType)
38+
{
39+
Console.WriteLine ("# DynamicJavaClass({0}).field({1}) as {2}", JniClassName, fieldName, fieldType);
40+
var typeInfo = JniEnvironment.Current.JavaVM.GetJniTypeInfoForJniTypeReference (JniClassName);
41+
switch (typeInfo.ToString ()) {
42+
case "I":
43+
case "java/lang/Integer": // WTF?
44+
return members.StaticFields.GetInt32Value (fieldName + "\u0000I");
45+
}
46+
return null;
47+
}
48+
49+
internal StaticFieldAccess GetStaticFieldAccess (string fieldName)
50+
{
51+
return new StaticFieldAccess (this, fieldName);
52+
}
53+
54+
Type CreateManagedPeerType ()
1655
{
17-
return new MyMetaObject(parameter, this);
56+
var className = JniClassName.Replace ('/', '-');
57+
var aname = new AssemblyName ("Java.Interop.Dynamic-" + className);
58+
59+
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly (aname, AssemblyBuilderAccess.ReflectionOnly);
60+
var module = assembly.DefineDynamicModule (className);
61+
var type = module.DefineType (JniClassName, TypeAttributes.Sealed, typeof (JavaObject));
62+
type.SetCustomAttribute (
63+
new CustomAttributeBuilder (
64+
typeof (JniTypeInfoAttribute).GetConstructor (new[]{ typeof(string) }),
65+
new []{JniClassName}));
66+
67+
return type.CreateType ();
1868
}
1969
}
2070

21-
public class MyMetaObject : DynamicMetaObject
71+
class MetaStaticMemberAccessObject : DynamicMetaObject
2272
{
23-
public MyMetaObject(Expression parameter, object value)
73+
public MetaStaticMemberAccessObject (Expression parameter, object value)
2474
: base(parameter, BindingRestrictions.Empty, value)
2575
{
76+
Console.WriteLine ("# MyMetaObject..ctor: paramter={0} {1} {2}", parameter.ToCSharpCode (), parameter.GetType (), parameter.Type);
77+
Console.WriteLine ("# MyMetaObject..ctor: value={0} {1}", value, value.GetType ());
2678
}
2779

2880
public override DynamicMetaObject BindConvert(ConvertBinder binder)
2981
{
82+
Console.WriteLine ("Convert: Expression={0} [{1}]", Expression.ToCSharpCode (), Expression.Type);
3083
return this.PrintAndReturnIdentity("Convert: Explicit={0}; ReturnType={1}; Type={2}", binder.Explicit, binder.ReturnType, binder.Type);
3184
}
3285

3386
public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
3487
{
35-
return this.PrintAndReturnIdentity("InvokeMember of method {0}", binder.Name);
88+
return this.PrintAndReturnIdentity("InvokeMember of method={0}; ReturnType={1}; args={{{2}}}; CallInfo={3}", binder.Name, binder.ReturnType,
89+
string.Join (", ", args.Select (a => string.Format ("{0} [{1}]", a.Expression.ToCSharpCode (), a.LimitType))), binder.CallInfo);
3690
}
3791

3892
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
@@ -42,7 +96,17 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM
4296

4397
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
4498
{
45-
return this.PrintAndReturnIdentity("GetMember of property {0}", binder.Name);
99+
Console.WriteLine ("GetMember: Expression={0} [{1}]; property={2}", Expression.ToCSharpCode (), Expression.Type, binder.Name);
100+
var expr =
101+
Expression.Call (
102+
Expression.Convert (Expression, typeof(DynamicJavaClass)),
103+
typeof (DynamicJavaClass).GetMethod ("GetStaticFieldAccess", BindingFlags.Instance | BindingFlags.NonPublic),
104+
Expression.Constant (binder.Name));
105+
return new DynamicMetaObject (
106+
expr,
107+
BindingRestrictions.GetTypeRestriction (
108+
expr,
109+
typeof (StaticFieldAccess)));
46110
}
47111

48112
private DynamicMetaObject PrintAndReturnIdentity(string message, params object[] args)
@@ -55,6 +119,51 @@ private DynamicMetaObject PrintAndReturnIdentity(string message, params object[]
55119
typeof (DynamicJavaClass)));
56120
}
57121
}
122+
class StaticFieldAccess : IDynamicMetaObjectProvider {
123+
124+
public string FieldName {get; private set;}
125+
public DynamicJavaClass JavaClass {get; private set;}
126+
127+
public StaticFieldAccess (DynamicJavaClass klass, string fieldName)
128+
{
129+
JavaClass = klass;
130+
FieldName = fieldName;
131+
}
132+
133+
public DynamicMetaObject GetMetaObject(Expression parameter)
134+
{
135+
Console.WriteLine ("# FieldAccessInfo.GetMetaObject: parameter={0}", parameter);
136+
return new MetaStaticFieldAccessObject(parameter, this);
137+
}
138+
}
139+
140+
class MetaStaticFieldAccessObject : DynamicMetaObject {
141+
142+
public MetaStaticFieldAccessObject (Expression e, object value)
143+
: base (e, BindingRestrictions.Empty, value)
144+
{
145+
Console.WriteLine ("MyHelperObject: e={0}", e.ToCSharpCode ());
146+
}
147+
148+
public override DynamicMetaObject BindConvert (ConvertBinder binder)
149+
{
150+
Console.WriteLine ("MetaStaticFieldAccessObject.Convert: Expression={0} [{1}]", Expression.ToCSharpCode (), Expression.Type);
151+
152+
var field = Expression.Convert(Expression, typeof(StaticFieldAccess));
153+
var instance = Expression.Property (field, "JavaClass");
154+
var fieldName = Expression.Property (field, "FieldName");
155+
var gsfv = typeof(DynamicJavaClass).GetMethod ("GetStaticFieldValue");
156+
var expr = Expression.Convert (
157+
Expression.Call (instance, gsfv, fieldName, Expression.Constant (binder.Type)),
158+
binder.Type);
159+
return new DynamicMetaObject (
160+
expr,
161+
BindingRestrictions.GetTypeRestriction (
162+
expr,
163+
binder.Type));
164+
}
165+
}
166+
58167
#if false
59168
public class DynamicJavaClass : DynamicObject {
60169

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,17 @@ namespace Java.Interop.DynamicTests {
1616
[TestFixture]
1717
class DynamicJavaClassTests : JVM
1818
{
19+
[Test]
20+
public void Constructor ()
21+
{
22+
Assert.Throws<ArgumentNullException> (() => new DynamicJavaClass (null));
23+
}
24+
1925
[Test]
2026
public void Frobnicate ()
2127
{
22-
dynamic d = new DynamicJavaClass ();
28+
Console.WriteLine ("---");
29+
dynamic d = new DynamicJavaClass ("java/lang/Object");
2330

2431
d.P3 = d.M1(d.P1, d.M2(d.P2));
2532
Console.WriteLine ("--");
@@ -35,6 +42,14 @@ public void Frobnicate ()
3542
Console.WriteLine (e);
3643
}
3744
}
45+
46+
[Test]
47+
public void AccessStaticMember ()
48+
{
49+
dynamic Integer = new DynamicJavaClass ("java/lang/Integer");
50+
int max = Integer.MAX_VALUE;
51+
Assert.AreEqual (int.MaxValue, max);
52+
}
3853
}
3954
}
4055

0 commit comments

Comments
 (0)