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; }
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)); }
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); }