//
you're reading...
.NET/COM/ActiveX Interop, Type Information

Obtain Type Information of IDispatch-Based COM Objects from Managed Code.

1. Introduction.

1.1 Twice this year, someone from the MSDN forum attempted to determine the names of methods exported from a COM object by using Type.GetMethods().

1.2 This is possible if the type library of the COM object has been imported into the managed code project using TLBIMP.EXE (see section 2 below).

1.3 When a COM object is created dynamically using Activator.CreateInstance() without having its type first imported by the type library importer into a managed code project, this information cannot be acquired using Type.GetMethods().

1.4 However, it is still possible to obtain the method names of a COM object (and other metadata) whose type library has not been referenced. This blog examines the options available for accomplishing this end.

2. Obtaining COM Type Information from the Runtime Callable Wrapper Class Generated by the Type Library Importer.

2.1 When a COM type library is referenced inside a managed code project, the type library importer converts a COM coclass into a managed wrapper class with the same name as the original coclass but with the word “Class” appended.

2.2 This class represents the Runtime-Callable Wrapper Class. For example, a COM coclass named MyCOMClass will have a RCW class named “MyCOMClassClass”.

2.3 Each member of each interface the coclass implements is added as a class member of the managed wrapper class. Hence all properties and methods the coclass implements can be accessed directly from the wrapper class without the need to first cast to a specific interface.

2.4 The Type Library Importer even prepares a default public constructor prepared for the wrapper class. It is inside this constructor that the COM class is instantiated with a call to CoCreateInstance().

2.5 Also, all events of all source interfaces supported by the coclass will be added as events of the RCW class.

2.6 For this reason the type information of a COM coclass, when represented by a .NET wrapper class, can be accessed by using the various methods of the Type class, e.g. Type.GetMethods(), Type.GetEvents(), Type.GetProperties().

2.7 For example, let’s say we have a COM type library named MyCOMLibrary.tlb referenced from a C# project. This type library contains the following IDL definitions :

// MyCOMLibrary.idl : IDL source for MyCOMLibrary
//

// This file will be processed by the MIDL tool to
// produce the type library (MyCOMLibrary.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";

[
	uuid(301D35F1-7B28-437B-B1FD-11F04A8BEE62),
	version(1.0),
	helpstring("MyCOMLibrary 1.0 Type Library")
]
library MyCOMLibraryLib
{
	importlib("stdole2.tlb");

	[
		object,
		uuid(B7577D5D-5D9B-4B86-A033-2D591B794318),
		dual,
		nonextensible,
		helpstring("IMyCOMClass Interface"),
		pointer_default(unique)
	]
	interface IMyCOMClass : IDispatch
	{
		[id(1), helpstring("method Method01. Additional method information.")] HRESULT Method01([in] BSTR strParam);
		[propget, id(2), helpstring("property LongProperty")] HRESULT LongProperty([out, retval] LONG* pVal);
		[propput, id(2), helpstring("property LongProperty")] HRESULT LongProperty([in] LONG newVal);
	};

	[
		uuid(BE52701E-E5AC-4803-8620-C05814E55E42),
		helpstring("_IMyCOMClassEvents Interface")
	]
	dispinterface _IMyCOMClassEvents
	{
		properties:
		methods:
			[id(1), helpstring("method Event01. Additional event information.")] HRESULT Event01([in] BSTR strParam);
	};
	[
		uuid(532A5F8B-AD73-4D0D-A8AF-D72C4F73084A),
		helpstring("MyCOMClass Class")
	]
	coclass MyCOMClass
	{
		[default] interface IMyCOMClass;
		[default, source] dispinterface _IMyCOMClassEvents;
	};
};

2.8 Via the Object Browser, we can see the following information :

Note the following :

  • The MyCOMClassClass class is defined.
  • A public default constructor is generated.
  • Method01() is listed as a member of this class.
  • LongProperty is listed as a property of this class.
  • The Event01 event method is also listed.

2.8 We can thus run the following code :

Type type = typeof(MyCOMClassClass);
MethodInfo[] method_info_array = type.GetMethods();

and the following items will be included for “method_info_array” :

