/* **************************************************************************** * * 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 list, ParameterExpression var) { if (var != null) list.Add(var); } private Expression CreateScope(Expression expression) { List vars = new List(); 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 tryStatements = new List(); 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 finallyStatements = new List(); // // 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); } /// /// Create a stub for the target of the optimized lopop. /// /// private Expression MakeIDispatchInvokeTarget() { Debug.Assert(_varEnumSelector.VariantBuilders.Length == _totalExplicitArgs); List exprs = new List(); // // _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 = ; // 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(); foreach (var variant in _varEnumSelector.VariantBuilders) { if (variant.TempVariable != null) { vars.Add(variant.TempVariable); } } return Expression.Block(vars, exprs); } /// /// Gets expressions to access all the arguments. This includes the instance argument. /// 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