File: src\Framework\System\Windows\Interop\DynamicScriptObject.cs
Project: wpf\PresentationFramework.csproj (PresentationFramework)
//------------------------------------------------------------------------------
// <copyright file="DynamicScriptObject.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
//
// Description:  
//      Enables scripting support against the HTML DOM for XBAPs using the DLR
//      dynamic feature, as available through the dynamic keyword in C# 4.0 and
//      also supported in Visual Basic.
//
// History
//  04/29/09    Microsoft      Created
//  06/30/09    Microsoft      Changed to use IDispatchEx where possible
//------------------------------------------------------------------------------
 
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
using System.Windows;
using MS.Internal.Interop;
using MS.Win32;
 
namespace System.Windows.Interop
{
    /// <summary>
    /// Enables scripting support against the HTML DOM for XBAPs using the DLR.
    /// </summary>
    /// <SecurityNote>
    /// Instances of this type are directly exposed to partial-trust code through BrowserInteropHelper's
    /// HostScript property as an entry-point, and subsequently as a result of making dynamic calls on it
    /// causing wrapping in new instances of this type. All public methods on this type are used by the
    /// DLR to dispatch dynamic calls. The extent of the security measure taken here is to ensure that
    /// objects that get wrapped in DynamicScriptObject are safe for scripting, so all operations on them
    /// are deemed safe as well.
    /// This class needs to be public in order to make DLR accept it in partial trust. Making it internal
    /// causes failure followed by fallback to the default binders. Security-wise this should be fine as
    /// the wrapped script object is protected as SecurityCritical and only settable via the constructor,
    /// and therefore attempts to call any of the Try* methods with custom binder objects are fine as no
    /// calls can be made on an untrusted object.
    /// </SecurityNote>
    public sealed class DynamicScriptObject : DynamicObject
    {
        //----------------------------------------------
        //
        // Constructors
        //
        //----------------------------------------------
 
        #region Constructor
 
        /// <summary>
        /// Wraps the given object in a script object for "dynamic" access.
        /// </summary>
        /// <param name="scriptObject">Object to be wrapped.</param>
        /// <SecurityNote>
        ///     Critical - Sets the critical _scriptObject field. It's the responsibility of the caller
        ///                to ensure the object passed in is safe for scripting. We assume a closed world
        ///                OM where everything returned from an object that's safe for scripting is still
        ///                safe for scripting. This knowledge is used in wrapping returned objects in a
        ///                DynamicScriptObject upon return of a dynamic IDispatch-based call.
        /// </SecurityNote>
        [SecurityCritical]
        internal DynamicScriptObject(UnsafeNativeMethods.IDispatch scriptObject)
        {
            if (scriptObject == null)
            {
                throw new ArgumentNullException("scriptObject");
            }
 
            _scriptObject = scriptObject;
 
            // In the case of IE, we use IDispatchEx for enhanced security (see InvokeOnScriptObject).
            _scriptObjectEx = _scriptObject as UnsafeNativeMethods.IDispatchEx;
        }
 
        #endregion Constructor
 
 
        //----------------------------------------------
        //
        // Public Methods
        //
        //----------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        /// Calls a script method. Corresponds to methods calls in the front-end language.
        /// </summary>
        /// <param name="binder">The binder provided by the call site.</param>
        /// <param name="args">The arguments to be used for the invocation.</param>
        /// <param name="result">The result of the invocation.</param>
        /// <returns>true - We never defer behavior to the call site, and throw if invalid access is attempted.</returns>
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }
 
