2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Dynamic / IDispatchMetaObject.cs
1 /* ****************************************************************************
2  *
3  * Copyright (c) Microsoft Corporation. 
4  *
5  * This source code is subject to terms and conditions of the Microsoft Public License. A 
6  * copy of the license can be found in the License.html file at the root of this distribution. If 
7  * you cannot locate the  Microsoft Public License, please send an email to 
8  * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
9  * by the terms of the Microsoft Public License.
10  *
11  * You must not remove this notice, or any other, from this software.
12  *
13  *
14  * ***************************************************************************/
15 using System; using Microsoft;
16
17
18 #if !SILVERLIGHT // ComObject
19
20 using System.Diagnostics;
21 #if CODEPLEX_40
22 using System.Linq.Expressions;
23 #else
24 using Microsoft.Linq.Expressions;
25 #endif
26
27 #if CODEPLEX_40
28 namespace System.Dynamic {
29 #else
30 namespace Microsoft.Scripting {
31 #endif
32
33     internal sealed class IDispatchMetaObject : ComFallbackMetaObject {
34         private readonly IDispatchComObject _self;
35
36         internal IDispatchMetaObject(Expression expression, IDispatchComObject self)
37             : base(expression, BindingRestrictions.Empty, self) {
38             _self = self;
39         }
40
41         public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {
42             ContractUtils.RequiresNotNull(binder, "binder");
43
44             ComMethodDesc method;
45             if (_self.TryGetMemberMethod(binder.Name, out method) ||
46                 _self.TryGetMemberMethodExplicit(binder.Name, out method)) {
47
48                 bool[] isByRef = ComBinderHelpers.ProcessArgumentsForCom(ref args);
49                 return BindComInvoke(args, method, binder.CallInfo, isByRef);
50             }
51
52             return base.BindInvokeMember(binder, args);
53         }
54
55         public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) {
56             ContractUtils.RequiresNotNull(binder, "binder");
57
58             ComMethodDesc method;
59             if (_self.TryGetGetItem(out method)) {
60
61                 bool[] isByRef = ComBinderHelpers.ProcessArgumentsForCom(ref args);
62                 return BindComInvoke(args, method, binder.CallInfo, isByRef);
63             }
64
65             return base.BindInvoke(binder, args);
66         }
67
68         private DynamicMetaObject BindComInvoke(DynamicMetaObject[] args, ComMethodDesc method, CallInfo callInfo, bool[] isByRef) {
69             return new ComInvokeBinder(
70                 callInfo,
71                 args,
72                 isByRef,
73                 IDispatchRestriction(),
74                 Expression.Constant(method),
75                 Expression.Property(
76                     Helpers.Convert(Expression, typeof(IDispatchComObject)),
77                     typeof(IDispatchComObject).GetProperty("DispatchObject")
78                 ),
79                 method
80             ).Invoke();
81         }
82
83         public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
84             ComBinder.ComGetMemberBinder comBinder = binder as ComBinder.ComGetMemberBinder;
85             bool canReturnCallables = comBinder == null ? false : comBinder._CanReturnCallables;
86
87             ContractUtils.RequiresNotNull(binder, "binder");
88
89             ComMethodDesc method;
90             ComEventDesc @event;
91
92             // 1. Try methods
93             if (_self.TryGetMemberMethod(binder.Name, out method)) {
94                 return BindGetMember(method, canReturnCallables);
95             }
96
97             // 2. Try events
98             if (_self.TryGetMemberEvent(binder.Name, out @event)) {
99                 return BindEvent(@event);
100             }
101
102             // 3. Try methods explicitly by name
103             if (_self.TryGetMemberMethodExplicit(binder.Name, out method)) {
104                 return BindGetMember(method, canReturnCallables);
105
106             }
107
108             // 4. Fallback
109             return base.BindGetMember(binder);
110         }
111
112         private DynamicMetaObject BindGetMember(ComMethodDesc method, bool canReturnCallables) {
113             if (method.IsDataMember) {
114                 if (method.ParamCount == 0) {
115                     return BindComInvoke(DynamicMetaObject.EmptyMetaObjects, method, new CallInfo(0) , new bool[]{});
116                 }
117             }
118
119             // ComGetMemberBinder does not expect callables. Try to call always.
120             if (!canReturnCallables) {
121                 return BindComInvoke(DynamicMetaObject.EmptyMetaObjects, method, new CallInfo(0), new bool[0]);
122             }
123
124             return new DynamicMetaObject(
125                 Expression.Call(
126                     typeof(ComRuntimeHelpers).GetMethod("CreateDispCallable"),
127                     Helpers.Convert(Expression, typeof(IDispatchComObject)),
128                     Expression.Constant(method)
129                 ),
130                 IDispatchRestriction()
131             );
132         }
133
134         private DynamicMetaObject BindEvent(ComEventDesc @event) {
135             // BoundDispEvent CreateComEvent(object rcw, Guid sourceIid, int dispid)
136             Expression result =
137                 Expression.Call(
138                     typeof(ComRuntimeHelpers).GetMethod("CreateComEvent"),
139                     ComObject.RcwFromComObject(Expression),
140                     Expression.Constant(@event.sourceIID),
141                     Expression.Constant(@event.dispid)
142                 );
143
144             return new DynamicMetaObject(
145                 result,
146                 IDispatchRestriction()
147             );
148         }
149
150         public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) {
151             ContractUtils.RequiresNotNull(binder, "binder");
152
153             ComMethodDesc getItem;
154             if (_self.TryGetGetItem(out getItem)) {
155
156                 bool[] isByRef = ComBinderHelpers.ProcessArgumentsForCom(ref indexes);
157                 return BindComInvoke(indexes, getItem, binder.CallInfo , isByRef);
158             }
159
160             return base.BindGetIndex(binder, indexes);
161         }
162
163         public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) {
164             ContractUtils.RequiresNotNull(binder, "binder");
165
166             ComMethodDesc setItem;
167             if (_self.TryGetSetItem(out setItem)) {
168
169                 bool[] isByRef = ComBinderHelpers.ProcessArgumentsForCom(ref indexes);
170                 isByRef = isByRef.AddLast(false);
171
172                 var result = BindComInvoke(indexes.AddLast(value), setItem, binder.CallInfo, isByRef);
173
174                 // Make sure to return the value; some languages need it.
175                 return new DynamicMetaObject(
176                     Expression.Block(result.Expression, Expression.Convert(value.Expression, typeof(object))),
177                     result.Restrictions
178                 );
179             }
180
181             return base.BindSetIndex(binder, indexes, value);
182         }
183
184         public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {
185             ContractUtils.RequiresNotNull(binder, "binder");
186
187             return
188                 // 1. Check for simple property put
189                 TryPropertyPut(binder, value) ??
190
191                 // 2. Check for event handler hookup where the put is dropped
192                 TryEventHandlerNoop(binder, value) ??
193
194                 // 3. Fallback
195                 base.BindSetMember(binder, value);
196         }
197
198         private DynamicMetaObject TryPropertyPut(SetMemberBinder binder, DynamicMetaObject value) {
199             ComMethodDesc method;
200             bool holdsNull = value.Value == null && value.HasValue;
201             if (_self.TryGetPropertySetter(binder.Name, out method, value.LimitType, holdsNull) ||
202                 _self.TryGetPropertySetterExplicit(binder.Name, out method, value.LimitType, holdsNull)) {
203                 BindingRestrictions restrictions = IDispatchRestriction();
204                 Expression dispatch =
205                     Expression.Property(
206                         Helpers.Convert(Expression, typeof(IDispatchComObject)),
207                         typeof(IDispatchComObject).GetProperty("DispatchObject")
208                     );
209
210                 var result = new ComInvokeBinder(
211                     new CallInfo(1),
212                     new[] { value },
213                     new bool[] { false },
214                     restrictions,
215                     Expression.Constant(method),
216                     dispatch,
217                     method
218                 ).Invoke();
219
220                 // Make sure to return the value; some languages need it.
221                 return new DynamicMetaObject(
222                     Expression.Block(result.Expression, Expression.Convert(value.Expression, typeof(object))),
223                     result.Restrictions
224                 );
225             }
226
227             return null;
228         }
229
230         private DynamicMetaObject TryEventHandlerNoop(SetMemberBinder binder, DynamicMetaObject value) {
231             ComEventDesc @event;
232             if (_self.TryGetMemberEvent(binder.Name, out @event) && value.LimitType == typeof(BoundDispEvent)) {
233                 // Drop the event property set.
234                 return new DynamicMetaObject(
235                     Expression.Constant(null),
236                     value.Restrictions.Merge(IDispatchRestriction()).Merge(BindingRestrictions.GetTypeRestriction(value.Expression, typeof(BoundDispEvent)))
237                 );
238             }
239
240             return null;
241         }
242
243         private BindingRestrictions IDispatchRestriction() {
244             return IDispatchRestriction(Expression, _self.ComTypeDesc);
245         }
246
247         internal static BindingRestrictions IDispatchRestriction(Expression expr, ComTypeDesc typeDesc) {
248             return BindingRestrictions.GetTypeRestriction(
249                 expr, typeof(IDispatchComObject)
250             ).Merge(
251                 BindingRestrictions.GetExpressionRestriction(
252                     Expression.Equal(
253                         Expression.Property(
254                             Helpers.Convert(expr, typeof(IDispatchComObject)),
255                             typeof(IDispatchComObject).GetProperty("ComTypeDesc")
256                         ),
257                         Expression.Constant(typeDesc)
258                     )
259                 )
260             );
261         }
262
263         protected override ComUnwrappedMetaObject UnwrapSelf() {
264             return new ComUnwrappedMetaObject(
265                 ComObject.RcwFromComObject(Expression),
266                 IDispatchRestriction(),
267                 _self.RuntimeCallableWrapper
268             );
269         }
270     }
271 }
272
273 #endif