context: /* ****************************************************************************
   *
   * Copyright (c) Microsoft Corporation. 
   *
   * This source code is subject to terms and conditions of the Microsoft Public License. A 
   * copy of the license can be found in the License.html file at the root of this distribution. If 
   * you cannot locate the  Microsoft Public License, please send an email to 
   * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
   * by the terms of the Microsoft Public License.
   *
   * You must not remove this notice, or any other, from this software.
   *
   *
   * ***************************************************************************/
  using System; using Microsoft;
  
  
  #if !SILVERLIGHT
  
  using System.Collections.Generic;
  using System.Diagnostics;
  #if CODEPLEX_40
  using System.Linq.Expressions;
  #else
  using Microsoft.Linq.Expressions;
  #endif
  using System.Runtime.InteropServices;
  #if CODEPLEX_40
  using System.Dynamic;
  using System.Dynamic.Utils;
  #else
  using Microsoft.Scripting;
  using Microsoft.Scripting.Utils;
  #endif
  using ComTypes = System.Runtime.InteropServices.ComTypes;
  
  #if CODEPLEX_40
  namespace System.Dynamic {
  #else
  namespace Microsoft.Scripting {
  #endif
      internal sealed class ComInvokeBinder {
          private readonly ComMethodDesc _methodDesc;
          private readonly Expression _method;        // ComMethodDesc to be called
          private readonly Expression _dispatch;      // IDispatch
  
          private readonly CallInfo _callInfo;
          private readonly DynamicMetaObject[] _args;
          private readonly bool[] _isByRef;
          private readonly Expression _instance;
  
          private BindingRestrictions _restrictions;
  
          private VarEnumSelector _varEnumSelector;
          private string[] _keywordArgNames;
          private int _totalExplicitArgs; // Includes the individial elements of ArgumentKind.Dictionary (if any)
  
          private ParameterExpression _dispatchObject;
          private ParameterExpression _dispatchPointer;
          private ParameterExpression _dispId;
          private ParameterExpression _dispParams;
          private ParameterExpression _paramVariants;
          private ParameterExpression _invokeResult;
          private ParameterExpression _returnValue;
          private ParameterExpression _dispIdsOfKeywordArgsPinned;
          private ParameterExpression _propertyPutDispId;
  
          internal ComInvokeBinder(
                  CallInfo callInfo, 
                  DynamicMetaObject[] args,
                  bool[] isByRef,
                  BindingRestrictions restrictions, 
                  Expression method, 
                  Expression dispatch, 
                  ComMethodDesc methodDesc
                  ) {
  
              Debug.Assert(callInfo != null, "arguments");
              Debug.Assert(args != null, "args");
              Debug.Assert(isByRef != null, "isByRef");
              Debug.Assert(method != null, "method");
              Debug.Assert(dispatch != null, "dispatch");
  
              Debug.Assert(TypeUtils.AreReferenceAssignable(typeof(ComMethodDesc), method.Type), "method");
              Debug.Assert(TypeUtils.AreReferenceAssignable(typeof(IDispatch), dispatch.Type), "dispatch");
  
              _method = method;
              _dispatch = dispatch;
              _methodDesc = methodDesc;
  
              _callInfo = callInfo;
              _args = args;
              _isByRef = isByRef;
              _restrictions = restrictions;
  
              // Set Instance to some value so that CallBinderHelper has the right number of parameters to work with
              _instance = dispatch;
          }
  
          private ParameterExpression DispatchObjectVariable {
              get { return EnsureVariable(ref _dispatchObject, typeof(IDispatch), "dispatchObject"); }
          }
  
          private ParameterExpression DispatchPointerVariable {
              get { return EnsureVariable(ref _dispatchPointer, typeof(IntPtr), "dispatchPointer"); }
          }
  
          private ParameterExpression DispIdVariable {
              get { return EnsureVariable(ref _dispId, typeof(int), "dispId"); }
          }
  
          private ParameterExpression DispParamsVariable {
              get { return EnsureVariable(ref _dispParams, typeof(ComTypes.DISPPARAMS), "dispParams"); }
          }
  
          private ParameterExpression InvokeResultVariable {
              get { return EnsureVariable(ref _invokeResult, typeof(Variant), "invokeResult"); }
          }
  
          private ParameterExpression ReturnValueVariable {
              get { return EnsureVariable(ref _returnValue, typeof(object), "returnValue"); }
          }
  
          private ParameterExpression DispIdsOfKeywordArgsPinnedVariable {
              get { return EnsureVariable(ref _dispIdsOfKeywordArgsPinned, typeof(GCHandle), "dispIdsOfKeywordArgsPinned"); }
          }
  
          private ParameterExpression PropertyPutDispIdVariable {
              get { return EnsureVariable(ref _propertyPutDispId, typeof(int), "propertyPutDispId"); }
          }
  
          private ParameterExpression ParamVariantsVariable {
              get {
                  if (_paramVariants == null) {
                      _paramVariants = Expression.Variable(VariantArray.GetStructType(_args.Length), "paramVariants");
                  }
                  return _paramVariants;
              }
          }
  
          private static ParameterExpression EnsureVariable(ref ParameterExpression var, Type type, string name) {
              if (var != null) {
                  return var;
              }
              return var = Expression.Variable(type, name);
          }
  
          private static Type MarshalType(DynamicMetaObject mo, bool isByRef) {
              Type marshalType = (mo.Value == null && mo.HasValue && !mo.LimitType.IsValueType) ? null : mo.LimitType;
  
              // we are not checking that mo.Expression is writeable or whether evaluating it has no sideeffects
              // the assumption is that whoever matched it with ByRef arginfo took care of this.
              if (isByRef) {
                  // Null just means that null was supplied.
                  if (marshalType == null) {
                      marshalType = mo.Expression.Type;
                  }
                  marshalType = marshalType.MakeByRefType();
              }
              return marshalType;
          }
  
          internal DynamicMetaObject Invoke() {
              _keywordArgNames = _callInfo.ArgumentNames.ToArray();
              _totalExplicitArgs = _args.Length;
              
              Type[] marshalArgTypes = new Type[_args.Length];
  
              // We already tested the instance, so no need to test it again
              for (int i = 0; i < _args.Length; i++) {
                  DynamicMetaObject curMo = _args[i];
                  _restrictions = _restrictions.Merge(ComBinderHelpers.GetTypeRestrictionForDynamicMetaObject(curMo));
                  marshalArgTypes[i] = MarshalType(curMo, _isByRef[i]);
              }
  
              _varEnumSelector = new VarEnumSelector(marshalArgTypes);
  
              return new DynamicMetaObject(
                  CreateScope(MakeIDispatchInvokeTarget()),
                  BindingRestrictions.Combine(_args).Merge(_restrictions)
              );
          }
  
          private static void AddNotNull(List<ParameterExpression> list, ParameterExpression var) {
              if (var != null) list.Add(var);
          }
  
          private Expression CreateScope(Expression expression) {
              List<ParameterExpression> vars = new List<ParameterExpression>();
              AddNotNull(vars, _dispatchObject);
              AddNotNull(vars, _dispatchPointer);
              AddNotNull(vars, _dispId);
              AddNotNull(vars, _dispParams);
              AddNotNull(vars, _paramVariants);
              AddNotNull(vars, _invokeResult);
              AddNotNull(vars, _returnValue);
              AddNotNull(vars, _dispIdsOfKeywordArgsPinned);
              AddNotNull(vars, _propertyPutDispId);
              return vars.Count > 0 ? Expression.Block(vars, expression) : expression;
          }
  
          private Expression GenerateTryBlock() {
              //
              // Declare variables
              //
              ParameterExpression excepInfo = Expression.Variable(typeof(ExcepInfo), "excepInfo");
              ParameterExpression argErr = Expression.Variable(typeof(uint), "argErr");
              ParameterExpression hresult = Expression.Variable(typeof(int), "hresult");
  
              List<Expression> tryStatements = new List<Expression>();
              Expression expr;
  
              if (_keywordArgNames.Length > 0) {
                  string[] names = _keywordArgNames.AddFirst(_methodDesc.Name);
  
                  tryStatements.Add(
                      Expression.Assign(
                          Expression.Field(
                              DispParamsVariable,
                              typeof(ComTypes.DISPPARAMS).GetField("rgdispidNamedArgs")
                          ),
                          Expression.Call(typeof(UnsafeMethods).GetMethod("GetIdsOfNamedParameters"),
                              DispatchObjectVariable,
                              Expression.Constant(names),
                              DispIdVariable,
                              DispIdsOfKeywordArgsPinnedVariable
                          )
                      )
                  );
              }
  
              //
              // Marshal the arguments to Variants
              //
              // For a call like this:
              //   comObj.Foo(100, 101, 102, x=123, z=125)
              // DISPPARAMS needs to be setup like this:
              //   cArgs:             5
              //   cNamedArgs:        2
              //   rgArgs:            123, 125, 102, 101, 100
              //   rgdispidNamedArgs: dispid x, dispid z (the dispids of x and z respectively)
  
              Expression[] parameters = MakeArgumentExpressions();
  
              int reverseIndex = _varEnumSelector.VariantBuilders.Length - 1;
              int positionalArgs = _varEnumSelector.VariantBuilders.Length - _keywordArgNames.Length; // args passed by position order and not by name
              for (int i = 0; i < _varEnumSelector.VariantBuilders.Length; i++, reverseIndex--) {
                  int variantIndex;
                  if (i >= positionalArgs) {
                      // Named arguments are in order at the start of rgArgs
                      variantIndex = i - positionalArgs;
                  } else {
                      // Positial arguments are in reverse order at the tail of rgArgs
                      variantIndex = reverseIndex;
                  }
                  VariantBuilder variantBuilder = _varEnumSelector.VariantBuilders[i];
  
                  Expression marshal = variantBuilder.InitializeArgumentVariant(
                      VariantArray.GetStructField(ParamVariantsVariable, variantIndex),
                      parameters[i + 1]
                  );
  
                  if (marshal != null) {
                      tryStatements.Add(marshal);
                  }
              }
  
  
              //
              // Call Invoke
              //
  
              ComTypes.INVOKEKIND invokeKind;
              if (_methodDesc.IsPropertyPut) {
                  if (_methodDesc.IsPropertyPutRef) {
                      invokeKind = ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF;
                  } else {
                      invokeKind = ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT;
                  }
              } else {
                  // INVOKE_PROPERTYGET should only be needed for COM objects without typeinfo, where we might have to treat properties as methods
                  invokeKind = ComTypes.INVOKEKIND.INVOKE_FUNC | ComTypes.INVOKEKIND.INVOKE_PROPERTYGET;
              }
  
              MethodCallExpression invoke = Expression.Call(
                  typeof(UnsafeMethods).GetMethod("IDispatchInvoke"),
                  DispatchPointerVariable,
                  DispIdVariable,
                  Expression.Constant(invokeKind),
                  DispParamsVariable,
                  InvokeResultVariable,
                  excepInfo,
                  argErr
              );
  
              expr = Expression.Assign(hresult, invoke);
              tryStatements.Add(expr);
  
              //
              // ComRuntimeHelpers.CheckThrowException(hresult, excepInfo, argErr, ThisParameter);
              //
              expr = Expression.Call(
                  typeof(ComRuntimeHelpers).GetMethod("CheckThrowException"),
                  hresult,
                  excepInfo,
                  argErr,
                  Expression.Constant(_methodDesc.Name, typeof(string))
              );
              tryStatements.Add(expr);
  
              //
              // _returnValue = (ReturnType)_invokeResult.ToObject();
              //
              Expression invokeResultObject =
                  Expression.Call(
                      InvokeResultVariable,
                      typeof(Variant).GetMethod("ToObject"));
  
              VariantBuilder[] variants = _varEnumSelector.VariantBuilders;
  
              Expression[] parametersForUpdates = MakeArgumentExpressions();
              tryStatements.Add(Expression.Assign(ReturnValueVariable, invokeResultObject));
  
              for (int i = 0, n = variants.Length; i < n; i++) {
                  Expression updateFromReturn = variants[i].UpdateFromReturn(parametersForUpdates[i + 1]);
                  if (updateFromReturn != null) {
                      tryStatements.Add(updateFromReturn);
                  }
              }
  
              tryStatements.Add(Expression.Empty());
  
              return Expression.Block(new[] { excepInfo, argErr, hresult }, tryStatements);
          }
  
          private Expression GenerateFinallyBlock() {
              List<Expression> finallyStatements = new List<Expression>();
  
              //
              // UnsafeMethods.IUnknownRelease(dispatchPointer);
              //
              finallyStatements.Add(
                  Expression.Call(
                      typeof(UnsafeMethods).GetMethod("IUnknownRelease"),
                      DispatchPointerVariable
                  )
              );
  
              //
              // Clear memory allocated for marshalling
              //
              for (int i = 0, n = _varEnumSelector.VariantBuilders.Length; i < n; i++) {
                  Expression clear = _varEnumSelector.VariantBuilders[i].Clear();
  
gt:                 if (clear != null) {