DynamicScriptObject.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Interop / DynamicScriptObject.cs / 1305600 / DynamicScriptObject.cs

                            //------------------------------------------------------------------------------ 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
//
// 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    [....]      Created
//  06/30/09    [....]      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 
{
    /// 
    /// Enables scripting support against the HTML DOM for XBAPs using the DLR.
    ///  
    /// 
    /// 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. 
    /// 
    public sealed class DynamicScriptObject : DynamicObject 
    {
        //----------------------------------------------
        //
        // Constructors 
        //
        //---------------------------------------------- 
 
        #region Constructor
 
        /// 
        /// Wraps the given object in a script object for "dynamic" access.
        /// 
        /// Object to be wrapped. 
        /// 
        ///     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.
        /// 
        [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
 
        /// 
        /// Calls a script method. Corresponds to methods calls in the front-end language.
        /// 
        /// The binder provided by the call site. 
        /// The arguments to be used for the invocation.
        /// The result of the invocation. 
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted. 
        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; 
        }
 
        /// 
        /// Gets a member from script. Corresponds to property getter syntax in the front-end language.
        /// 
        /// The binder provided by the call site. 
        /// The result of the invocation.
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted. 
        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; 
        } 

        ///  
        /// Sets a member in script. Corresponds to property setter syntax in the front-end language.
        /// 
        /// The binder provided by the call site.
        /// The value to set. 
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted.
        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; 
        } 

        ///  
        /// Gets an indexed value from script. Corresponds to indexer getter syntax in the front-end language.
        /// 
        /// The binder provided by the call site.
        /// The indexes to be used. 
        /// The result of the invocation.
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted. 
        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; 
        }
 
        /// 
        /// Sets a member in script, through an indexer. Corresponds to indexer setter syntax in the front-end language.
        /// 
        /// The binder provided by the call site. 
        /// The indexes to be used.
        /// The value to set. 
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted. 
        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;
        }

        ///  
        /// Calls the default script method. Corresponds to delegate calling syntax in the front-end language.
        ///  
        /// The binder provided by the call site. 
        /// The arguments to be used for the invocation.
        /// The result of the invocation. 
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted.
        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;
        }

        ///  
        /// Provides a string representation of the wrapped script object.
        ///  
        /// String representation of the wrapped script object, using the toString method or default member on the script object. 
        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

        /// 
        /// Gets the IDispatch script object wrapped by the DynamicScriptObject. 
        /// 
        ///  
        ///     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.
        /// 
        internal UnsafeNativeMethods.IDispatch ScriptObject 
        {
            [SecurityCritical, SecurityTreatAsSafe] 
            get 
            {
                return _scriptObject; 
            }
        }

        #endregion Internal Properties 

 
        //---------------------------------------------- 
        //
        // Internal Methods 
        //
        //----------------------------------------------

        #region Internal Methods 

        ///  
        /// Helper method to attempt invoking a script member with the given name, flags and arguments. 
        /// 
        /// The name of the member to invoke. 
        /// One of the DISPATCH_ flags for IDispatch calls.
        /// true to enable caching of DISPIDs; false otherwise.
        /// Arguments passed to the call.
        /// The raw (not wrapped in DynamicScriptObject) result of the invocation. 
        /// true if the member was found; false otherwise.
        ///  
        ///     Critical - Unpacks the critical _scriptObject field on DynamicScriptObject arguments. 
        ///     TreatAsSafe - Objects returned from script are considered safe for scripting.
        ///  
        [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 [....], see [....] 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 

        ///  
        /// Helper method to invoke a script member with the given name, flags and arguments. 
        /// This overload always caches resolved DISPIDs.
        ///  
        /// The name of the member to invoke.
        /// One of the DISPATCH_ flags for IDispatch calls.
        /// Arguments passed to the call.
        /// The result of the invocation. 
        private object InvokeAndReturn(string memberName, int flags, object[] args)
        { 
            return InvokeAndReturn(memberName, flags, true /* DISPID caching */, args); 
        }
 
        /// 
        /// Helper method to invoke a script member with the given name, flags and arguments.
        /// This overload allows control over the resolved DISPIDs caching behavior.
        ///  
        /// The name of the member to invoke.
        /// One of the DISPATCH_ flags for IDispatch calls. 
        /// true to enable caching of DISPIDs; false otherwise. 
        /// Arguments passed to the call.
        /// The result of the invocation. 
        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;
        } 

        ///  
        /// Helper method to attempt invoking a script member with the given name, flags and arguments. 
        /// Wraps the result value in a DynamicScriptObject if required.
        ///  
        /// The name of the member to invoke.
        /// One of the DISPATCH_ flags for IDispatch calls.
        /// true to enable caching of DISPIDs; false otherwise.
        /// Arguments passed to the call. 
        /// The result of the invocation, wrapped in DynamicScriptObject if required.
        /// true if the member was found; false otherwise. 
        ///  
        ///     Critical - Calls the DynamicScriptObject constructor.
        ///     TreatAsSafe - Objects promoted into DynamicScriptObject are considered safe for scripting. 
        /// 
        [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;
        } 

        /// 
        /// Helper method to map a script member with the given name onto a DISPID.
        ///  
        /// The name of the member to look up.
        /// true to enable caching of DISPIDs; false otherwise. 
        /// If the member was found, its DISPID; otherwise, default DISPID_VALUE. 
        /// true if the member was found; false if it wasn't (DISP_E_UNKNOWNNAME).
        ///  
        ///     Critical - Invokes code on the critical _scriptObject field.
        ///     TreatAsSafe - Objects promoted into DynamicScriptObject are considered safe for scripting.
        /// 
        [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;
        }
 
        /// 
        ///     Critical - Invokes code on the critical _scriptObject field. 
        ///     TreatAsSafe - Objects promoted into DynamicScriptObject are considered safe for scripting. 
        /// 
        [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);
            } 
        } 

        ///  
        /// Helper function to get the IDispatch::Invoke invocation method for setting a property.
        /// 
        /// Object to be assigned to the property.
        /// DISPATCH_PROPERTYPUTREF or DISPATCH_PROPERTYPUT 
        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 

        /// 
        /// Script object to invoke operations on through the "dynamic" feature.
        ///  
        /// 
        ///     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. 
        /// 
        [SecurityCritical]
        private UnsafeNativeMethods.IDispatch _scriptObject;
 
        /// 
        /// 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). 
        /// 
        ///  
        /// Same as for _scriptObject field.
        /// 
        [SecurityCritical]
        private UnsafeNativeMethods.IDispatchEx _scriptObjectEx; 

        ///  
        /// Cache of DISPID values for members. Allows to speed up repeated calls. 
        /// 
        private Dictionary _dispIdCache = new Dictionary(); 

        #endregion Private Fields
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------ 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
//
// 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    [....]      Created
//  06/30/09    [....]      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 
{
    /// 
    /// Enables scripting support against the HTML DOM for XBAPs using the DLR.
    ///  
    /// 
    /// 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. 
    /// 
    public sealed class DynamicScriptObject : DynamicObject 
    {
        //----------------------------------------------
        //
        // Constructors 
        //
        //---------------------------------------------- 
 
        #region Constructor
 
        /// 
        /// Wraps the given object in a script object for "dynamic" access.
        /// 
        /// Object to be wrapped. 
        /// 
        ///     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.
        /// 
        [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
 
        /// 
        /// Calls a script method. Corresponds to methods calls in the front-end language.
        /// 
        /// The binder provided by the call site. 
        /// The arguments to be used for the invocation.
        /// The result of the invocation. 
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted. 
        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; 
        }
 
        /// 
        /// Gets a member from script. Corresponds to property getter syntax in the front-end language.
        /// 
        /// The binder provided by the call site. 
        /// The result of the invocation.
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted. 
        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; 
        } 

        ///  
        /// Sets a member in script. Corresponds to property setter syntax in the front-end language.
        /// 
        /// The binder provided by the call site.
        /// The value to set. 
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted.
        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; 
        } 

        ///  
        /// Gets an indexed value from script. Corresponds to indexer getter syntax in the front-end language.
        /// 
        /// The binder provided by the call site.
        /// The indexes to be used. 
        /// The result of the invocation.
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted. 
        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; 
        }
 
        /// 
        /// Sets a member in script, through an indexer. Corresponds to indexer setter syntax in the front-end language.
        /// 
        /// The binder provided by the call site. 
        /// The indexes to be used.
        /// The value to set. 
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted. 
        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;
        }

        ///  
        /// Calls the default script method. Corresponds to delegate calling syntax in the front-end language.
        ///  
        /// The binder provided by the call site. 
        /// The arguments to be used for the invocation.
        /// The result of the invocation. 
        /// true - We never defer behavior to the call site, and throw if invalid access is attempted.
        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;
        }

        ///  
        /// Provides a string representation of the wrapped script object.
        ///  
        /// String representation of the wrapped script object, using the toString method or default member on the script object. 
        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

        /// 
        /// Gets the IDispatch script object wrapped by the DynamicScriptObject. 
        /// 
        ///  
        ///     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.
        /// 
        internal UnsafeNativeMethods.IDispatch ScriptObject 
        {
            [SecurityCritical, SecurityTreatAsSafe] 
            get 
            {
                return _scriptObject; 
            }
        }

        #endregion Internal Properties 

 
        //---------------------------------------------- 
        //
        // Internal Methods 
        //
        //----------------------------------------------

        #region Internal Methods 

        ///  
        /// Helper method to attempt invoking a script member with the given name, flags and arguments. 
        /// 
        /// The name of the member to invoke. 
        /// One of the DISPATCH_ flags for IDispatch calls.
        /// true to enable caching of DISPIDs; false otherwise.
        /// Arguments passed to the call.
        /// The raw (not wrapped in DynamicScriptObject) result of the invocation. 
        /// true if the member was found; false otherwise.
        ///  
        ///     Critical - Unpacks the critical _scriptObject field on DynamicScriptObject arguments. 
        ///     TreatAsSafe - Objects returned from script are considered safe for scripting.
        ///  
        [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 [....], see [....] 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 

        ///  
        /// Helper method to invoke a script member with the given name, flags and arguments. 
        /// This overload always caches resolved DISPIDs.
        ///  
        /// The name of the member to invoke.
        /// One of the DISPATCH_ flags for IDispatch calls.
        /// Arguments passed to the call.
        /// The result of the invocation. 
        private object InvokeAndReturn(string memberName, int flags, object[] args)
        { 
            return InvokeAndReturn(memberName, flags, true /* DISPID caching */, args); 
        }
 
        /// 
        /// Helper method to invoke a script member with the given name, flags and arguments.
        /// This overload allows control over the resolved DISPIDs caching behavior.
        ///  
        /// The name of the member to invoke.
        /// One of the DISPATCH_ flags for IDispatch calls. 
        /// true to enable caching of DISPIDs; false otherwise. 
        /// Arguments passed to the call.
        /// The result of the invocation. 
        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;
        } 

        ///  
        /// Helper method to attempt invoking a script member with the given name, flags and arguments. 
        /// Wraps the result value in a DynamicScriptObject if required.
        ///  
        /// The name of the member to invoke.
        /// One of the DISPATCH_ flags for IDispatch calls.
        /// true to enable caching of DISPIDs; false otherwise.
        /// Arguments passed to the call. 
        /// The result of the invocation, wrapped in DynamicScriptObject if required.
        /// true if the member was found; false otherwise. 
        ///  
        ///     Critical - Calls the DynamicScriptObject constructor.
        ///     TreatAsSafe - Objects promoted into DynamicScriptObject are considered safe for scripting. 
        /// 
        [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;
        } 

        /// 
        /// Helper method to map a script member with the given name onto a DISPID.
        ///  
        /// The name of the member to look up.
        /// true to enable caching of DISPIDs; false otherwise. 
        /// If the member was found, its DISPID; otherwise, default DISPID_VALUE. 
        /// true if the member was found; false if it wasn't (DISP_E_UNKNOWNNAME).
        ///  
        ///     Critical - Invokes code on the critical _scriptObject field.
        ///     TreatAsSafe - Objects promoted into DynamicScriptObject are considered safe for scripting.
        /// 
        [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;
        }
 
        /// 
        ///     Critical - Invokes code on the critical _scriptObject field. 
        ///     TreatAsSafe - Objects promoted into DynamicScriptObject are considered safe for scripting. 
        /// 
        [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);
            } 
        } 

        ///  
        /// Helper function to get the IDispatch::Invoke invocation method for setting a property.
        /// 
        /// Object to be assigned to the property.
        /// DISPATCH_PROPERTYPUTREF or DISPATCH_PROPERTYPUT 
        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 

        /// 
        /// Script object to invoke operations on through the "dynamic" feature.
        ///  
        /// 
        ///     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. 
        /// 
        [SecurityCritical]
        private UnsafeNativeMethods.IDispatch _scriptObject;
 
        /// 
        /// 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). 
        /// 
        ///  
        /// Same as for _scriptObject field.
        /// 
        [SecurityCritical]
        private UnsafeNativeMethods.IDispatchEx _scriptObjectEx; 

        ///  
        /// Cache of DISPID values for members. Allows to speed up repeated calls. 
        /// 
        private Dictionary _dispIdCache = new Dictionary(); 

        #endregion Private Fields
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK