2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Dynamic / ComInvokeBinder.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
19
20 using System.Collections.Generic;
21 using System.Diagnostics;
22 #if CODEPLEX_40
23 using System.Linq.Expressions;
24 #else
25 using Microsoft.Linq.Expressions;
26 #endif
27 using System.Runtime.InteropServices;
28 #if CODEPLEX_40
29 using System.Dynamic;
30 using System.Dynamic.Utils;
31 #else
32 using Microsoft.Scripting;
33 using Microsoft.Scripting.Utils;
34 #endif
35 using ComTypes = System.Runtime.InteropServices.ComTypes;
36
37 #if CODEPLEX_40
38 namespace System.Dynamic {
39 #else
40 namespace Microsoft.Scripting {
41 #endif
42     internal sealed class ComInvokeBinder {
43         private readonly ComMethodDesc _methodDesc;
44         private readonly Expression _method;        // ComMethodDesc to be called
45         private readonly Expression _dispatch;      // IDispatch
46
47         private readonly CallInfo _callInfo;
48         private readonly DynamicMetaObject[] _args;
49         private readonly bool[] _isByRef;
50         private readonly Expression _instance;
51
52         private BindingRestrictions _restrictions;
53
54         private VarEnumSelector _varEnumSelector;
55         private string[] _keywordArgNames;
56         private int _totalExplicitArgs; // Includes the individial elements of ArgumentKind.Dictionary (if any)
57
58         private ParameterExpression _dispatchObject;
59         private ParameterExpression _dispatchPointer;
60         private ParameterExpression _dispId;
61         private ParameterExpression _dispParams;
62         private ParameterExpression _paramVariants;
63         private ParameterExpression _invokeResult;
64         private ParameterExpression _returnValue;
65         private ParameterExpression _dispIdsOfKeywordArgsPinned;
66         private ParameterExpression _propertyPutDispId;
67
68         internal ComInvokeBinder(
69                 CallInfo callInfo, 
70                 DynamicMetaObject[] args,
71                 bool[] isByRef,
72                 BindingRestrictions restrictions, 
73                 Expression method, 
74                 Expression dispatch, 
75                 ComMethodDesc methodDesc
76                 ) {
77
78             Debug.Assert(callInfo != null, "arguments");
79             Debug.Assert(args != null, "args");
80             Debug.Assert(isByRef != null, "isByRef");
81             Debug.Assert(method != null, "method");
82             Debug.Assert(dispatch != null, "dispatch");
83
84             Debug.Assert(TypeUtils.AreReferenceAssignable(typeof(ComMethodDesc), method.Type), "method");
85             Debug.Assert(TypeUtils.AreReferenceAssignable(typeof(IDispatch), dispatch.Type), "dispatch");
86
87             _method = method;
88             _dispatch = dispatch;
89             _methodDesc = methodDesc;
90
91             _callInfo = callInfo;
92             _args = args;
93             _isByRef = isByRef;
94             _restrictions = restrictions;
95
96             // Set Instance to some value so that CallBinderHelper has the right number of parameters to work with
97             _instance = dispatch;
98         }
99
100         private ParameterExpression DispatchObjectVariable {
101             get { return EnsureVariable(ref _dispatchObject, typeof(IDispatch), "dispatchObject"); }
102         }
103
104         private ParameterExpression DispatchPointerVariable {
105             get { return EnsureVariable(ref _dispatchPointer, typeof(IntPtr), "dispatchPointer"); }
106         }
107
108         private ParameterExpression DispIdVariable {
109             get { return EnsureVariable(ref _dispId, typeof(int), "dispId"); }
110         }
111
112         private ParameterExpression DispParamsVariable {
113             get { return EnsureVariable(ref _dispParams, typeof(ComTypes.DISPPARAMS), "dispParams"); }
114         }
115
116         private ParameterExpression InvokeResultVariable {
117             get { return EnsureVariable(ref _invokeResult, typeof(Variant), "invokeResult"); }
118         }
119
120         private ParameterExpression ReturnValueVariable {
121             get { return EnsureVariable(ref _returnValue, typeof(object), "returnValue"); }
122         }
123
124         private ParameterExpression DispIdsOfKeywordArgsPinnedVariable {
125             get { return EnsureVariable(ref _dispIdsOfKeywordArgsPinned, typeof(GCHandle), "dispIdsOfKeywordArgsPinned"); }
126         }
127
128         private ParameterExpression PropertyPutDispIdVariable {
129             get { return EnsureVariable(ref _propertyPutDispId, typeof(int), "propertyPutDispId"); }
130         }
131
132         private ParameterExpression ParamVariantsVariable {
133             get {
134                 if (_paramVariants == null) {
135                     _paramVariants = Expression.Variable(VariantArray.GetStructType(_args.Length), "paramVariants");
136                 }
137                 return _paramVariants;
138             }
139         }
140
141         private static ParameterExpression EnsureVariable(ref ParameterExpression var, Type type, string name) {
142             if (var != null) {
143                 return var;
144             }
145             return var = Expression.Variable(type, name);
146         }
147
148         private static Type MarshalType(DynamicMetaObject mo, bool isByRef) {
149             Type marshalType = (mo.Value == null && mo.HasValue && !mo.LimitType.IsValueType) ? null : mo.LimitType;
150
151             // we are not checking that mo.Expression is writeable or whether evaluating it has no sideeffects
152             // the assumption is that whoever matched it with ByRef arginfo took care of this.
153             if (isByRef) {
154                 // Null just means that null was supplied.
155                 if (marshalType == null) {
156                     marshalType = mo.Expression.Type;
157                 }
158                 marshalType = marshalType.MakeByRefType();
159             }
160             return marshalType;
161         }
162
163         internal DynamicMetaObject Invoke() {
164             _keywordArgNames = _callInfo.ArgumentNames.ToArray();
165             _totalExplicitArgs = _args.Length;
166             
167             Type[] marshalArgTypes = new Type[_args.Length];
168
169             // We already tested the instance, so no need to test it again
170             for (int i = 0; i < _args.Length; i++) {
171                 DynamicMetaObject curMo = _args[i];
172                 _restrictions = _restrictions.Merge(ComBinderHelpers.GetTypeRestrictionForDynamicMetaObject(curMo));
173                 marshalArgTypes[i] = MarshalType(curMo, _isByRef[i]);
174             }
175
176             _varEnumSelector = new VarEnumSelector(marshalArgTypes);
177
178             return new DynamicMetaObject(
179                 CreateScope(MakeIDispatchInvokeTarget()),
180                 BindingRestrictions.Combine(_args).Merge(_restrictions)
181             );
182         }
183
184         private static void AddNotNull(List<ParameterExpression> list, ParameterExpression var) {
185             if (var != null) list.Add(var);
186         }
187
188         private Expression CreateScope(Expression expression) {
189             List<ParameterExpression> vars = new List<ParameterExpression>();
190             AddNotNull(vars, _dispatchObject);
191             AddNotNull(vars, _dispatchPointer);
192             AddNotNull(vars, _dispId);
193             AddNotNull(vars, _dispParams);
194             AddNotNull(vars, _paramVariants);
195             AddNotNull(vars, _invokeResult);
196             AddNotNull(vars, _returnValue);
197             AddNotNull(vars, _dispIdsOfKeywordArgsPinned);
198             AddNotNull(vars, _propertyPutDispId);
199             return vars.Count > 0 ? Expression.Block(vars, expression) : expression;
200         }
201
202         private Expression GenerateTryBlock() {
203             //
204             // Declare variables
205             //
206             ParameterExpression excepInfo = Expression.Variable(typeof(ExcepInfo), "excepInfo");
207             ParameterExpression argErr = Expression.Variable(typeof(uint), "argErr");
208             ParameterExpression hresult = Expression.Variable(typeof(int), "hresult");
209
210             List<Expression> tryStatements = new List<Expression>();
211             Expression expr;
212
213             if (_keywordArgNames.Length > 0) {
214                 string[] names = _keywordArgNames.AddFirst(_methodDesc.Name);
215
216                 tryStatements.Add(
217                     Expression.Assign(
218                         Expression.Field(
219                             DispParamsVariable,
220                             typeof(ComTypes.DISPPARAMS).GetField("rgdispidNamedArgs")
221                         ),
222                         Expression.Call(typeof(UnsafeMethods).GetMethod("GetIdsOfNamedParameters"),
223                             DispatchObjectVariable,
224                             Expression.Constant(names),
225                             DispIdVariable,
226                             DispIdsOfKeywordArgsPinnedVariable
227                         )
228                     )
229                 );
230             }
231
232             //
233             // Marshal the arguments to Variants
234             //
235             // For a call like this:
236             //   comObj.Foo(100, 101, 102, x=123, z=125)
237             // DISPPARAMS needs to be setup like this:
238             //   cArgs:             5
239             //   cNamedArgs:        2
240             //   rgArgs:            123, 125, 102, 101, 100
241             //   rgdispidNamedArgs: dispid x, dispid z (the dispids of x and z respectively)
242
243             Expression[] parameters = MakeArgumentExpressions();
244
245             int reverseIndex = _varEnumSelector.VariantBuilders.Length - 1;
246             int positionalArgs = _varEnumSelector.VariantBuilders.Length - _keywordArgNames.Length; // args passed by position order and not by name
247             for (int i = 0; i < _varEnumSelector.VariantBuilders.Length; i++, reverseIndex--) {
248                 int variantIndex;
249                 if (i >= positionalArgs) {
250                     // Named arguments are in order at the start of rgArgs
251                     variantIndex = i - positionalArgs;
252                 } else {
253                     // Positial arguments are in reverse order at the tail of rgArgs
254                     variantIndex = reverseIndex;
255                 }
256                 VariantBuilder variantBuilder = _varEnumSelector.VariantBuilders[i];
257
258                 Expression marshal = variantBuilder.InitializeArgumentVariant(
259                     VariantArray.GetStructField(ParamVariantsVariable, variantIndex),
260                     parameters[i + 1]
261                 );
262
263                 if (marshal != null) {
264                     tryStatements.Add(marshal);
265                 }
266             }
267
268
269             //
270             // Call Invoke
271             //
272
273             ComTypes.INVOKEKIND invokeKind;
274             if (_methodDesc.IsPropertyPut) {
275                 if (_methodDesc.IsPropertyPutRef) {
276                     invokeKind = ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF;
277                 } else {
278                     invokeKind = ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT;
279                 }
280             } else {
281                 // INVOKE_PROPERTYGET should only be needed for COM objects without typeinfo, where we might have to treat properties as methods
282                 invokeKind = ComTypes.INVOKEKIND.INVOKE_FUNC | ComTypes.INVOKEKIND.INVOKE_PROPERTYGET;
283             }
284
285             MethodCallExpression invoke = Expression.Call(
286                 typeof(UnsafeMethods).GetMethod("IDispatchInvoke"),
287                 DispatchPointerVariable,
288                 DispIdVariable,
289                 Expression.Constant(invokeKind),
290                 DispParamsVariable,
291                 InvokeResultVariable,
292                 excepInfo,
293                 argErr
294             );
295
296             expr = Expression.Assign(hresult, invoke);
297             tryStatements.Add(expr);
298
299             //
300             // ComRuntimeHelpers.CheckThrowException(hresult, excepInfo, argErr, ThisParameter);
301             //
302             expr = Expression.Call(
303                 typeof(ComRuntimeHelpers).GetMethod("CheckThrowException"),
304                 hresult,
305                 excepInfo,
306                 argErr,
307                 Expression.Constant(_methodDesc.Name, typeof(string))
308             );
309             tryStatements.Add(expr);
310
311             //
312             // _returnValue = (ReturnType)_invokeResult.ToObject();
313             //
314             Expression invokeResultObject =
315                 Expression.Call(
316                     InvokeResultVariable,
317                     typeof(Variant).GetMethod("ToObject"));
318
319             VariantBuilder[] variants = _varEnumSelector.VariantBuilders;
320
321             Expression[] parametersForUpdates = MakeArgumentExpressions();
322             tryStatements.Add(Expression.Assign(ReturnValueVariable, invokeResultObject));
323
324             for (int i = 0, n = variants.Length; i < n; i++) {
325                 Expression updateFromReturn = variants[i].UpdateFromReturn(parametersForUpdates[i + 1]);
326                 if (updateFromReturn != null) {
327                     tryStatements.Add(updateFromReturn);
328                 }
329             }
330
331             tryStatements.Add(Expression.Empty());
332
333             return Expression.Block(new[] { excepInfo, argErr, hresult }, tryStatements);
334         }
335
336         private Expression GenerateFinallyBlock() {
337             List<Expression> finallyStatements = new List<Expression>();
338
339             //
340             // UnsafeMethods.IUnknownRelease(dispatchPointer);
341             //
342             finallyStatements.Add(
343                 Expression.Call(
344                     typeof(UnsafeMethods).GetMethod("IUnknownRelease"),
345                     DispatchPointerVariable
346                 )
347             );
348
349             //
350             // Clear memory allocated for marshalling
351             //
352             for (int i = 0, n = _varEnumSelector.VariantBuilders.Length; i < n; i++) {
353                 Expression clear = _varEnumSelector.VariantBuilders[i].Clear();
354                 if (clear != null) {
355                     finallyStatements.Add(clear);
356                 }
357             }
358
359             //
360             // _invokeResult.Clear()
361             //
362
363             finallyStatements.Add(
364                 Expression.Call(
365                     InvokeResultVariable,
366                     typeof(Variant).GetMethod("Clear")
367                 )
368             );
369
370             //
371             // _dispIdsOfKeywordArgsPinned.Free()
372             //
373             if (_dispIdsOfKeywordArgsPinned != null) {
374                 finallyStatements.Add(
375                     Expression.Call(
376                         DispIdsOfKeywordArgsPinnedVariable,
377                         typeof(GCHandle).GetMethod("Free")
378                     )
379                 );
380             }
381
382             finallyStatements.Add(Expression.Empty());
383             return Expression.Block(finallyStatements);
384         }
385
386         /// <summary>
387         /// Create a stub for the target of the optimized lopop.
388         /// </summary>
389         /// <returns></returns>
390         private Expression MakeIDispatchInvokeTarget() {
391             Debug.Assert(_varEnumSelector.VariantBuilders.Length == _totalExplicitArgs);
392
393             List<Expression> exprs = new List<Expression>();
394
395             //
396             // _dispId = ((DispCallable)this).ComMethodDesc.DispId;
397             //
398             exprs.Add(
399                 Expression.Assign(
400                     DispIdVariable,
401                     Expression.Property(_method, typeof(ComMethodDesc).GetProperty("DispId"))
402                 )
403             );
404
405             //
406             // _dispParams.rgvararg = RuntimeHelpers.UnsafeMethods.ConvertVariantByrefToPtr(ref _paramVariants._element0)
407             //
408             if (_totalExplicitArgs != 0) {
409                 exprs.Add(
410                     Expression.Assign(
411                         Expression.Field(
412                             DispParamsVariable,
413                             typeof(ComTypes.DISPPARAMS).GetField("rgvarg")
414                         ),
415                         Expression.Call(
416                             typeof(UnsafeMethods).GetMethod("ConvertVariantByrefToPtr"),
417                             VariantArray.GetStructField(ParamVariantsVariable, 0)
418                         )
419                     )
420                 );
421             }
422
423             //
424             // _dispParams.cArgs = <number_of_params>;
425             //
426             exprs.Add(
427                 Expression.Assign(
428                     Expression.Field(
429                         DispParamsVariable,
430                         typeof(ComTypes.DISPPARAMS).GetField("cArgs")
431                     ),
432                     Expression.Constant(_totalExplicitArgs)
433                 )
434             );
435
436             if (_methodDesc.IsPropertyPut) {
437                 //
438                 // dispParams.cNamedArgs = 1;
439                 // dispParams.rgdispidNamedArgs = RuntimeHelpers.UnsafeMethods.GetNamedArgsForPropertyPut()
440                 //
441                 exprs.Add(
442                     Expression.Assign(
443                         Expression.Field(
444                             DispParamsVariable,
445                             typeof(ComTypes.DISPPARAMS).GetField("cNamedArgs")
446                         ),
447                         Expression.Constant(1)
448                     )
449                 );
450
451                 exprs.Add(
452                     Expression.Assign(
453                         PropertyPutDispIdVariable,
454                         Expression.Constant(ComDispIds.DISPID_PROPERTYPUT)
455                     )
456                 );
457
458                 exprs.Add(
459                     Expression.Assign(
460                         Expression.Field(
461                             DispParamsVariable,
462                             typeof(ComTypes.DISPPARAMS).GetField("rgdispidNamedArgs")
463                         ),
464                         Expression.Call(
465                             typeof(UnsafeMethods).GetMethod("ConvertInt32ByrefToPtr"),
466                             PropertyPutDispIdVariable
467                         )
468                     )
469                 );
470             } else {
471                 //
472                 // _dispParams.cNamedArgs = N;
473                 //
474                 exprs.Add(
475                     Expression.Assign(
476                         Expression.Field(
477                             DispParamsVariable,
478                             typeof(ComTypes.DISPPARAMS).GetField("cNamedArgs")
479                         ),
480                         Expression.Constant(_keywordArgNames.Length)
481                     )
482                 );
483             }
484
485             //
486             // _dispatchObject = _dispatch
487             // _dispatchPointer = Marshal.GetIDispatchForObject(_dispatchObject);
488             //
489
490             exprs.Add(Expression.Assign(DispatchObjectVariable, _dispatch));
491
492             exprs.Add(
493                 Expression.Assign(
494                     DispatchPointerVariable,
495                     Expression.Call(
496                         typeof(Marshal).GetMethod("GetIDispatchForObject"),
497                         DispatchObjectVariable
498                     )
499                 )
500             );
501
502             Expression tryStatements = GenerateTryBlock();
503             Expression finallyStatements = GenerateFinallyBlock();
504
505             exprs.Add(Expression.TryFinally(tryStatements, finallyStatements));
506
507             exprs.Add(ReturnValueVariable);
508             var vars = new List<ParameterExpression>();
509             foreach (var variant in _varEnumSelector.VariantBuilders) {
510                 if (variant.TempVariable != null) {
511                     vars.Add(variant.TempVariable);
512                 }
513             }
514             return Expression.Block(vars, exprs);
515         }
516
517         /// <summary>
518         /// Gets expressions to access all the arguments. This includes the instance argument.
519         /// </summary>
520         private Expression[] MakeArgumentExpressions() {
521             Expression[] res;
522             int copy = 0;
523             if (_instance != null) {
524                 res = new Expression[_args.Length + 1];
525                 res[copy++] = _instance;
526             } else {
527                 res = new Expression[_args.Length];
528             }
529
530             for (int i = 0; i < _args.Length; i++) {
531                 res[copy++] = _args[i].Expression;
532             }
533             return res;
534         }
535     }
536 }
537
538 #endif