A MethodInfo instance for “Method01” is included as part of the array. However, note that managed methods that are not based on the COM interfaces of the MyCOMClass coclass are also included, e.g. ToString(), GetType(), etc.

3. Obtaining Type Information Dynamically from a COM Object.

3.1 If, however, the type library of a COM coclass is not referenced and the COM coclass is instantiated dynamically using Activator.CreateInstance(), we cannot use the Type class to obtain type information on the object.

3.2 If we were to run code like the following :

Type type = Type.GetTypeFromProgID("MyCOMLibrary.MyCOMClass");
MethodInfo[] method_info_array = type.GetMethods();
EventInfo[] event_info_array = type.GetEvents();
PropertyInfo[] property_info_array = type.GetProperties();

Now, because “type” will equal “System.__ComObject”, the items of method_info_array array after being returned from type.GetMethods() will contain only the managed methods of the System.__ComObject type.

The event_info_array array after being returned from type.GetEvents() is empty and the property_info_array array after being returned from type.GetProperties() is also empty.

3.2 The type metadata for the COM object must be obtained dynamically. How do we perform this ? As it turns out, the IDispatch interface itself provides a doorway to the type information of the object by way of its GetTypeInfo() method :

HRESULT GetTypeInfo
(
  unsigned int iTInfo,
  LCID lcid,
  ITypeInfo FAR* FAR* ppTInfo
);

3.3 What we need to do is to be able to obtain the actual COM IDispatch interface from the object and then invoke its GetTypeInfo() method. This call will return to us a pointer to an ITypeInfo interface which is the gateway to obtaining various type information of an IDispatch-based COM object.

3.4 Note however, that if a COM object does not implement the IDispatch interface, then it is not possible to obtain the type information on the object other than by directly accessing its type library. Hence the discussion that follow will work only for IDispatch-based objects.

3.5 Beginning from the next section, I will provide an in-depth discussion on how this can be accomplished. Example codes will be given.

4. The IDispatch Interface in C#.

4.1 The COM IDispatch interface will be required to be used in the managed code that will be presented. This is listed below :

[ComImport()]
[Guid("00020400-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDispatch
{
    [PreserveSig]
    int GetTypeInfoCount(out int Count);

    [PreserveSig]
    int GetTypeInfo
        (
            [MarshalAs(UnmanagedType.U4)] int iTInfo,
            [MarshalAs(UnmanagedType.U4)] int lcid,
            out System.Runtime.InteropServices.ComTypes.ITypeInfo typeInfo
        );

    [PreserveSig]
    int GetIDsOfNames
        (
            ref Guid riid,
            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)]
            string[] rgsNames,
            int cNames,
            int lcid,
            [MarshalAs(UnmanagedType.LPArray)] int[] rgDispId
        );

    [PreserveSig]
    int Invoke
        (
            int dispIdMember,
            ref Guid riid,
            uint lcid,
            ushort wFlags,
            ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams,
            out object pVarResult,
            ref System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo,
            IntPtr[] pArgErr
        );
}

Please note the following important points about the above declaration :

  • The ComImportAttribute is important in order to indicate to the interop marshaler that the IDispatch interface is a pre-defined external COM interface.
  • The GuidAttribute is vital when a low-level QueryInterface() call is made to a COM object in order to obtain its IDispatch interface.
  • Note that we use the System.Runtime.InteropServices.ComTypes.ITypeInfo type for the 3rd out parameter for the GetTypeInfo() method.
  • We do not need to define this interface manually. It has already been defined in the System.Runtime.InteropServices.ComTypes namespace.

4.2 Now that we have the COM IDispatch interface defined properly, we next need to know how to obtain the IDispatch interface from a COM object. This can be done very simply by performing a cast on the object :

Type type = Type.GetTypeFromProgID("MyCOMLibrary.MyCOMClass");
Object com_obj = Activator.CreateInstance(type);
IDispatch pIDispatch = (IDispatch)com_obj;

 If the target object of the cast does not implement the IDispatch interface, then a System.InvalidCastException will be thrown.

5. Accessing Method Information of a COM Object.

5.1 Once we are able to obtain the IDispatch interface pointer from a COM object, the next thing to do is to call its GetTypeInfo() method to obtain a pointer to the System.Runtime.InteropServices.ComTypes.ITypeInfo interface. The following is an example :

