/Microsoft.Dynamic/IDispatchComObject.cs
C# | 611 lines | 409 code | 96 blank | 106 comment | 109 complexity | 66f7e79d941a0bee2c6d846e8572a9a0 MD5 | raw file
- /* ****************************************************************************
- *
- * Copyright (c) Microsoft Corporation.
- *
- * This source code is subject to terms and conditions of the Microsoft Public License. A
- * copy of the license can be found in the License.html file at the root of this distribution. If
- * you cannot locate the Microsoft Public License, please send an email to
- * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
- * by the terms of the Microsoft Public License.
- *
- * You must not remove this notice, or any other, from this software.
- *
- *
- * ***************************************************************************/
- using System; using Microsoft;
-
-
- #if !SILVERLIGHT // ComObject
-
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Globalization;
- #if CODEPLEX_40
- using System.Linq.Expressions;
- #else
- using Microsoft.Linq.Expressions;
- #endif
- using System.Reflection;
- using System.Runtime.InteropServices;
- using ComTypes = System.Runtime.InteropServices.ComTypes;
-
- #if CODEPLEX_40
- namespace System.Dynamic {
- #else
- namespace Microsoft.Scripting {
- #endif
-
- /// <summary>
- /// An object that implements IDispatch
- ///
- /// This currently has the following issues:
- /// 1. If we prefer ComObjectWithTypeInfo over IDispatchComObject, then we will often not
- /// IDispatchComObject since implementations of IDispatch often rely on a registered type library.
- /// If we prefer IDispatchComObject over ComObjectWithTypeInfo, users get a non-ideal experience.
- /// 2. IDispatch cannot distinguish between properties and methods with 0 arguments (and non-0
- /// default arguments?). So obj.foo() is ambiguous as it could mean invoking method foo,
- /// or it could mean invoking the function pointer returned by property foo.
- /// We are attempting to find whether we need to call a method or a property by examining
- /// the ITypeInfo associated with the IDispatch. ITypeInfo tell's use what parameters the method
- /// expects, is it a method or a property, what is the default property of the object, how to
- /// create an enumerator for collections etc.
- /// 3. IronPython processes the signature and converts ref arguments into return values.
- /// However, since the signature of a DispMethod is not available beforehand, this conversion
- /// is not possible. There could be other signature conversions that may be affected. How does
- /// VB6 deal with ref arguments and IDispatch?
- ///
- /// We also support events for IDispatch objects:
- /// Background:
- /// COM objects support events through a mechanism known as Connect Points.
- /// Connection Points are separate objects created off the actual COM
- /// object (this is to prevent circular references between event sink
- /// and event source). When clients want to sink events generated by
- /// COM object they would implement callback interfaces (aka source
- /// interfaces) and hand it over (advise) to the Connection Point.
- ///
- /// Implementation details:
- /// When IDispatchComObject.TryGetMember request is received we first check
- /// whether the requested member is a property or a method. If this check
- /// fails we will try to determine whether an event is requested. To do
- /// so we will do the following set of steps:
- /// 1. Verify the COM object implements IConnectionPointContainer
- /// 2. Attempt to find COM object's coclass's description
- /// a. Query the object for IProvideClassInfo interface. Go to 3, if found
- /// b. From object's IDispatch retrieve primary interface description
- /// c. Scan coclasses declared in object's type library.
- /// d. Find coclass implementing this particular primary interface
- /// 3. Scan coclass for all its source interfaces.
- /// 4. Check whether to any of the methods on the source interfaces matches
- /// the request name
- ///
- /// Once we determine that TryGetMember requests an event we will return
- /// an instance of BoundDispEvent class. This class has InPlaceAdd and
- /// InPlaceSubtract operators defined. Calling InPlaceAdd operator will:
- /// 1. An instance of ComEventSinksContainer class is created (unless
- /// RCW already had one). This instance is hanged off the RCW in attempt
- /// to bind the lifetime of event sinks to the lifetime of the RCW itself,
- /// meaning event sink will be collected once the RCW is collected (this
- /// is the same way event sinks lifetime is controlled by PIAs).
- /// Notice: ComEventSinksContainer contains a Finalizer which will go and
- /// unadvise all event sinks.
- /// Notice: ComEventSinksContainer is a list of ComEventSink objects.
- /// 2. Unless we have already created a ComEventSink for the required
- /// source interface, we will create and advise a new ComEventSink. Each
- /// ComEventSink implements a single source interface that COM object
- /// supports.
- /// 3. ComEventSink contains a map between method DISPIDs to the
- /// multicast delegate that will be invoked when the event is raised.
- /// 4. ComEventSink implements IReflect interface which is exposed as
- /// custom IDispatch to COM consumers. This allows us to intercept calls
- /// to IDispatch.Invoke and apply custom logic - in particular we will
- /// just find and invoke the multicast delegate corresponding to the invoked
- /// dispid.
- /// </summary>
-
- internal sealed class IDispatchComObject : ComObject, IDynamicMetaObjectProvider {
-
- private readonly IDispatch _dispatchObject;
- private ComTypeDesc _comTypeDesc;
- private static readonly Dictionary<Guid, ComTypeDesc> _CacheComTypeDesc = new Dictionary<Guid, ComTypeDesc>();
-
- internal IDispatchComObject(IDispatch rcw)
- : base(rcw) {
- _dispatchObject = rcw;
- }
-
- public override string ToString() {
- EnsureScanDefinedMethods();
-
- string typeName = this._comTypeDesc.TypeName;
- if (String.IsNullOrEmpty(typeName))
- typeName = "IDispatch";
-
- return String.Format(CultureInfo.CurrentCulture, "{0} ({1})", RuntimeCallableWrapper.ToString(), typeName);
- }
-
- public ComTypeDesc ComTypeDesc {
- get {
- EnsureScanDefinedMethods();
- return _comTypeDesc;
- }
- }
-
- public IDispatch DispatchObject {
- get {
- return _dispatchObject;
- }
- }
-
- private static int GetIDsOfNames(IDispatch dispatch, string name, out int dispId) {
- int[] dispIds = new int[1];
- Guid emtpyRiid = Guid.Empty;
- int hresult = dispatch.TryGetIDsOfNames(
- ref emtpyRiid,
- new string[] { name },
- 1,
- 0,
- dispIds);
-
- dispId = dispIds[0];
- return hresult;
- }
-
- static int Invoke(IDispatch dispatch, int memberDispId, out object result) {
- Guid emtpyRiid = Guid.Empty;
- ComTypes.DISPPARAMS dispParams = new ComTypes.DISPPARAMS();
- ComTypes.EXCEPINFO excepInfo = new ComTypes.EXCEPINFO();
- uint argErr;
- int hresult = dispatch.TryInvoke(
- memberDispId,
- ref emtpyRiid,
- 0,
- ComTypes.INVOKEKIND.INVOKE_PROPERTYGET,
- ref dispParams,
- out result,
- out excepInfo,
- out argErr);
-
- return hresult;
- }
-
- internal bool TryGetGetItem(out ComMethodDesc value) {
- ComMethodDesc methodDesc = _comTypeDesc.GetItem;
- if (methodDesc != null) {
- value = methodDesc;
- return true;
- }
-
- return SlowTryGetGetItem(out value);
- }
-
- private bool SlowTryGetGetItem(out ComMethodDesc value) {
- EnsureScanDefinedMethods();
-
- ComMethodDesc methodDesc = _comTypeDesc.GetItem;
-
- // Without type information, we really don't know whether or not we have a property getter.
- if (methodDesc == null) {
- string name = "[PROPERTYGET, DISPID(0)]";
-
- _comTypeDesc.EnsureGetItem(new ComMethodDesc(name, ComDispIds.DISPID_VALUE, ComTypes.INVOKEKIND.INVOKE_PROPERTYGET));
- methodDesc = _comTypeDesc.GetItem;
- }
-
- value = methodDesc;
- return true;
- }
-
- internal bool TryGetSetItem(out ComMethodDesc value) {
- ComMethodDesc methodDesc = _comTypeDesc.SetItem;
- if (methodDesc != null) {
- value = methodDesc;
- return true;
- }
-
- return SlowTryGetSetItem(out value);
- }
-
- private bool SlowTryGetSetItem(out ComMethodDesc value) {
- EnsureScanDefinedMethods();
-
- ComMethodDesc methodDesc = _comTypeDesc.SetItem;
-
- // Without type information, we really don't know whether or not we have a property setter.
- if (methodDesc == null) {
- string name = "[PROPERTYPUT, DISPID(0)]";
-
- _comTypeDesc.EnsureSetItem(new ComMethodDesc(name, ComDispIds.DISPID_VALUE, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT));
- methodDesc = _comTypeDesc.SetItem;
- }
-
- value = methodDesc;
- return true;
- }
-
- internal bool TryGetMemberMethod(string name, out ComMethodDesc method) {
- EnsureScanDefinedMethods();
- return _comTypeDesc.TryGetFunc(name, out method);
- }
-
- internal bool TryGetMemberEvent(string name, out ComEventDesc @event) {
- EnsureScanDefinedEvents();
- return _comTypeDesc.TryGetEvent(name, out @event);
- }
-
- internal bool TryGetMemberMethodExplicit(string name, out ComMethodDesc method) {
- EnsureScanDefinedMethods();
-
- int dispId;
- int hresult = GetIDsOfNames(_dispatchObject, name, out dispId);
-
- if (hresult == ComHresults.S_OK) {
- ComMethodDesc cmd = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_FUNC);
- _comTypeDesc.AddFunc(name, cmd);
- method = cmd;
- return true;
- } else if (hresult == ComHresults.DISP_E_UNKNOWNNAME) {
- method = null;
- return false;
- } else {
- throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{1:X})", hresult));
- }
- }
-
- internal bool TryGetPropertySetterExplicit(string name, out ComMethodDesc method, Type limitType, bool holdsNull) {
- EnsureScanDefinedMethods();
-
- int dispId;
- int hresult = GetIDsOfNames(_dispatchObject, name, out dispId);
-
- if (hresult == ComHresults.S_OK) {
- // we do not know whether we have put or putref here
- // and we will not guess and pretend we found both.
- ComMethodDesc put = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT);
- _comTypeDesc.AddPut(name, put);
-
- ComMethodDesc putref = new ComMethodDesc(name, dispId, ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF);
- _comTypeDesc.AddPutRef(name, putref);
-
- if (ComBinderHelpers.PreferPut(limitType, holdsNull)) {
- method = put;
- } else {
- method = putref;
- }
- return true;
- } else if (hresult == ComHresults.DISP_E_UNKNOWNNAME) {
- method = null;
- return false;
- } else {
- throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{1:X})", hresult));
- }
- }
-
- internal override IList<string> GetMemberNames(bool dataOnly) {
- EnsureScanDefinedMethods();
- EnsureScanDefinedEvents();
-
- return ComTypeDesc.GetMemberNames(dataOnly);
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
- internal override IList<KeyValuePair<string, object>> GetMembers(IEnumerable<string> names) {
- if (names == null) {
- names = GetMemberNames(true);
- }
-
- Type comType = RuntimeCallableWrapper.GetType();
-
- var members = new List<KeyValuePair<string, object>>();
- foreach (string name in names) {
- if (name == null) {
- continue;
- }
-
- ComMethodDesc method;
- if (ComTypeDesc.TryGetFunc(name, out method) && method.IsDataMember) {
- try {
- object value = comType.InvokeMember(
- method.Name,
- BindingFlags.GetProperty,
- null,
- RuntimeCallableWrapper,
- new object[0],
- CultureInfo.InvariantCulture
- );
- members.Add(new KeyValuePair<string, object>(method.Name, value));
-
- //evaluation failed for some reason. pass exception out
- } catch (Exception ex) {
- members.Add(new KeyValuePair<string, object>(method.Name, ex));
- }
- }
- }
-
- return members.ToArray();
- }
-
- DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) {
- EnsureScanDefinedMethods();
- return new IDispatchMetaObject(parameter, this);
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
- private static void GetFuncDescForDescIndex(ComTypes.ITypeInfo typeInfo, int funcIndex, out ComTypes.FUNCDESC funcDesc, out IntPtr funcDescHandle) {
- IntPtr pFuncDesc = IntPtr.Zero;
- typeInfo.GetFuncDesc(funcIndex, out pFuncDesc);
-
- // GetFuncDesc should never return null, this is just to be safe
- if (pFuncDesc == IntPtr.Zero) {
- throw Error.CannotRetrieveTypeInformation();
- }
-
- funcDesc = (ComTypes.FUNCDESC)Marshal.PtrToStructure(pFuncDesc, typeof(ComTypes.FUNCDESC));
- funcDescHandle = pFuncDesc;
- }
-
- private void EnsureScanDefinedEvents() {
- // _comTypeDesc.Events is null if we have not yet attempted
- // to scan the object for events.
- if (_comTypeDesc != null && _comTypeDesc.Events != null) {
- return;
- }
-
- // check type info in the type descriptions cache
- ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(_dispatchObject, true);
- if (typeInfo == null) {
- _comTypeDesc = ComTypeDesc.CreateEmptyTypeDesc();
- return;
- }
-
- ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);
-
- if (_comTypeDesc == null) {
- lock (_CacheComTypeDesc) {
- if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) == true &&
- _comTypeDesc.Events != null) {
- return;
- }
- }
- }
-
- ComTypeDesc typeDesc = ComTypeDesc.FromITypeInfo(typeInfo, typeAttr);
-
- ComTypes.ITypeInfo classTypeInfo = null;
- Dictionary<string, ComEventDesc> events = null;
-
- var cpc = RuntimeCallableWrapper as ComTypes.IConnectionPointContainer;
- if (cpc == null) {
- // No ICPC - this object does not support events
- events = ComTypeDesc.EmptyEvents;
- } else if ((classTypeInfo = GetCoClassTypeInfo(this.RuntimeCallableWrapper, typeInfo)) == null) {
- // no class info found - this object may support events
- // but we could not discover those
- events = ComTypeDesc.EmptyEvents;
- } else {
- events = new Dictionary<string, ComEventDesc>();
-
- ComTypes.TYPEATTR classTypeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(classTypeInfo);
- for (int i = 0; i < classTypeAttr.cImplTypes; i++) {
- int hRefType;
- classTypeInfo.GetRefTypeOfImplType(i, out hRefType);
-
- ComTypes.ITypeInfo interfaceTypeInfo;
- classTypeInfo.GetRefTypeInfo(hRefType, out interfaceTypeInfo);
-
- ComTypes.IMPLTYPEFLAGS flags;
- classTypeInfo.GetImplTypeFlags(i, out flags);
- if ((flags & ComTypes.IMPLTYPEFLAGS.IMPLTYPEFLAG_FSOURCE) != 0) {
- ScanSourceInterface(interfaceTypeInfo, ref events);
- }
- }
-
- if (events.Count == 0) {
- events = ComTypeDesc.EmptyEvents;
- }
- }
-
- lock (_CacheComTypeDesc) {
- ComTypeDesc cachedTypeDesc;
- if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out cachedTypeDesc)) {
- _comTypeDesc = cachedTypeDesc;
- } else {
- _comTypeDesc = typeDesc;
- _CacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc);
- }
- _comTypeDesc.Events = events;
- }
- }
-
- private static void ScanSourceInterface(ComTypes.ITypeInfo sourceTypeInfo, ref Dictionary<string, ComEventDesc> events) {
- ComTypes.TYPEATTR sourceTypeAttribute = ComRuntimeHelpers.GetTypeAttrForTypeInfo(sourceTypeInfo);
-
- for (int index = 0; index < sourceTypeAttribute.cFuncs; index++) {
- IntPtr funcDescHandleToRelease = IntPtr.Zero;
-
- try {
- ComTypes.FUNCDESC funcDesc;
- GetFuncDescForDescIndex(sourceTypeInfo, index, out funcDesc, out funcDescHandleToRelease);
-
- // we are not interested in hidden or restricted functions for now.
- if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FHIDDEN) != 0) {
- continue;
- }
- if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0) {
- continue;
- }
-
- string name = ComRuntimeHelpers.GetNameOfMethod(sourceTypeInfo, funcDesc.memid);
- name = name.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
-
- // Sometimes coclass has multiple source interfaces. Usually this is caused by
- // adding new events and putting them on new interfaces while keeping the
- // old interfaces around. This may cause name collisioning which we are
- // resolving by keeping only the first event with the same name.
- if (events.ContainsKey(name) == false) {
- ComEventDesc eventDesc = new ComEventDesc();
- eventDesc.dispid = funcDesc.memid;
- eventDesc.sourceIID = sourceTypeAttribute.guid;
- events.Add(name, eventDesc);
- }
- } finally {
- if (funcDescHandleToRelease != IntPtr.Zero) {
- sourceTypeInfo.ReleaseFuncDesc(funcDescHandleToRelease);
- }
- }
- }
- }
-
- private static ComTypes.ITypeInfo GetCoClassTypeInfo(object rcw, ComTypes.ITypeInfo typeInfo) {
- Debug.Assert(typeInfo != null);
-
- IProvideClassInfo provideClassInfo = rcw as IProvideClassInfo;
- if (provideClassInfo != null) {
- IntPtr typeInfoPtr = IntPtr.Zero;
- try {
- provideClassInfo.GetClassInfo(out typeInfoPtr);
- if (typeInfoPtr != IntPtr.Zero) {
- return Marshal.GetObjectForIUnknown(typeInfoPtr) as ComTypes.ITypeInfo;
- }
- } finally {
- if (typeInfoPtr != IntPtr.Zero) {
- Marshal.Release(typeInfoPtr);
- }
- }
- }
-
- // retrieving class information through IPCI has failed -
- // we can try scanning the typelib to find the coclass
-
- ComTypes.ITypeLib typeLib;
- int typeInfoIndex;
- typeInfo.GetContainingTypeLib(out typeLib, out typeInfoIndex);
- string typeName = ComRuntimeHelpers.GetNameOfType(typeInfo);
-
- ComTypeLibDesc typeLibDesc = ComTypeLibDesc.GetFromTypeLib(typeLib);
- ComTypeClassDesc coclassDesc = typeLibDesc.GetCoClassForInterface(typeName);
- if (coclassDesc == null) {
- return null;
- }
-
- ComTypes.ITypeInfo typeInfoCoClass;
- Guid coclassGuid = coclassDesc.Guid;
- typeLib.GetTypeInfoOfGuid(ref coclassGuid, out typeInfoCoClass);
- return typeInfoCoClass;
- }
-
- private void EnsureScanDefinedMethods() {
- if (_comTypeDesc != null && _comTypeDesc.Funcs != null) {
- return;
- }
-
- ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(_dispatchObject, true);
- if (typeInfo == null) {
- _comTypeDesc = ComTypeDesc.CreateEmptyTypeDesc();
- return;
- }
-
- ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo);
-
- if (_comTypeDesc == null) {
- lock (_CacheComTypeDesc) {
- if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) == true &&
- _comTypeDesc.Funcs != null) {
- return;
- }
- }
- }
-
- ComTypeDesc typeDesc = ComTypeDesc.FromITypeInfo(typeInfo, typeAttr);
-
- ComMethodDesc getItem = null;
- ComMethodDesc setItem = null;
- Hashtable funcs = new Hashtable(typeAttr.cFuncs);
- Hashtable puts = new Hashtable();
- Hashtable putrefs = new Hashtable();
-
- for (int definedFuncIndex = 0; definedFuncIndex < typeAttr.cFuncs; definedFuncIndex++) {
- IntPtr funcDescHandleToRelease = IntPtr.Zero;
-
- try {
- ComTypes.FUNCDESC funcDesc;
- GetFuncDescForDescIndex(typeInfo, definedFuncIndex, out funcDesc, out funcDescHandleToRelease);
-
- if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0) {
- // This function is not meant for the script user to use.
- continue;
- }
-
- ComMethodDesc method = new ComMethodDesc(typeInfo, funcDesc);
- string name = method.Name.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
-
- if ((funcDesc.invkind & ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT) != 0) {
- puts.Add(name, method);
-
- // for the special dispId == 0, we need to store
- // the method descriptor for the Do(SetItem) binder.
- if (method.DispId == ComDispIds.DISPID_VALUE && setItem == null) {
- setItem = method;
- }
- continue;
- }
- if ((funcDesc.invkind & ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF) != 0) {
- putrefs.Add(name, method);
- // for the special dispId == 0, we need to store
- // the method descriptor for the Do(SetItem) binder.
- if (method.DispId == ComDispIds.DISPID_VALUE && setItem == null) {
- setItem = method;
- }
- continue;
- }
-
- if (funcDesc.memid == ComDispIds.DISPID_NEWENUM) {
- funcs.Add("GETENUMERATOR", method);
- continue;
- }
-
- funcs.Add(name, method);
-
- // for the special dispId == 0, we need to store the method descriptor
- // for the Do(GetItem) binder.
- if (funcDesc.memid == ComDispIds.DISPID_VALUE) {
- getItem = method;
- }
- } finally {
- if (funcDescHandleToRelease != IntPtr.Zero) {
- typeInfo.ReleaseFuncDesc(funcDescHandleToRelease);
- }
- }
- }
-
- lock (_CacheComTypeDesc) {
- ComTypeDesc cachedTypeDesc;
- if (_CacheComTypeDesc.TryGetValue(typeAttr.guid, out cachedTypeDesc)) {
- _comTypeDesc = cachedTypeDesc;
- } else {
- _comTypeDesc = typeDesc;
- _CacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc);
- }
- _comTypeDesc.Funcs = funcs;
- _comTypeDesc.Puts = puts;
- _comTypeDesc.PutRefs = putrefs;
- _comTypeDesc.EnsureGetItem(getItem);
- _comTypeDesc.EnsureSetItem(setItem);
- }
- }
-
- internal bool TryGetPropertySetter(string name, out ComMethodDesc method, Type limitType, bool holdsNull) {
- EnsureScanDefinedMethods();
-
- if (ComBinderHelpers.PreferPut(limitType, holdsNull)) {
- return _comTypeDesc.TryGetPut(name, out method) ||
- _comTypeDesc.TryGetPutRef(name, out method);
- } else {
- return _comTypeDesc.TryGetPutRef(name, out method) ||
- _comTypeDesc.TryGetPut(name, out method);
- }
- }
- }
- }
-
- #endif