            result = InvokeAndReturn(binder.Name, NativeMethods.DISPATCH_METHOD, args);
            return true;
        }
 
        /// <summary>
        /// Gets a member from script. Corresponds to property getter syntax in the front-end language.
        /// </summary>
        /// <param name="binder">The binder provided by the call site.</param>
        /// <param name="result">The result of the invocation.</param>
        /// <returns>true - We never defer behavior to the call site, and throw if invalid access is attempted.</returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }
 
            result = InvokeAndReturn(binder.Name, NativeMethods.DISPATCH_PROPERTYGET, null);
            return true;
        }
 
        /// <summary>
        /// Sets a member in script. Corresponds to property setter syntax in the front-end language.
        /// </summary>
        /// <param name="binder">The binder provided by the call site.</param>
        /// <param name="value">The value to set.</param>
        /// <returns>true - We never defer behavior to the call site, and throw if invalid access is attempted.</returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }
 
            int flags = GetPropertyPutMethod(value);
            object result = InvokeAndReturn(binder.Name, flags, new object[] { value });
            return true;
        }
 
        /// <summary>
        /// Gets an indexed value from script. Corresponds to indexer getter syntax in the front-end language.
        /// </summary>
        /// <param name="binder">The binder provided by the call site.</param>
        /// <param name="indexes">The indexes to be used.</param>
        /// <param name="result">The result of the invocation.</param>
        /// <returns>true - We never defer behavior to the call site, and throw if invalid access is attempted.</returns>
        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }
 
            if (indexes == null)
            {
                throw new ArgumentNullException("indexes");
            }
 
            // IE supports a default member for indexers. Try that first. This accommodates for indexing
            // in collection types, using a default method called "item".
            if (BrowserInteropHelper.IsHostedInIEorWebOC)
            {
                if (TryFindMemberAndInvoke(null, NativeMethods.DISPATCH_METHOD, false /* no DISPID caching */, indexes, out result))
                {
                    return true;
                }
            }
 
            // We fall back to property lookup given the first argument of the indices. This accommodates
            // for arrays (e.g. d.x[0]) and square-bracket-style property lookup (e.g. d.document["title"]).
            if (indexes.Length != 1)
            {
                throw new ArgumentException("indexes", HRESULT.DISP_E_BADPARAMCOUNT.GetException());
            }
 
            object index = indexes[0];
            if (index == null)
            {
                throw new ArgumentOutOfRangeException("indexes");
            }
 
            result = InvokeAndReturn(index.ToString(), NativeMethods.DISPATCH_PROPERTYGET, false /* no DISPID caching */, null);
            return true;
        }
 
        /// <summary>
        /// Sets a member in script, through an indexer. Corresponds to indexer setter syntax in the front-end language.
        /// </summary>
        /// <param name="binder">The binder provided by the call site.</param>
        /// <param name="indexes">The indexes to be used.</param>
        /// <param name="value">The value to set.</param>
        /// <returns>true - We never defer behavior to the call site, and throw if invalid access is attempted.</returns>
        public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }
 
            if (indexes == null)
            {
                throw new ArgumentNullException("indexes");
            }
 
            if (indexes.Length != 1)
            {
                throw new ArgumentException("indexes", HRESULT.DISP_E_BADPARAMCOUNT.GetException());
            }
 
            object index = indexes[0];
            if (index == null)
            {
                throw new ArgumentOutOfRangeException("indexes");
            }
 
            // We don't cache resolved DISPIDs for indexers as they have the potential to be used for dynamic resolution
            // of a bunch of members, e.g. when indexing into arrays. This would flood the cache, likely for just a one-
            // time access. So we just don't cache in this case.
            object result = InvokeAndReturn(index.ToString(), NativeMethods.DISPATCH_PROPERTYPUT, false /* no DISPID caching */,
                                new object[] { value });
 
            return true;
        }
 
        /// <summary>
        /// Calls the default script method. Corresponds to delegate calling syntax in the front-end language.
        /// </summary>
        /// <param name="binder">The binder provided by the call site.</param>
        /// <param name="args">The arguments to be used for the invocation.</param>
        /// <param name="result">The result of the invocation.</param>
        /// <returns>true - We never defer behavior to the call site, and throw if invalid access is attempted.</returns>
        public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }
 
            result = InvokeAndReturn(null, NativeMethods.DISPATCH_METHOD, args);
            return true;
        }
 
        /// <summary>
        /// Provides a string representation of the wrapped script object.
        /// </summary>
        /// <returns>String representation of the wrapped script object, using the toString method or default member on the script object.</returns>
        public override string ToString()
        {
            // Note we shouldn't throw in this method (rule CA1065), so we try with best attempt.
 
            HRESULT hr;
 
            Guid guid = Guid.Empty;
            object result = null;
            var dp = new NativeMethods.DISPPARAMS();
 
            // Try to find a toString method.
            int dispid;
            if (TryGetDispIdForMember("toString", true /* DISPID caching */, out dispid))
            {
                hr = InvokeOnScriptObject(dispid, NativeMethods.DISPATCH_METHOD, dp, null /* EXCEPINFO */, out result);
            }
            else
            {
                // If no toString method is found, we try the default member first as a property, then as a method.
 
                dispid = NativeMethods.DISPID_VALUE;
 
                hr = InvokeOnScriptObject(dispid, NativeMethods.DISPATCH_PROPERTYGET, dp, null /* EXCEPINFO */, out result);
 
                if (hr.Failed)
                {
                    hr = InvokeOnScriptObject(dispid, NativeMethods.DISPATCH_METHOD, dp, null /* EXCEPINFO */, out result);
                }
            }
 
            if (hr.Succeeded && result != null)
            {
                return result.ToString();
            }
 
            return base.ToString();
        }
 
        #endregion Public Methods
 
 
        //----------------------------------------------
        //
        // Internal Properties
        //
        //----------------------------------------------
 
        #region Internal Properties
 
        /// <summary>
        /// Gets the IDispatch script object wrapped by the DynamicScriptObject.
        /// </summary>
        /// <SecurityNote>
        ///     Critical - Accesses the critical _scriptObject field.
        ///     TreatAsSafe - Though the IDispatch object per se is not necessarily safe for scripting, it has
        ///                   necessary protections built-in on the browser side. The more relevant reason for
        ///                   marking this as TAS is that invocations of members on the IDispatch interface
        ///                   required unmanaged code permissions anyway.
        /// </SecurityNote>
        internal UnsafeNativeMethods.IDispatch ScriptObject
        {
            [SecurityCritical, SecurityTreatAsSafe]
            get
            {
                return _scriptObject;
            }
        }
 
        #endregion Internal Properties
 
 
        //----------------------------------------------
        //
        // Internal Methods
        //
        //----------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        /// Helper method to attempt invoking a script member with the given name, flags and arguments.
        /// </summary>
        /// <param name="memberName">The name of the member to invoke.</param>
        /// <param name="flags">One of the DISPATCH_ flags for IDispatch calls.</param>
        /// <param name="cacheDispId">true to enable caching of DISPIDs; false otherwise.</param>
        /// <param name="args">Arguments passed to the call.</param>
        /// <param name="result">The raw (not wrapped in DynamicScriptObject) result of the invocation.</param>
        /// <returns>true if the member was found; false otherwise.</returns>
        /// <SecurityNote>
        ///     Critical - Unpacks the critical _scriptObject field on DynamicScriptObject arguments.
        ///     TreatAsSafe - Objects returned from script are considered safe for scripting.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        internal unsafe bool TryFindMemberAndInvokeNonWrapped(string memberName, int flags, bool cacheDispId, object[] args, out object result)
        {
            result = null;
 
            // In true DLR style we'd return false here, deferring further attempts for resolution to the
            // call site. For better debuggability though, and as we're talking to a specialized object
            // model, we rather throw instead. This Try-method allows internal lookups without directly
            // throwing on non-fatal "member not found" situations, as we might want to have more complex
            // fallback logic (as in ToString).
            int dispid;
            if (!TryGetDispIdForMember(memberName, cacheDispId, out dispid))
            {
                return false;
            }
 
            NativeMethods.DISPPARAMS dp = new NativeMethods.DISPPARAMS();
 
            // Required additional DISPPARAMS arguments for PROPERTYPUT cases. See IDispatch::Invoke
            // documentation for more info.
            int propertyPutDispId = NativeMethods.DISPID_PROPERTYPUT;
            if (flags == NativeMethods.DISPATCH_PROPERTYPUT || flags == NativeMethods.DISPATCH_PROPERTYPUTREF)
            {
                dp.cNamedArgs = 1;
 
                // Stack allocated variables are fixed (unmoveable), so no need to use a fixed
                // statement. The local variable should not get repurposed by the compiler in an
                // unsafe block as we're handing out a pointer to it. For comparison, see the DLR
                // code, CorRuntimeHelpers.cs, where the ConvertInt32ByrefToPtr function relies
                // on the same behavior as they take an IntPtr to a stack-allocated variable that
                // gets used by a DISPPARAMS in a similar way. They have a separate method likely
                // because expression trees can't deal with unsafe code, and they need to fix as
                // they use a by-ref argument which is considered movable (see C# spec, 18.3):
                //
                // public static unsafe IntPtr ConvertInt32ByrefToPtr(ref Int32 value) {
                //     fixed (Int32 *x = &value) {
                //         AssertByrefPointsToStack(new IntPtr(x));
                //         return new IntPtr(x);
                //     }
                // }
                dp.rgdispidNamedArgs = new IntPtr(&propertyPutDispId);
            }
 
            try
            {
                if (args != null)
                {
                    // Callers of this method might want to implement fallbacks that require the original
                    // arguments in the original order, maybe to feed it in to this method again. If we
                    // wouldn't clone the arguments array into a local copy, we'd be reversing again.
                    args = (object[])args.Clone();
 
                    // Reverse the argument order so that parameters read naturally after IDispatch.
                    // This code was initially ported from Microsoft, see Microsoft bug 187662.
                    Array.Reverse(args);
 
                    // Unwrap arguments that were already promoted to DynamicScriptObject. This can happen
                    // if the output of a script accessing method is fed in to the input of another one.
                    for (int i = 0; i < args.Length; i++)
                    {
                        var wrappedArg = args[i] as DynamicScriptObject;
                        if (wrappedArg != null)
                        {
                            args[i] = wrappedArg._scriptObject;
                        }
 
                        if (args[i] != null)
                        {
                            // Arrays of COM visible objects (in our definition of the word, see further) are
                            // not considered COM visible by themselves, so we take care of this case as well.
                            // Jagged arrays are not supported somehow, causing a SafeArrayTypeMismatchException
                            // in the call to GetNativeVariantForObject on ArrayToVARIANTVector called below.
                            // Multi-dimensional arrays turn out to work fine, so we don't opt out from those.
                            Type argType = args[i].GetType();
                            if (argType.IsArray)
                            {
                                argType = argType.GetElementType();
                            }
 
                            // Caveat: IsTypeVisibleFromCom evaluates false for COM object wrappers provided
                            // by the CLR. Therefore we also check for the IsCOMObject property. It also seems
                            // COM interop special-cases DateTime as it's not revealed to be visible by any
                            // of the first two checks below.
                            if (!Marshal.IsTypeVisibleFromCom(argType) && !argType.IsCOMObject && argType != typeof(DateTime))
                            {
                                throw new ArgumentException(SR.Get(SRID.NeedToBeComVisible));
                            }
                        }
                    }
 
                    dp.rgvarg = UnsafeNativeMethods.ArrayToVARIANTHelper.ArrayToVARIANTVector(args);
                    dp.cArgs = (uint)args.Length;
                }
 
                NativeMethods.EXCEPINFO exInfo = new NativeMethods.EXCEPINFO();
                HRESULT hr = InvokeOnScriptObject(dispid, flags, dp, exInfo, out result);
 
                if (hr.Failed)
                {
                    if (hr == HRESULT.DISP_E_MEMBERNOTFOUND)
                    {
                        return false;
                    }
 
                    // See KB article 247784, INFO: '80020101' Returned From Some ActiveX Scripting Methods.
                    // Internet Explorer returns this error when it has already reported a script error to the user
                    // through a dialog or by putting a message in the status bar (Page contains script error). We
                    // want consistency between browsers, so route this through the DISP_E_EXCEPTION case.
                    if (hr == HRESULT.SCRIPT_E_REPORTED)
                    {
                        exInfo.scode = hr.Code;
                        hr = HRESULT.DISP_E_EXCEPTION;
                    }
 
                    // We prefix exception messagages with "[memberName]" so that the target of the invocation can
                    // be found easily. This is useful beyond just seeing the call site in the debugger as dynamic
                    // calls lead to complicated call stacks with the DLR sliced in between the source and target.
                    // Also, for good or for bad, dynamic has the potential to encourage endless "dotting into", so
                    // it's preferrable to have our runtime resolution failure eloquating the target of the dynamic
                    // call. Unfortunately stock CLR exception types often don't offer a convenient spot to put
                    // this info in, so we resort to the Message property. Anyway, those exceptions are primarily
                    // meant to provide debugging convenience and should not be reported to the end-user in a well-
                    // tested application. Essentially all of this is to be conceived as "deferred compilation".
                    string member = "[" + (memberName ?? "(default)") + "]";
                    Exception comException = hr.GetException();
 
                    if (hr == HRESULT.DISP_E_EXCEPTION)
                    {
                        // We wrap script execution failures in TargetInvocationException to keep the API surface
                        // free of COMExceptions that reflect a mere implementation detail.
                        int errorCode = exInfo.scode != 0 ? exInfo.scode : exInfo.wCode;
                        hr = HRESULT.Make(true /* severity */, Facility.Dispatch, errorCode);
                        string message = member + " " + (exInfo.bstrDescription ?? string.Empty);
                        throw new TargetInvocationException(message, comException)
                        {
                            HelpLink = exInfo.bstrHelpFile,
                            Source = exInfo.bstrSource
                        };
                    }
                    else if (hr == HRESULT.DISP_E_BADPARAMCOUNT || hr == HRESULT.DISP_E_PARAMNOTOPTIONAL)
                    {
                        throw new TargetParameterCountException(member, comException);
                    }
                    else if (hr == HRESULT.DISP_E_OVERFLOW || hr == HRESULT.DISP_E_TYPEMISMATCH)
                    {
                        throw new ArgumentException(member, new InvalidCastException(comException.Message, hr.Code));
                    }
                    else
                    {
                        // Something really bad has happened; keeping the exception as-is.
                        throw comException;
                    }
                }
            }
            finally
            {
                if (dp.rgvarg != IntPtr.Zero)
                {
                    UnsafeNativeMethods.ArrayToVARIANTHelper.FreeVARIANTVector(dp.rgvarg, args.Length);
                }
            }
 
            return true;
        }
 
        #endregion Internal Methods
 
 
        //----------------------------------------------
        //
        // Private Methods
        //
        //----------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        /// Helper method to invoke a script member with the given name, flags and arguments.
        /// This overload always caches resolved DISPIDs.
        /// </summary>
        /// <param name="memberName">The name of the member to invoke.</param>
        /// <param name="flags">One of the DISPATCH_ flags for IDispatch calls.</param>
        /// <param name="args">Arguments passed to the call.</param>
        /// <returns>The result of the invocation.</returns>
        private object InvokeAndReturn(string memberName, int flags, object[] args)
        {
            return InvokeAndReturn(memberName, flags, true /* DISPID caching */, args);
        }
 
        /// <summary>
        /// Helper method to invoke a script member with the given name, flags and arguments.
        /// This overload allows control over the resolved DISPIDs caching behavior.
        /// </summary>
        /// <param name="memberName">The name of the member to invoke.</param>
        /// <param name="flags">One of the DISPATCH_ flags for IDispatch calls.</param>
        /// <param name="cacheDispId">true to enable caching of DISPIDs; false otherwise.</param>
        /// <param name="args">Arguments passed to the call.</param>
        /// <returns>The result of the invocation.</returns>
        private object InvokeAndReturn(string memberName, int flags, bool cacheDispId, object[] args)
        {
            object result;
            if (!TryFindMemberAndInvoke(memberName, flags, cacheDispId, args, out result))
            {
                if (flags == NativeMethods.DISPATCH_METHOD)
                    throw new MissingMethodException(this.ToString(), memberName);
                else
                    throw new MissingMemberException(this.ToString(), memberName);
            }
 
            return result;
        }
 
        /// <summary>
        /// Helper method to attempt invoking a script member with the given name, flags and arguments.
        /// Wraps the result value in a DynamicScriptObject if required.
        /// </summary>
        /// <param name="memberName">The name of the member to invoke.</param>
        /// <param name="flags">One of the DISPATCH_ flags for IDispatch calls.</param>
        /// <param name="cacheDispId">true to enable caching of DISPIDs; false otherwise.</param>
        /// <param name="args">Arguments passed to the call.</param>
        /// <param name="result">The result of the invocation, wrapped in DynamicScriptObject if required.</param>
        /// <returns>true if the member was found; false otherwise.</returns>
        /// <SecurityNote>
        ///     Critical - Calls the DynamicScriptObject constructor.
        ///     TreatAsSafe - Objects promoted into DynamicScriptObject are considered safe for scripting.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private bool TryFindMemberAndInvoke(string memberName, int flags, bool cacheDispId, object[] args, out object result)
        {
            if (!TryFindMemberAndInvokeNonWrapped(memberName, flags, cacheDispId, args, out result))
            {
                return false;
            }
 
            // Only wrap returned COM objects; if the object returns as a CLR object, we just return it.
            if (result != null && Marshal.IsComObject(result))
            {
                // Script objects implement IDispatch.
                result = new DynamicScriptObject((UnsafeNativeMethods.IDispatch)result);
            }
 
            return true;
        }
 
        /// <summary>
        /// Helper method to map a script member with the given name onto a DISPID.
        /// </summary>
        /// <param name="memberName">The name of the member to look up.</param>
        /// <param name="cacheDispId">true to enable caching of DISPIDs; false otherwise.</param>
        /// <param name="dispid">If the member was found, its DISPID; otherwise, default DISPID_VALUE.</param>
        /// <returns>true if the member was found; false if it wasn't (DISP_E_UNKNOWNNAME).</returns>
        /// <SecurityNote>
        ///     Critical - Invokes code on the critical _scriptObject field.
        ///     TreatAsSafe - Objects promoted into DynamicScriptObject are considered safe for scripting.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private bool TryGetDispIdForMember(string memberName, bool cacheDispId, out int dispid)
        {
            dispid = NativeMethods.DISPID_VALUE;
            if (!string.IsNullOrEmpty(memberName))
            {
                if (   !cacheDispId /* short-circuit lookup; will never get cached */
                    || !_dispIdCache.TryGetValue(memberName, out dispid))
                {
                    Guid guid = Guid.Empty;
 
                    string[] names   = new string[] { memberName };
                    int[]    dispids = new int[]    { NativeMethods.DISPID_UNKNOWN };
 
                    // Only the "member not found" case deserves special treatment. We leave it up to the
                    // caller to decide on the right treatment.
                    HRESULT hr = _scriptObject.GetIDsOfNames(ref guid, names, dispids.Length, Thread.CurrentThread.CurrentCulture.LCID, dispids);
                    if (hr == HRESULT.DISP_E_UNKNOWNNAME)
                    {
                        return false;
                    }
 
                    // Fatal unexpected exception here; it's fine to leak a COMException to the surface.
                    hr.ThrowIfFailed();
 
                    dispid = dispids[0];
 
                    if (cacheDispId)
                    {
                        _dispIdCache[memberName] = dispid;
                    }
                }
            }
 
            return true;
        }
 
        /// <SecurityNote>
        ///     Critical - Invokes code on the critical _scriptObject field.
        ///     TreatAsSafe - Objects promoted into DynamicScriptObject are considered safe for scripting.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private HRESULT InvokeOnScriptObject(int dispid, int flags, NativeMethods.DISPPARAMS dp, NativeMethods.EXCEPINFO exInfo, out object result)
        {
            // If we use reflection to call script code, we need to Assert for the UnmanagedCode permission. 
            // But it will be a security issue when the WPF app makes a framework object available to the 
            // hosted script via ObjectForScripting or as parameter of InvokeScript, and calls the framework
            // API that demands the UnmanagedCode permission. We do not want the demand to succeed. However, 
            // the stack walk will ignore the native frames and keeps going until it reaches the Assert.
            //
            // As an example, if a call to a script object causes an immediate callback before the initial
            // call returns, reentrancy occurs via COM's blocking message loop on outgoing calls:
            //
            //   [managed ComVisible object]
            //   [CLR COM interop]
            //   [COM runtime]
            //   ole32.dll!CCliModalLoop::BlockFn()
            //   ole32.dll!ModalLoop()
            //   [COM runtime]
            //   PresentationFramework!DynamicScriptObject::InvokeScript(...)
            //
            // That is why we switch to invoking the script via IDispatch with SUCS on the methods.
 
            if (_scriptObjectEx != null)
            {
                // This case takes care of IE hosting where the use of IDispatchEx is recommended by IE people
                // since the service provider object we can pass here is used by the browser to enforce cross-
                // zone scripting mitigations. See Dev10 work item 710325 for more information.
                return _scriptObjectEx.InvokeEx(dispid, Thread.CurrentThread.CurrentCulture.LCID, flags, dp, out result, exInfo, BrowserInteropHelper.HostHtmlDocumentServiceProvider);
            }
            else
            {
                Guid guid = Guid.Empty;
                return _scriptObject.Invoke(dispid, ref guid, Thread.CurrentThread.CurrentCulture.LCID, flags, dp, out result, exInfo, null);
            }
        }
 
        /// <summary>
        /// Helper function to get the IDispatch::Invoke invocation method for setting a property.
        /// </summary>
        /// <param name="value">Object to be assigned to the property.</param>
        /// <returns>DISPATCH_PROPERTYPUTREF or DISPATCH_PROPERTYPUT</returns>
        private static int GetPropertyPutMethod(object value)
        {
            // TFS DD Dev10 787708 - On the top-level script scope, setting a variable of a reference
            // type without using the DISPATCH_PROPERTYPUTREF flag doesn't work since it causes the
            // default member to be invoked as part of the conversion of the reference to a "value".
            // It seems this didn't affect DOM property setters where the IDispatch implementation is
            // more relaxed about the use of PUTREF versus PUT. This code is pretty much analog to
            // the DLR's COM binder's; see ndp\fx\src\Dynamic\System\Dynamic\ComBinderHelpers.cs for
            // further information.
 
            if (value == null)
            {
                return NativeMethods.DISPATCH_PROPERTYPUTREF;
            }
 
            Type type = value.GetType();
            if (   type.IsValueType
                || type.IsArray
                || type == typeof(string)
                || type == typeof(CurrencyWrapper)
                || type == typeof(DBNull)
                || type == typeof(Missing))
            {
                return NativeMethods.DISPATCH_PROPERTYPUT;
            }
            else
            {
                return NativeMethods.DISPATCH_PROPERTYPUTREF;
            }
        }
 
        #endregion Private Methods
 
 
        //----------------------------------------------
        //
        // Private Fields
        //
        //----------------------------------------------
 
        #region Private Fields
 
        /// <summary>
        /// Script object to invoke operations on through the "dynamic" feature.
        /// </summary>
        /// <SecurityNote>
        ///     Critical - Can be used to script against untrusted objects that are not safe for scripting.
        ///                If setting this field to an arbitrary value were possible, dynamic calls against
        ///                the DynamicScriptObject instance would dispatch against objects that could be
        ///                unsafe for scripting.
        /// </SecurityNote>
        [SecurityCritical]
        private UnsafeNativeMethods.IDispatch _scriptObject;
 
        /// <summary>
        /// Script object to invoke operations on through the "dynamic" feature.
        /// Used in the case of IE, where IDispatchEx is used to tighten security (see InvokeOnScriptObject).
        /// </summary>
        /// <SecurityNote>
        /// Same as for _scriptObject field.
        /// </SecurityNote>
        [SecurityCritical]
        private UnsafeNativeMethods.IDispatchEx _scriptObjectEx;
 
        /// <summary>
        /// Cache of DISPID values for members. Allows to speed up repeated calls.
        /// </summary>
        private Dictionary<string, int> _dispIdCache = new Dictionary<string, int>();
 
        #endregion Private Fields
    }
}