// Obtain the ITypeInfo interface from the object via its
// IDispatch.GetTypeInfo() method.
System.Runtime.InteropServices.ComTypes.ITypeInfo pTypeInfo = null;
// Call the IDispatch.GetTypeInfo() to obtain an ITypeInfo interface
// pointer from the com_obj.
// Note that the first parameter must be 0 in order to
// obtain the Type Info of the current object.
pDispatch.GetTypeInfo
(
  0,
  0,
  out pTypeInfo
);

5.2 The COM ITypeInfo interface is very widely useful. As a demonstration of its effectiveness, I shall demonstrate how to obtain the names and helpstring documentation of the methods of a COM object.

5.3 The following is a summary of my approach to this :

  • I shall write a class with a static function that will return an array of structures.
  • Each structure will contain the names, helpstring documentation of a COM object.
  • Each structure will additionally indicate the type of the method : i.e. whether it is a regular method or a property getter function or a property setter function or a property set by reference function.

5.4 The structure mentioned above is listed as follows :

public enum MethodType
{
    Method = 0,
    Property_Getter = 1,
    Property_Putter = 2,
    Property_PutRef = 3
}

public struct MethodInformation
{
    public string m_strName;
    public string m_strDocumentation;
    public MethodType m_method_type;
}

5.5 The listing below contains code for the class which is named COMIDispatch :

class COMIDispatch
{
    // This static function returns an array containing
    // information of all public methods of a COM object.
    // Note that the COM object must implement the IDispatch
    // interface in order to be usable in this function.
    public static MethodInformation[] GetMethodInformation(object com_obj)
    {
        IDispatch pDispatch = null;

        try
        {
            // Obtain the COM IDispatch interface from the input com_obj object.
            // Low-level-wise this causes a QueryInterface() to be performed on
            // com_obj to obtain the IDispatch interface from it.
            pDispatch = (IDispatch)com_obj;
        }
        catch (System.InvalidCastException ex)
        {
            // This means that the input com_obj
            // does not support the IDispatch
            // interface.
            return null;
        }

        // Obtain the ITypeInfo interface from the object via its
        // IDispatch.GetTypeInfo() method.
        System.Runtime.InteropServices.ComTypes.ITypeInfo pTypeInfo = null;
        // Call the IDispatch.GetTypeInfo() to obtain an ITypeInfo interface
        // pointer from the com_obj.
        // Note that the first parameter must be 0 in order to
        // obtain the Type Info of the current object.
        pDispatch.GetTypeInfo
        (
            0,
            0,
            out pTypeInfo
        );

        // If for some reason we are not able to obtain the
        // ITypeInfo interface from the IDispatch interface
        // of the COM object, we return immediately.
        if (pTypeInfo == null)
        {
            return null;
        }

        // Get the TYPEATTR (type attributes) of the object
        // via its ITypeInfo interface.
        IntPtr pTypeAttr = IntPtr.Zero;
        System.Runtime.InteropServices.ComTypes.TYPEATTR typeattr;
        // The TYPEATTR is returned via an IntPtr.
        pTypeInfo.GetTypeAttr(out pTypeAttr);
        // We must convert the IntPtr into the TYPEATTR structure
        // defined in the System.Runtime.InteropServices.ComTypes
        // namespace.
        typeattr = (System.Runtime.InteropServices.ComTypes.TYPEATTR)Marshal.PtrToStructure
            (
              pTypeAttr,
              typeof(System.Runtime.InteropServices.ComTypes.TYPEATTR)
            );
        // Release the resources related to the obtaining of the
        // COM TYPEATTR structure from an ITypeInfo interface.
        // From now onwards, we will only work with the
        // System.Runtime.InteropServices.ComTypes.TYPEATTR
        // structure.
        pTypeInfo.ReleaseTypeAttr(pTypeAttr);
        pTypeAttr = IntPtr.Zero;

        // The TYPEATTR.guid member indicates the default interface implemented
        // by the COM object.
        Guid defaultInterfaceGuid = typeattr.guid;

        MethodInformation[] method_information_array = new MethodInformation[typeattr.cFuncs];
        // The TYPEATTR.cFuncs member indicates the total number of methods
        // that the current COM object implements.
        for (int i = 0; i < (typeattr.cFuncs); i++)
        {
            // We loop through the number of methods.
            System.Runtime.InteropServices.ComTypes.FUNCDESC funcdesc;
            IntPtr pFuncDesc = IntPtr.Zero;
            string strName;
            string strDocumentation;
            int iHelpContext;
            string strHelpFile;

            // During each loop, we use the ITypeInfo.GetFuncDesc()
            // method to obtain a FUNCDESC structure which describes
            // the current method indexed by "i".
            pTypeInfo.GetFuncDesc(i, out pFuncDesc);
            // The FUNCDESC structure is returned as an IntPtr.
            // We need to convert it into a FUNCDESC structure
            // defined in the System.Runtime.InteropServices.ComTypes
            // namespace.
            funcdesc = (System.Runtime.InteropServices.ComTypes.FUNCDESC)Marshal.PtrToStructure
                (
                  pFuncDesc,
                  typeof(System.Runtime.InteropServices.ComTypes.FUNCDESC)
                );
            // The FUNCDESC.memid contains the member id of the current function
            // in the Type Info of the object.
            // Use the ITypeInfo.GetDocumentation() with reference to memid
            // to obtain information for this function.
            pTypeInfo.GetDocumentation
                (
                  funcdesc.memid,
                  out strName,
                  out strDocumentation,
                  out iHelpContext,
                  out strHelpFile
                );
            // Fill up the appropriate method_information_array element
            // with field information.
            method_information_array[i].m_strName = strName;
            method_information_array[i].m_strDocumentation = strDocumentation;

            if (funcdesc.invkind == System.Runtime.InteropServices.ComTypes.INVOKEKIND.INVOKE_FUNC)
            {
                method_information_array[i].m_method_type = MethodType.Method;
            }
            else if (funcdesc.invkind == System.Runtime.InteropServices.ComTypes.INVOKEKIND.INVOKE_PROPERTYGET)
            {
                method_information_array[i].m_method_type = MethodType.Property_Getter;
            }
            else if (funcdesc.invkind == System.Runtime.InteropServices.ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT)
            {
                method_information_array[i].m_method_type = MethodType.Property_Putter;
            }
            else if (funcdesc.invkind == System.Runtime.InteropServices.ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF)
            {
                method_information_array[i].m_method_type = MethodType.Property_PutRef;
            }

            // The ITypeInfo.ReleaseFuncDesc() must be called
            // to release the (unmanaged) memory of the FUNCDESC
            // returned in pFuncDesc (an IntPtr).
            pTypeInfo.ReleaseFuncDesc(pFuncDesc);
            pFuncDesc = IntPtr.Zero;
        }

        return method_information_array;
    }
}

Here are the pertinent points of the code above :

  • The input object com_obj is first tested to see if an IDispatch interface can be extracted from it.
  • If not, the System.InvalidCastException is caught and we exit the function.
  • If an IDispatch interface pointer can be obtained, its GetTypeInfo() is called and a pointer to an ITypeInfo interface is obtained.
  • Using the newly obtained ITypeInfo interface of com_obj, we get the TYPEATTR (type attributes) of the object via the ITypeInfo::GetTypeAttr() method.
  • This will return to us an IntPtr which points to an unmanaged TYPEATTR structure.
  • We must convert this unmanaged TYPEATTR structure into its managed counterpart using Marshal.PtrToStructure().
  • After this is done, we must release the resources related to the obtaining of the unmanaged TYPEATTR structure by using the ITypeInfo::ReleaseTypeAttr() method.
  • Because the COM type the attributes of which we have obtained is a COM coclass which implements the IDispatch interface, the cFuncs member field of the TYPEATTR structure is relevant and this field contains the number of functions that the coclass implements.
  • Or rather, to be more accurate, the number of functions that the IDispatch-based interface of the coclass implements.
  • Looking at the “MyCOMLibrary.MyCOMClass” coclass, we know that its IDispatch interface contains the 4 standard IDispatch methods (i.e. GetTypeInfoCount(), GetTypeInfo(), GetIDsOfNames() and Invoke()), the 3 standard IUnknown methods (AddRef(), Release() and QueryInterface()) and finally the methods of the IMyCOMClass interface (Method01(), get_LongProperty() and put_LongProperty()).
  • This totals 10 methods and so typeattr.cFuncs equals 10. We use this number to prepare an array of MethodInformation structures.
  • We loop through this number of iterations and during each loop, we use ITypeInfo.GetFuncDesc() to obtain a FUNCDESC structure which describes the current method indexed by “i”.
  • The FUNCDESC structure is returned as an IntPtr. It is an unmanaged structure. We need to convert it into its managed equivalent which is defined in the System.Runtime.InteropServices.ComTypes namespace. We do this by calling Marshal.PtrToStructure().
  • The FUNCDESC.memid field contains the member id of the current function in the Type Info of the object. We use the ITypeInfo.GetDocumentation() method with reference to this memid to obtain information for this function.
  • Here is where the name of the function and its helpstring documentation are obtained.
  • The FUNCDESC.invkind field contains a value from the INVOKEKIND enumeration and this value indicates to us what type of function we are dealing with (a method, a property setter, getter, etc).
  • All these information are stored inside an instance of the MethodInformation array.
  • At the end of the function, the MethodInformation array is returned.

