/* ****************************************************************************
*
* 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.
*
*
* ***************************************************************************/
#if !SILVERLIGHT
#if !CLR2
using System.Linq.Expressions;
#else
using Microsoft.Scripting.Ast;
#endif
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Dynamic;
using System.Dynamic.Utils;
using ComTypesSystem.Runtime.InteropServices.ComTypes;
namespace Microsoft.Scripting.ComInterop{
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();
if (clear != null) {
finallyStatements.Add(clear);
}
}
//
// _invokeResult.Clear()
//
finallyStatements.Add(
Expression.Call(
InvokeResultVariable,
typeof(Variant).GetMethod("Clear")
)
);
//
// _dispIdsOfKeywordArgsPinned.Free()
//
if (_dispIdsOfKeywordArgsPinned != null) {
finallyStatements.Add(
Expression.Call(
DispIdsOfKeywordArgsPinnedVariable,
typeof(GCHandle).GetMethod("Free")
)
);
}
finallyStatements.Add(Expression.Empty());
return Expression.Block(finallyStatements);
}
/// <summary>
/// Create a stub for the target of the optimized lopop.
/// </summary>
/// <returns></returns>
private Expression MakeIDispatchInvokeTarget() {
Debug.Assert(_varEnumSelector.VariantBuilders.Length == _totalExplicitArgs);
List<Expression> exprs = new List<Expression>();
//
// _dispId = ((DispCallable)this).ComMethodDesc.DispId;
//
exprs.Add(
Expression.Assign(
DispIdVariable,
Expression.Property(_method, typeof(ComMethodDesc).GetProperty("DispId"))
)
);
//
// _dispParams.rgvararg = RuntimeHelpers.UnsafeMethods.ConvertVariantByrefToPtr(ref _paramVariants._element0)
//
if (_totalExplicitArgs != 0) {
exprs.Add(
Expression.Assign(
Expression.Field(
DispParamsVariable,
typeof(ComTypes.DISPPARAMS).GetField("rgvarg")
),
Expression.Call(
typeof(UnsafeMethods).GetMethod("ConvertVariantByrefToPtr"),
VariantArray.GetStructField(ParamVariantsVariable, 0)
)
)
);
}
//
// _dispParams.cArgs = <number_of_params>;
//
exprs.Add(
Expression.Assign(
Expression.Field(
DispParamsVariable,
typeof(ComTypes.DISPPARAMS).GetField("cArgs")
),
Expression.Constant(_totalExplicitArgs)
)
);
if (_methodDesc.IsPropertyPut) {
//
// dispParams.cNamedArgs = 1;
// dispParams.rgdispidNamedArgs = RuntimeHelpers.UnsafeMethods.GetNamedArgsForPropertyPut()
//
exprs.Add(
Expression.Assign(
Expression.Field(
DispParamsVariable,
typeof(ComTypes.DISPPARAMS).GetField("cNamedArgs")
),
Expression.Constant(1)
)
);
exprs.Add(
Expression.Assign(
PropertyPutDispIdVariable,
Expression.Constant(ComDispIds.DISPID_PROPERTYPUT)
)
);
exprs.Add(
Expression.Assign(
Expression.Field(
DispParamsVariable,
typeof(ComTypes.DISPPARAMS).GetField("rgdispidNamedArgs")
),
Expression.Call(
typeof(UnsafeMethods).GetMethod("ConvertInt32ByrefToPtr"),
PropertyPutDispIdVariable
)
)
);
} else {
//
// _dispParams.cNamedArgs = N;
//
exprs.Add(
Expression.Assign(
Expression.Field(
DispParamsVariable,
typeof(ComTypes.DISPPARAMS).GetField("cNamedArgs")
),
Expression.Constant(_keywordArgNames.Length)
)
);
}
//
// _dispatchObject = _dispatch
// _dispatchPointer = Marshal.GetIDispatchForObject(_dispatchObject);
//
exprs.Add(Expression.Assign(DispatchObjectVariable, _dispatch));
exprs.Add(
Expression.Assign(
DispatchPointerVariable,
Expression.Call(
typeof(Marshal).GetMethod("GetIDispatchForObject"),
DispatchObjectVariable
)
)
);
Expression tryStatements = GenerateTryBlock();
Expression finallyStatements = GenerateFinallyBlock();
exprs.Add(Expression.TryFinally(tryStatements, finallyStatements));
exprs.Add(ReturnValueVariable);
var vars = new List<ParameterExpression>();
foreach (var variant in _varEnumSelector.VariantBuilders) {
if (variant.TempVariable != null) {
vars.Add(variant.TempVariable);
}
}
return Expression.Block(vars, exprs);
}
/// <summary>
/// Gets expressions to access all the arguments. This includes the instance argument.
/// </summary>
private Expression[] MakeArgumentExpressions() {
Expression[] res;
int copy = 0;
if (_instance != null) {
res = new Expression[_args.Length + 1];
res[copy++] = _instance;
} else {
res = new Expression[_args.Length];
}
for (int i = 0; i < _args.Length; i++) {
res[copy++] = _args[i].Expression;
}
return res;
}
}
}
#endif
|