#region Using Directives using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Reflection; using System.Security.Permissions; #endregion namespace TestDispatchUtility { /// /// Provides helper methods for working with COM IDispatch objects that have a registered type library. /// public static class DispatchUtility { #region Private Constants private const int S_OK = 0; //From WinError.h private const int LOCALE_SYSTEM_DEFAULT = 2 << 10; //From WinNT.h == 2048 == 0x800 #endregion #region Public Methods /// /// Gets whether the specified object implements IDispatch. /// /// An object to check. /// True if the object implements IDispatch. False otherwise. public static bool ImplementsIDispatch(object obj) { bool result = obj is IDispatchInfo; return result; } /// /// Gets a Type that can be used with reflection. /// /// An object that implements IDispatch. /// Whether an exception should be thrown if a Type can't be obtained. /// A .NET Type that can be used with reflection. /// If doesn't implement IDispatch. [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static Type GetType(object obj, bool throwIfNotFound) { RequireReference(obj, "obj"); Type result = GetType((IDispatchInfo)obj, throwIfNotFound); return result; } /// /// Tries to get the DISPID for the requested member name. /// /// An object that implements IDispatch. /// The name of a member to lookup. /// If the method returns true, this holds the DISPID on output. /// If the method returns false, this value should be ignored. /// True if the member was found and resolved to a DISPID. False otherwise. /// If doesn't implement IDispatch. [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static bool TryGetDispId(object obj, string name, out int dispId) { RequireReference(obj, "obj"); bool result = TryGetDispId((IDispatchInfo)obj, name, out dispId); return result; } /// /// Invokes a member by DISPID. /// /// An object that implements IDispatch. /// The DISPID of a member. This can be obtained using /// . /// The arguments to pass to the member. /// The member's return value. /// /// This can invoke a method or a property get accessor. /// public static object Invoke(object obj, int dispId, object[] args) { string memberName = "[DispId=" + dispId + "]"; object result = Invoke(obj, memberName, args); return result; } /// /// Invokes a member by name. /// /// An object. /// The name of the member to invoke. /// The arguments to pass to the member. /// The member's return value. /// /// This can invoke a method or a property get accessor. /// public static object Invoke(object obj, string memberName, object[] args) { RequireReference(obj, "obj"); Type type = obj.GetType(); object result = type.InvokeMember(memberName, BindingFlags.InvokeMethod | BindingFlags.GetProperty, null, obj, args, null); return result; } #endregion #region Private Methods /// /// Requires that the value is non-null. /// /// The type of the value. /// The value to check. /// The name of the value. private static void RequireReference(T value, string name) where T : class { if (value == null) { throw new ArgumentNullException(name); } } /// /// Gets a Type that can be used with reflection. /// /// An object that implements IDispatch. /// Whether an exception should be thrown if a Type can't be obtained. /// A .NET Type that can be used with reflection. private static Type GetType(IDispatchInfo dispatch, bool throwIfNotFound) { RequireReference(dispatch, "dispatch"); Type result = null; int typeInfoCount; int hr = dispatch.GetTypeInfoCount(out typeInfoCount); if (hr == S_OK && typeInfoCount > 0) { // Type info isn't usually culture-aware for IDispatch, so we might as well pass // the default locale instead of looking up the current thread's LCID each time // (via CultureInfo.CurrentCulture.LCID). dispatch.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out result); } if (result == null && throwIfNotFound) { // If the GetTypeInfoCount called failed, throw an exception for that. Marshal.ThrowExceptionForHR(hr); // Otherwise, throw the same exception that Type.GetType would throw. throw new TypeLoadException(); } return result; } /// /// Tries to get the DISPID for the requested member name. /// /// An object that implements IDispatch. /// The name of a member to lookup. /// If the method returns true, this holds the DISPID on output. /// If the method returns false, this value should be ignored. /// True if the member was found and resolved to a DISPID. False otherwise. private static bool TryGetDispId(IDispatchInfo dispatch, string name, out int dispId) { RequireReference(dispatch, "dispatch"); RequireReference(name, "name"); bool result = false; // Members names aren't usually culture-aware for IDispatch, so we might as well // pass the default locale instead of looking up the current thread's LCID each time // (via CultureInfo.CurrentCulture.LCID). Guid iidNull = Guid.Empty; int hr = dispatch.GetDispId(ref iidNull, ref name, 1, LOCALE_SYSTEM_DEFAULT, out dispId); const int DISP_E_UNKNOWNNAME = unchecked((int)0x80020006); //From WinError.h const int DISPID_UNKNOWN = -1; //From OAIdl.idl if (hr == S_OK) { result = true; } else if (hr == DISP_E_UNKNOWNNAME && dispId == DISPID_UNKNOWN) { // This is the only supported "error" case because it means IDispatch // is saying it doesn't know the member we asked about. result = false; } else { // The other documented result codes are all errors. Marshal.ThrowExceptionForHR(hr); } return result; } #endregion #region Private Interfaces /// /// A partial declaration of IDispatch used to lookup Type information and DISPIDs. /// /// /// This interface only declares the first three methods of IDispatch. It omits the /// fourth method (Invoke) because there are already plenty of ways to do dynamic /// invocation in .NET. But the first three methods provide dynamic type metadata /// discovery, which .NET doesn't provide normally if you have a System.__ComObject /// RCW instead of a strongly-typed RCW. /// /// Note: The original declaration of IDispatch is in OAIdl.idl. /// [ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("00020400-0000-0000-C000-000000000046")] private interface IDispatchInfo { /// /// Gets the number of Types that the object provides (0 or 1). /// /// Returns 0 or 1 for the number of Types provided by . /// /// http://msdn.microsoft.com/en-us/library/da876d53-cb8a-465c-a43e-c0eb272e2a12(VS.85) /// [PreserveSig] int GetTypeInfoCount(out int typeInfoCount); /// /// Gets the Type information for an object if returned 1. /// /// Must be 0. /// Typically, LOCALE_SYSTEM_DEFAULT (2048). /// Returns the object's Type information. /// /// http://msdn.microsoft.com/en-us/library/cc1ec9aa-6c40-4e70-819c-a7c6dd6b8c99(VS.85) /// void GetTypeInfo(int typeInfoIndex, int lcid, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(System.Runtime.InteropServices.CustomMarshalers.TypeToTypeInfoMarshaler))] out Type typeInfo); /// /// Gets the DISPID of the specified member name. /// /// Must be IID_NULL. Pass a copy of Guid.Empty. /// The name of the member to look up. /// Must be 1. /// Typically, LOCALE_SYSTEM_DEFAULT (2048). /// If a member with the requested /// is found, this returns its DISPID and the method's return value is 0. /// If the method returns a non-zero value, then this parameter's output value is /// undefined. /// Zero for success. Non-zero for failure. /// /// http://msdn.microsoft.com/en-us/library/6f6cf233-3481-436e-8d6a-51f93bf91619(VS.85) /// [PreserveSig] int GetDispId(ref Guid riid, ref string name, int nameCount, int lcid, out int dispId); // NOTE: The real IDispatch also has an Invoke method next, but we don't need it. // We can invoke methods using .NET's Type.InvokeMember method with the special // [DISPID=n] syntax for member "names", or we can get a .NET Type using GetTypeInfo // and invoke methods on that through reflection. // Type.InvokeMember: http://msdn.microsoft.com/en-us/library/de3dhzwy.aspx } #endregion } }