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;
        }
Example #2
0
        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.
                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));
            }
        }
        private HRESULT InvokeOnScriptObject(int dispid, int flags, NativeMethods.DISPPARAMS dp, NativeMethods.EXCEPINFO exInfo, out object result)
        {
            if (this._scriptObjectEx != null)
            {
                return(this._scriptObjectEx.InvokeEx(dispid, Thread.CurrentThread.CurrentCulture.LCID, flags, dp, out result, exInfo, BrowserInteropHelper.HostHtmlDocumentServiceProvider));
            }
            Guid empty = Guid.Empty;

            return(this._scriptObject.Invoke(dispid, ref empty, Thread.CurrentThread.CurrentCulture.LCID, flags, dp, out result, exInfo, null));
        }
Example #4
0
        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 WinForms, see WinForms 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 (
#if NETFX
                                !Marshal.IsTypeVisibleFromCom(argType)
#else
                                !MarshalLocal.IsTypeVisibleFromCom(argType)
#endif
                                && !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);
        }
        internal unsafe bool TryFindMemberAndInvokeNonWrapped(string memberName, int flags, bool cacheDispId, object[] args, out object result)
        {
            result = null;
            int dispid;

            if (!this.TryGetDispIdForMember(memberName, cacheDispId, out dispid))
            {
                return(false);
            }
            NativeMethods.DISPPARAMS dispparams = new NativeMethods.DISPPARAMS();
            int num = -3;

            if (flags == 4 || flags == 8)
            {
                dispparams.cNamedArgs        = 1U;
                dispparams.rgdispidNamedArgs = new IntPtr((void *)(&num));
            }
            try
            {
                if (args != null)
                {
                    args = (object[])args.Clone();
                    Array.Reverse(args);
                    for (int i = 0; i < args.Length; i++)
                    {
                        DynamicScriptObject dynamicScriptObject = args[i] as DynamicScriptObject;
                        if (dynamicScriptObject != null)
                        {
                            args[i] = dynamicScriptObject._scriptObject;
                        }
                        if (args[i] != null)
                        {
                            Type type = args[i].GetType();
                            if (type.IsArray)
                            {
                                type = type.GetElementType();
                            }
                            if (!Marshal.IsTypeVisibleFromCom(type) && !type.IsCOMObject && type != typeof(DateTime))
                            {
                                throw new ArgumentException(SR.Get("NeedToBeComVisible"));
                            }
                        }
                    }
                    dispparams.rgvarg = UnsafeNativeMethods.ArrayToVARIANTHelper.ArrayToVARIANTVector(args);
                    dispparams.cArgs  = (uint)args.Length;
                }
                NativeMethods.EXCEPINFO excepinfo = new NativeMethods.EXCEPINFO();
                HRESULT hrLeft = this.InvokeOnScriptObject(dispid, flags, dispparams, excepinfo, out result);
                if (hrLeft.Failed)
                {
                    if (hrLeft == HRESULT.DISP_E_MEMBERNOTFOUND)
                    {
                        return(false);
                    }
                    if (hrLeft == HRESULT.SCRIPT_E_REPORTED)
                    {
                        excepinfo.scode = hrLeft.Code;
                        hrLeft          = HRESULT.DISP_E_EXCEPTION;
                    }
                    string    text      = "[" + (memberName ?? "(default)") + "]";
                    Exception exception = hrLeft.GetException();
                    if (hrLeft == HRESULT.DISP_E_EXCEPTION)
                    {
                        int code = (excepinfo.scode != 0) ? excepinfo.scode : ((int)excepinfo.wCode);
                        hrLeft = HRESULT.Make(true, Facility.Dispatch, code);
                        string message = text + " " + (excepinfo.bstrDescription ?? string.Empty);
                        throw new TargetInvocationException(message, exception)
                              {
                                  HelpLink = excepinfo.bstrHelpFile,
                                  Source   = excepinfo.bstrSource
                              };
                    }
                    if (hrLeft == HRESULT.DISP_E_BADPARAMCOUNT || hrLeft == HRESULT.DISP_E_PARAMNOTOPTIONAL)
                    {
                        throw new TargetParameterCountException(text, exception);
                    }
                    if (hrLeft == HRESULT.DISP_E_OVERFLOW || hrLeft == HRESULT.DISP_E_TYPEMISMATCH)
                    {
                        throw new ArgumentException(text, new InvalidCastException(exception.Message, hrLeft.Code));
                    }
                    throw exception;
                }
            }
            finally
            {
                if (dispparams.rgvarg != IntPtr.Zero)
                {
                    UnsafeNativeMethods.ArrayToVARIANTHelper.FreeVARIANTVector(dispparams.rgvarg, args.Length);
                }
            }
            return(true);
        }