5.6 The following is a test code that can be used to run COMIDispatch.GetMethodInformation() :

Type type = Type.GetTypeFromProgID("MyCOMLibrary.MyCOMClass");
Object objMyCOMClass = Activator.CreateInstance(type);

MethodInformation[] method_information_array = COMIDispatch.GetMethodInformation(objMyCOMClass);

5.7 After COMIDispatch.GetMethodInformation() runs, method_information_array will contain the the following elements :

method_information_array[0].m_method_type = Method	
method_information_array[0].m_strDocumentation = null	
method_information_array[0].m_strName = "QueryInterface"	

method_information_array[1].m_method_type = Method
method_information_array[1].m_strDocumentation = null
method_information_array[1].m_strName = "AddRef"

method_information_array[2].m_method_type = Method
method_information_array[2].m_strDocumentation = null
method_information_array[2].m_strName = "Release"

method_information_array[3].m_method_type = Method
method_information_array[3].m_strDocumentation = null
method_information_array[3].m_strName = "GetTypeInfoCount"

method_information_array[4].m_method_type = Method
method_information_array[4].m_strDocumentation = null
method_information_array[4].m_strName = "GetTypeInfo"

method_information_array[5].m_method_type = Method
method_information_array[5].m_strDocumentation = null
method_information_array[5].m_strName = "GetIDsOfNames"

method_information_array[6].m_method_type = Method
method_information_array[6].m_strDocumentation = null
method_information_array[6].m_strName = "Invoke"

method_information_array[7].m_method_type = Method
method_information_array[7].m_strDocumentation = "method Method01. Additional method information."
method_information_array[7].m_strName = "Method01"

method_information_array[8].m_method_type = Property_Getter
method_information_array[8].m_strDocumentation = "property LongProperty"
method_information_array[8].m_strName = "LongProperty"

method_information_array[9].m_method_type = Property_Putter
method_information_array[9].m_strDocumentation = "property LongProperty"
method_information_array[9].m_strName = "LongProperty"

6. In Conclusion.

6.1 I had initially wanted to write the COMIDispatch class such that it will encapsulate the IDispatch interface pointer of a COM object in managed code. COMIDispatch was to internally obtain the ITypeInfo interface from the IDispatch interface and then process it so that type information like method names, helpstring documentation and parameter types can be easily and optimally obtained.

6.2 However, in the course of my research I decided that it would be better that I simply write one static function for the COMIDispatch class that shows how to use the ITypeInfo interface to obtain a COM object’s method metadata. I believe this woule better serve the reader’s understanding of ITypeInfo and the various support structures involved.

6.3 Once understanding of this function is achieved, the reader can further embellish and/or optimize the COMIDispatch class if he/she is suitably motivated.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

Trackbacks/Pingbacks

  1. Pingback: Reflection with IDispatch-based COM objects | Guray Cemberci - January 11, 2015

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: