//
you're reading...
.NET Interop, Programming Issues/Tips, Type Information

Using the TypeLibConverter to Customize Type Library Creation from an Assembly Part 1.

1. Introduction.

1.1 Creation of COM type libraries from an assembly with COM-visible constructs (e.g. classes, interfaces, etc) is usually performed using REGASM.EXE or TLBEXP.EXE.

1.2 However, these tools provide standardized assembly to type library type transformations which cannot be customized.

1.3 To customize type library creation, the .NET framework provides the TypeLibConverter class.

1.4 In this blog, I will demonstrate how customization of the output type library can be achieved using this class.

2. Customization of Type Library Creation.

2.1 The assembly to type library conversion process can be performed programmatically by the TypeLibConverter class.

2.2 More specifically, by the TypeLibConverter.ConvertAssemblyToTypeLib() function :

public Object ConvertAssemblyToTypeLib(
	Assembly assembly,
	string strTypeLibName,
	TypeLibExporterFlags flags,
	ITypeLibExporterNotifySink notifySink
);

2.3 At first glance, one might get the impression that the way to customize the type library creation process is by providing an object that implements the ITypeLibExporterNotifySink interface and setting it as the parameter to the ConvertAssemblyToTypeLib() function. But this is not true.

2.4 The actual way that the assembly to type library conversion process can be customized is by :

  • Calling the ConvertAssemblyToTypeLib() function and retaining the returned object.
  • Casting the returned object into useful COM interfaces (e.g. ICreateTypeLib2, ITypeLib).
  • Using these implemented interfaces to modify the in-memory type library object.
  • Saving the in-memory type library object into an actual type library file.

2.5 The customization process cannot be made generic and must be tailored to the specific requirements of a development team.

2.6 The best way to demonstrate the customization process is via an example. This is presented in the next section.

2.7 Due to the length of this treatise, I will break it into 2 parts. In this part one, I give some general background which pertains to the example customization program as well as an exposition into one of its two major code components.

3. Customization Example – Some Background About Type Library Transformations for .NET Property Accessors.

3.1 Starting from this section, I present an example use of the TypeLibConverter.ConvertAssemblyToTypeLib() function to perform type library customization.

3.2 Listed below is a C# program from which a class library assembly (CSTestInterfaceClass.dll) will be produced :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace CSTestInterfaceClass
{
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [Guid("2BC5C974-E70B-48c4-AA1F-B4EA15E957F7")]
    public interface ITestInterface
    {
        [DispId(0)]
        Object MyObject
        {
            set;
            get;
        }
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [Guid("B3CC27E0-FFFB-42c9-84E0-DC61874F73AC")]
    [ProgId("CSTestInterfaceClass.TestClass")]
    public class TestClass : ITestInterface
    {
        public TestClass()
        {
        }

        public Object MyObject
        {
            set
            {
                m_obj = value;
            }

            get
            {
                return m_obj;
            }
        }

        private Object m_obj = null;
    }
}
  • The code declares a COM-visible interface ITestInterface.
  • ITestInterface contains only one property MyObject of type Object accessible via a pair of get and set accessors.
  • Besides ITestInterface, a COM-visible class named TestClass is also declared.
  • TestClass implements the ITestInterface interface.

3.3 When the code has been compiled, we use REGASM.EXE to both register the COM-visible entities contained in the assembly as well as produce a TLB file :

regasm CSTestInterfaceClass.dll /tlb:CSTestInterfaceClass.tlb

3.4 Using OLEVIEW.EXE, we can view the output type library :

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: CSTestInterfaceClass.tlb

[
  uuid(22688ED0-4658-4526-BBBB-23917C416DD4),
  version(1.0)
]
library CSTestInterfaceClass
{
    // TLib :     // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
    importlib("mscorlib.tlb");
    // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    dispinterface ITestInterface;

    [
      uuid(2BC5C974-E70B-48C4-AA1F-B4EA15E957F7),
      version(1.0),
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "CSTestInterfaceClass.ITestInterface")    

    ]
    dispinterface ITestInterface {
        properties:
        methods:
            [id(00000000), propputref]
            void MyObject([in] VARIANT rhs);
            [id(00000000), propget]
            VARIANT MyObject();
    };

    [
      uuid(B3CC27E0-FFFB-42C9-84E0-DC61874F73AC),
      version(1.0),
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "CSTestInterfaceClass.TestClass")
    ]
    coclass TestClass {
        interface _Object;
        [default] dispinterface ITestInterface;
    };
};

 3.5 Notice that the MyObject property of ITestInterface has 2 types of accessors :

  • a propget accessor.
  • a propputref accessor.

3.6 There is nothing unusual about the propget accessor nor the propputref accessor. What seems to be amiss is the fact that there is no propput accessor.

3.7 This is the default output by the .NET TypeLibConverter which exports reference type .NET properties (with the exception of .NET strings and arrays) as propputref accessors.

3.8 The .NET TypeLibConverter exports value types, strings and arrays as propput accessors.

3.9 Hence a C# property like the following :

public Object MyObjectProperty { get; set; }

would produce the following IDL output (assuming dual-interface for the containing interface) :

[id(0x00000000), propget]
HRESULT MyObjectProperty([out, retval] VARIANT* pRetVal);
[id(0x00000000), propputref]
HRESULT MyObjectProperty([in] VARIANT rhs);

3.10 And a C# property like the following :

public int MyIntegerProperty { get; set; }

would produce the following IDL output (again assuming dual-interface for the containing interface) :

[id(0x00000001), propget]
HRESULT MyIntegerProperty([out, retval] long* pRetVal);
[id(0x00000001), propput]
HRESULT MyIntegerProperty([in] long rhs);

3.11 With the default type library transformation for managed Object properties, a Visual Basic 6.0 client may be coded as follows :

Dim TestClassObj As New TestClass
Dim TestClassObj2 As New TestClass

Private Sub Form_Load()

    Set TestClassObj.MyObject = TestClassObj2

End Sub

3.12 However, VB 6.0 code like the following :

TestClassObj.MyObject = "ABC"

would result in a runtime-error ‘424’ which means “Object required”. This is because “ABC” is a string and is not a COM object.

3.13 What is required is an accessor like the following :

[id(00000000), propput]
void MyObject([in] VARIANT rhs);

3.14 The next section presents a C# program that will produce a type library that will include a propput accessor for MyObject.

4. Customization Example – CustomizedTypeLibConverter.

4.1 Listed below is a C# program that performs the assembly to type library transformation that will include a propput accessor for MyObject :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

namespace CustomizedTypeLibConverter
{
    [
        ComImport,
        GuidAttribute("00020405-0000-0000-C000-000000000046"),
        InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
        ComVisible(false)
    ]
    public interface ICreateTypeInfo
    {
        void SetGuid(ref Guid guid);
        void SetTypeFlags(System.Runtime.InteropServices.ComTypes.TYPEFLAGS uTypeFlags);
        void SetDocString([MarshalAs(UnmanagedType.LPWStr)] string strDoc);
        void SetHelpContext(UInt32 dwHelpContext);
        void SetVersion(UInt16 wMajorVerNum, UInt16 wMinorVerNum);
        void AddRefTypeInfo(ITypeInfo pTInfo, ref UInt32 phRefType);
        void AddFuncDesc(UInt32 index, ref System.Runtime.InteropServices.ComTypes.FUNCDESC pFuncDesc);
        void AddImplType(UInt32 index, UInt32 hRefType);
        void SetImplTypeFlags(UInt32 index, Int32 implTypeFlags);
        void SetAlignment(UInt16 cbAlignment);
        void SetSchema([MarshalAs(UnmanagedType.LPWStr)] string strSchema);
        void AddVarDesc(UInt32 index, ref System.Runtime.InteropServices.ComTypes.VARDESC pVarDesc);
        void SetFuncAndParamNames(UInt32 index, IntPtr rgszNames, UInt32 cNames);
        void SetVarName(UInt32 index, [MarshalAs(UnmanagedType.LPWStr)] string strName);
        void SetTypeDescAlias(System.Runtime.InteropServices.ComTypes.TYPEDESC pTDescAlias);
        void DefineFuncAsDllEntry
             (
               UInt32 index, 
               [MarshalAs(UnmanagedType.LPWStr)] string strDllName, 
               [MarshalAs(UnmanagedType.LPWStr)] string strProcName
             );
        void SetFuncDocString(UInt32 index, [MarshalAs(UnmanagedType.LPWStr)] string strDocString);
        void SetVarDocString(UInt32 index, [MarshalAs(UnmanagedType.LPWStr)] string strDocString);
        void SetFuncHelpContext(UInt32 index, UInt32 dwHelpContext);
        void SetVarHelpContext(UInt32 index, UInt32 dwHelpContext);
        void SetMops(UInt32 index, string bstrMops);
        void SetTypeIdldesc(ref System.Runtime.InteropServices.ComTypes.IDLDESC pIdlDesc);
        void LayOut();
    }

    [
        ComImport,
        GuidAttribute("00020406-0000-0000-C000-000000000046"),
        InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
        ComVisible(false)
    ]
    public interface ICreateTypeLib2
    {
        ICreateTypeInfo CreateTypeInfo
                        (
                          [MarshalAs(UnmanagedType.LPWStr)] string strName, 
                          System.Runtime.InteropServices.ComTypes.TYPEKIND tkind
                        );
        void SetName([MarshalAs(UnmanagedType.LPWStr)] string strName);
        void SetVersion(UInt16 wMajorVerNum, UInt16 wMinorVerNum);
        void SetGuid(ref Guid guid);
        void SetDocString([MarshalAs(UnmanagedType.LPWStr)] string strDocString);
        void SetHelpFileName([MarshalAs(UnmanagedType.LPWStr)] string strHelpFileName);
        void SetHelpContext(UInt32 dwHelpContext);
        void SetLcid(UInt32 lcid);
        void SetLibFlags(UInt32 uLibFlags);
        void SaveAllChanges();
        void DeleteTypeInfo([MarshalAs(UnmanagedType.LPWStr)] string szName);
        void SetCustData(ref Guid guid, IntPtr pVarVal);
        void SetHelpStringContext(UInt32 dwHelpStringContext);
        void SetHelpStringDll([MarshalAs(UnmanagedType.LPWStr)] string strFileName);
    }

    public class ConversionEventHandler : ITypeLibExporterNotifySink
    {
        public void ReportEvent(ExporterEventKind eventKind, int eventCode, string eventMsg)
        {
            // Handle the warning event here.
        }

        public Object ResolveRef(Assembly asm)
        {
            // Resolve the reference here and return a correct type library.
            return null;
        }
    }

    class Program
    {
        // DisplayUsage() displays instructions on the command line
        // of this program.
        static void DisplayUsage()
        {
            Console.WriteLine("Usage : CustomizedTypeLibConverter [Input Assembly Path] [Output Type Library Path].");
        }

        // CheckArguments() interprets the command line of the program
        // and sets values for the variables of this program.
        static bool CheckArguments(string[] args, out string strInputAssembly, out string strOutputTypeLib)
        {
            // Assign receiver to default value.
            strInputAssembly = "";
            strOutputTypeLib = "";

            // Ensure that there are at least 2 arguments.
            if (args.Length < 2)
            {
                DisplayUsage();
                return false;
            }

            strInputAssembly = args[0];
            strOutputTypeLib = args[1];
            return true;
        }

        static void MarshalMananagedStrArray2UnmanagedStrArray
        (
          [In] string[] ManagedStringArray,
          int StringCount,
          [Out] out IntPtr[] pUnmanagedStringArray
        )
        {
            pUnmanagedStringArray = new IntPtr[StringCount];

            for (int i = 0; i < StringCount; i++)
            {
                pUnmanagedStringArray[i] = Marshal.StringToCoTaskMemUni(ManagedStringArray[i]);
            }
        }

        static void FreeUnmanagedStrArray(IntPtr[] pUnmanagedStringArray)
        {
            for (int i = 0; i < pUnmanagedStringArray.Length; i++)
            {
                Marshal.FreeCoTaskMem(pUnmanagedStringArray[i]);
            }
        }

        static void PerformCustomAction_ITestInterface(Object type_lib_converter)
        {
            ICreateTypeLib2 pICreateTypeLib2 = (ICreateTypeLib2)type_lib_converter;
            ITypeLib pITypeLib = (ITypeLib)type_lib_converter;

            // Obtain the ITypeInfo of ITestInterface
            Guid guid_ITestInterface = new Guid("2BC5C974-E70B-48c4-AA1F-B4EA15E957F7");

            ITypeInfo pITypeInfo_ITestInterface;
            pITypeLib.GetTypeInfoOfGuid(ref guid_ITestInterface, out pITypeInfo_ITestInterface);

            // Obtain the FUNCDESC of the MyObject propertyputref property.
            IntPtr pFuncDesc = IntPtr.Zero;
            pITypeInfo_ITestInterface.GetFuncDesc
                (
                    0,
                    out pFuncDesc
                );
            System.Runtime.InteropServices.ComTypes.FUNCDESC funcdesc_MyObject_PropertyPutRef 
                = (System.Runtime.InteropServices.ComTypes.FUNCDESC)
                  Marshal.PtrToStructure(pFuncDesc, typeof(System.Runtime.InteropServices.ComTypes.FUNCDESC));
            pITypeInfo_ITestInterface.ReleaseFuncDesc(pFuncDesc);
            pFuncDesc = IntPtr.Zero;

            // Obtain the FUNCDESC of the MyObject propertyget property.
            pITypeInfo_ITestInterface.GetFuncDesc
                (
                    1,
                    out pFuncDesc
                );
            System.Runtime.InteropServices.ComTypes.FUNCDESC funcdesc_MyObject_PropertyGet 
                = (System.Runtime.InteropServices.ComTypes.FUNCDESC)
                  Marshal.PtrToStructure(pFuncDesc, typeof(System.Runtime.InteropServices.ComTypes.FUNCDESC));
            pITypeInfo_ITestInterface.ReleaseFuncDesc(pFuncDesc);
            pFuncDesc = IntPtr.Zero;

            // Create an array of one single name for the MyObject property.
            string[] strNameArray_MyObject_Property = new string[1] { "MyObject" };
            // Create an array of IntPtr that contains the LPWSTR names of the MyObject property.
            IntPtr[] pstrNameArray_MyObject_Propery;
            MarshalMananagedStrArray2UnmanagedStrArray
            (
              strNameArray_MyObject_Property, 
              strNameArray_MyObject_Property.Length, 
              out pstrNameArray_MyObject_Propery
            );
            GCHandle gch = GCHandle.Alloc(pstrNameArray_MyObject_Propery, GCHandleType.Pinned);
            IntPtr ppstrNameArray_MyObject_Propery = gch.AddrOfPinnedObject();

            // Marshal.ReleaseComObject() must be called on pITypeInfo_ITestInterface 
            // otherwise it cannot be removed from the current typelib.
            Marshal.ReleaseComObject(pITypeInfo_ITestInterface);
            pITypeInfo_ITestInterface = null;
            // Delete away the type that we wish to re-construct.
            pICreateTypeLib2.DeleteTypeInfo("ITestInterface");

            // We now re-construct the ITestInterface interface
            // and insert it into the type library.
            ICreateTypeInfo pICreateTypeInfo = pICreateTypeLib2.CreateTypeInfo
                                               (
                                                 "ITestInterface", 
                                                 System.Runtime.InteropServices.ComTypes.TYPEKIND.TKIND_DISPATCH
                                               );
            // From here onwards, pICreateTypeInfo represents the 
            // builder for the ITestInterface type info.
            pICreateTypeInfo.SetGuid(ref guid_ITestInterface);

            // Add each existing functions to the ITestList interface using pICreateTypeInfo.
            // First set the name of the MyObject propertyputref function.
            pICreateTypeInfo.AddFuncDesc((uint)funcdesc_MyObject_PropertyPutRef.memid,  ref funcdesc_MyObject_PropertyPutRef);
            // Set the name of the propertyputref function.
            pICreateTypeInfo.SetFuncAndParamNames
            (
              (uint)funcdesc_MyObject_PropertyPutRef.memid, 
              ppstrNameArray_MyObject_Propery, 
              (uint)pstrNameArray_MyObject_Propery.Length
            ); 

            // Then set the name of the MyObject propertyget function.
            pICreateTypeInfo.AddFuncDesc((uint)funcdesc_MyObject_PropertyGet.memid,  ref funcdesc_MyObject_PropertyGet);
            // Set the name of the propertyget function.
            pICreateTypeInfo.SetFuncAndParamNames
            (
              (uint)funcdesc_MyObject_PropertyGet.memid, 
              ppstrNameArray_MyObject_Propery, 
              (uint)pstrNameArray_MyObject_Propery.Length
            );

            // Now set the name of a -new- MyObject propertyput function.
            // We do this by re-using funcdesc_MyObject_PropertyPutRef and modifying it.
            // Change the invoke kind from INVOKE_PROPERTYPUTREF to INVOKE_PROPERTYPUT.
            funcdesc_MyObject_PropertyPutRef.invkind = System.Runtime.InteropServices.ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT;
            pICreateTypeInfo.AddFuncDesc((uint)funcdesc_MyObject_PropertyPutRef.memid,  ref funcdesc_MyObject_PropertyPutRef);
            // Set the name of the new propertyput function.
            pICreateTypeInfo.SetFuncAndParamNames
            (
              (uint)funcdesc_MyObject_PropertyPutRef.memid, 
              ppstrNameArray_MyObject_Propery, 
              (uint)pstrNameArray_MyObject_Propery.Length
            );

            gch.Free();
            ppstrNameArray_MyObject_Propery = IntPtr.Zero;
            FreeUnmanagedStrArray(pstrNameArray_MyObject_Propery);

            pICreateTypeInfo.LayOut();
        }

        static void PerformCustomAction_CoClass_TestClass(Object type_lib_converter)
        {
            ICreateTypeLib2 pICreateTypeLib2 = (ICreateTypeLib2)type_lib_converter;
            ITypeLib pITypeLib = (ITypeLib)type_lib_converter;

            Guid guid_coclass = new Guid("B3CC27E0-FFFB-42c9-84E0-DC61874F73AC");

            // Obtain the ITypeInfo of the TestClass coclass.
            ITypeInfo pITypeInfo_CoClass_TestClass;
            pITypeLib.GetTypeInfoOfGuid(ref guid_coclass, out pITypeInfo_CoClass_TestClass);

            // Get the TYPEATTR of the coclass.
            IntPtr pTYPEATTR_CoClass = IntPtr.Zero;
            pITypeInfo_CoClass_TestClass.GetTypeAttr(out pTYPEATTR_CoClass);
            System.Runtime.InteropServices.ComTypes.TYPEATTR typeattr_coclass 
                = (System.Runtime.InteropServices.ComTypes.TYPEATTR)
                  Marshal.PtrToStructure(pTYPEATTR_CoClass, typeof(System.Runtime.InteropServices.ComTypes.TYPEATTR));
            pITypeInfo_CoClass_TestClass.ReleaseTypeAttr(pTYPEATTR_CoClass);
            pTYPEATTR_CoClass = IntPtr.Zero;

            // Prepare an array of ITypeInfo's : one for each TYPEATTR associated 
            // with the coclass minus the one for ITestInterface.
            ITypeInfo[] pTypeInfo_CoClassAttributes = new ITypeInfo[typeattr_coclass.cImplTypes - 1];

            int i = 0;
            int j = 0;
            for (i = 0, j = 0; i < typeattr_coclass.cImplTypes; i++)
            {
                int dwRefType;
                pITypeInfo_CoClass_TestClass.GetRefTypeOfImplType(i, out dwRefType);

                ITypeInfo pRefTypeInfo;
                pITypeInfo_CoClass_TestClass.GetRefTypeInfo(dwRefType, out pRefTypeInfo);

                string strName;
                string strDoc;
                int dwHelpContext;
                string strHelpFile;
                pRefTypeInfo.GetDocumentation(-1, out strName, out strDoc, out dwHelpContext, out strHelpFile);

                // Collect all TYPEATTR except for the one for the ITestInterface interface.
                if (strName != "ITestInterface")
                {
                    pTypeInfo_CoClassAttributes[j++] = pRefTypeInfo;
                }
            }

            // Marshal.ReleaseComObject() must be called on pITypeInfo_CoClass_TestClass 
            // otherwise it cannot be removed from the current typelib.
            Marshal.ReleaseComObject(pITypeInfo_CoClass_TestClass);
            pITypeInfo_CoClass_TestClass = null;

            // Delete away the ITypeInfo for the TestClass coclass.
            pICreateTypeLib2.DeleteTypeInfo("TestClass");

            // We now re-construct the TestClass coclass type definition.
            ICreateTypeInfo pICreateTypeInfo_CoClass 
                = pICreateTypeLib2.CreateTypeInfo("TestClass", System.Runtime.InteropServices.ComTypes.TYPEKIND.TKIND_COCLASS);
            pICreateTypeInfo_CoClass.SetGuid(ref guid_coclass);
            pICreateTypeInfo_CoClass.SetTypeFlags(System.Runtime.InteropServices.ComTypes.TYPEFLAGS.TYPEFLAG_FCANCREATE);

            // Attach each of the ITypeInfo's collected earlier to
            // the TestClass coclass.
            for (i = 0; i < pTypeInfo_CoClassAttributes.Length; i++)
            {
                uint phRefType = 0;
                pICreateTypeInfo_CoClass.AddRefTypeInfo(pTypeInfo_CoClassAttributes[i], ref phRefType);
                pICreateTypeInfo_CoClass.AddImplType((uint)i, phRefType);
            }

            // At this time, the new TestClass coclass is almost identical with the old TestClass coclass
            // except that it does not have the ITestInterface as a TYPEATTR.
            // We now obtain an ITypeInfo for the ITestInterface interface.
            Guid guid_ITestInterface = new Guid("2BC5C974-E70B-48c4-AA1F-B4EA15E957F7");
            ITypeInfo pITypeInfo_ITestInterface;
            pITypeLib.GetTypeInfoOfGuid(ref guid_ITestInterface, out pITypeInfo_ITestInterface);

            // We now add the ITestInterface interface to the TestClass coclass.
            uint phRefType_ITestInterface = 0;
            pICreateTypeInfo_CoClass.AddRefTypeInfo(pITypeInfo_ITestInterface, ref phRefType_ITestInterface);
            pICreateTypeInfo_CoClass.AddImplType((uint)1, phRefType_ITestInterface);
            pICreateTypeInfo_CoClass.SetImplTypeFlags
            (
              (uint)1, 
              (int)(System.Runtime.InteropServices.ComTypes.IMPLTYPEFLAGS.IMPLTYPEFLAG_FDEFAULT)
            );
            pICreateTypeInfo_CoClass.LayOut();
        }

        static void Main(string[] args)
        {
            string strInputAssembly;
            string strOutputTypeLib;

            if (CheckArguments(args, out strInputAssembly, out strOutputTypeLib) == false)
            {
                return;
            }

            Assembly asm = Assembly.LoadFrom(strInputAssembly);
            TypeLibConverter converter = new TypeLibConverter();
            ConversionEventHandler eventHandler = new ConversionEventHandler();

            Object type_lib_converter = converter.ConvertAssemblyToTypeLib
                                        (
                                          asm, 
                                          strOutputTypeLib, 
                                          TypeLibExporterFlags.ExportAs32Bit, eventHandler
                                        );

            PerformCustomAction_ITestInterface(type_lib_converter);
            PerformCustomAction_CoClass_TestClass(type_lib_converter);

            ICreateTypeLib2 pICreateTypeLib2 = (ICreateTypeLib2)type_lib_converter;
            pICreateTypeLib2.SaveAllChanges();
        }
    }
}

4.2 It is a heavily customized program and is in no way generic. It cannot be used on any other assembly other than CSTestInterfaceClass.dll.

4.3 It is pretty long and I shall skip going through some of the mundane functions like CheckArguments().

4.4 Other more substantial functions like MarshalMananagedStrArray2UnmanagedStrArray() and FreeUnmanagedStrArray() are also left to the reader to analyze as they are not particularly complicated.

4.5 I shall even skip explaining in detail the TypeLibConverter.ConvertAssemblyToTypeLib() function and would instead refer the reader to the existing MSDN documentation :

4.6 It may come across as a surprise to some readers but, as can be seen from the code provided above, I do not provide any non-trivial implementation for the (4th) ITypeLibExporterNotifySink parameter.

4.7 This is because any customizations available through such an ITypeLibExporterNotifySink object parameter is limited.

4.8 What is of interest to us as far as the TypeLibConverter.ConvertAssemblyToTypeLib() function is concerned is the return value which is of type object.

4.9 What is interesting about this returned object is that, among others, it implements the ICreateTypeLib2 and the ITypeLib COM interfaces. Both these interfaces are valuable in enabling us to directly manipulate the contents of the output type library produced by the TypeLibConverter class.

4.10 The program makes a lot of use of COM interfaces like ICreateTypeInfo, ICreateTypeLib2, ITypeLib and ITypeInfo. I shall also not spend time discussing the functionalities of these interfaces and leave it to the reader to refer to the provided links to their MSDN documentation.

5. CustomizedTypeLibConverter – Synopsis.

5.1 The program starts by loading the target CSTestInterfaceClass.dll assembly (which is to be provided as a full path in the command line), instantiating the TypeLibConverter class and the ConversionEventHandler class (which implements the ITypeLibExporterNotifySink interface).

5.2 The TypeLibConverter.ConvertAssemblyToTypeLib() function is then called with the appropriate parameters.

5.3 The TypeLibConverter instance then performs its job of interpreting the code of the input assembly and producing, in-memory, a copy of a COM type library equivalent of its types.

5.4 The returned object is stored in “type_lib_converter” (type object). This object stores the in-memory type library waiting to be saved as a disk file.

5.5 The game plan is to modify the contents of this in-memory type library before finally saving it as a file.

5.6 The PerformCustomAction_ITestInterface() function is then called in order to perform this modification on the ITestInterface.

5.7 Changes to ITestInterface is not enough, however. To complete the required transformations to the type library, all other types that refers to ITestInterface may also require modifications. This is where the PerformCustomAction_CoClass_TestClass() function comes in.

5.8 It is called in order to perform modification with respect to the TestClass coclass.

5.9 The in-memory type library is then saved as a file.

5.10 In the next section, we will study the PerformCustomAction_ITestInterface() function in greater detail. We will explore the PerformCustomAction_CoClass_TestClass() function in part two.

6. PerformCustomAction_ITestInterface().

6.1 Recall from points 3.13 and 3.14 that we want to add a propput accessor for the ITestInterface.MyObject property.

6.2 The game plan for PerformCustomAction_ITestInterface() is as follows :

  • Obtain information on the ITestInterface interface from the in-memory type library.
  • From the ITestInterface interface, obtain information on the MyObject propget and propputref accessors.
  • Delete away ITestInterface from the type library.
  • Re-construct ITestInterface using the MyObject propget and propputref accessors.
  • Create a propput accessor and add it to ITestInterface.
  • Add the new ITestInterface to the type library.

6.3 The function begins by casting the type_lib_converter object as a ICreateTypeLib2 object and as a ITypeLib object. These are stored in “pICreateTypeLib2” and “pITypeLib” respectively.

6.4 A Guid “guid_ITestInterface” is then defined with a specific value (i.e. 2BC5C974-E70B-48c4-AA1F-B4EA15E957F7) indicating ITestInterface (see 3.2).

6.5 Using guid_ITestInterface, pITypeLib.GetTypeInfoOfGuid() is called to obtain the ITypeInfo of ITestInterface (“pITypeInfo_ITestInterface”).

6.6 pITypeInfo_ITestInterface.GetFuncDesc() is then called to obtain the FUNCDESC of the MyObject propertyputref property accessor function.

6.7 Marshal.PtrToStructure() must be called to transform the IntPtr (“pFuncDesc”) returned from pITypeInfo_ITestInterface.GetFuncDesc() into a managed FUNCDESC structure (“funcdesc_MyObject_PropertyPutRef”).

6.8 After the transformation of “pFuncDesc” into “funcdesc_MyObject_PropertyPutRef”, pITypeInfo_ITestInterface.ReleaseFuncDesc() can be called on “pFuncDesc” to release the unmanaged resources associated with “pFuncDesc”.

6.9 The same process is then repeated to obtain the managed FUNCDESC associated with the MyObject propget accessor function with the FUNCDESC stored inside “funcdesc_MyObject_PropertyGet”.

6.10 Both “funcdesc_MyObject_PropertyPutRef” and “funcdesc_MyObject_PropertyGet” are very important in that they independently store accessor function information on the MyObject property. These can be re-used even if the original ITestInterface is deleted.

6.11 An array of unmanaged wide-character strings (“pstrNameArray_MyObject_Propery”) is then created using MarshalMananagedStrArray2UnmanagedStrArray(). A pointer to this array is obtained through using GCHandle and its AddrOfPinnedObject() function.

6.12 This pointer to the unmanaged string array is later used in the call to pICreateTypeInfo.SetFuncAndParamNames() which expects such a pointer. More on this later.

6.13 The ITypeInfo object associated with ITestInterface (“pITypeInfo_ITestInterface”) is then released via Marshal.ReleaseComObject(). This release is important and is required otherwise data on the ITestInterface interface cannot be removed from the current type library.

6.14 pICreateTypeLib2.DeleteTypeInfo() is then called to officially delete ITestInterface from the type library.

6.15 We then re-create a new ITestInterface interface with the same GUID and propget, propputref accessor functions. In addition, we will add a new propput accessors function to this interface.

6.16 pICreateTypeLib2.CreateTypeInfo() is called to create a new type name “ITestInterface” which is pure dispinterface-based.

6.17 The returned ICreateTypeInfo object (“pICreateTypeInfo”) serves as a builder for the new ITestInterface.

6.18 The process of adding an accessor function starts by calling pICreateTypeInfo.AddFuncDesc() using the FUNCDESC structure saved earlier. pICreateTypeInfo.SetFuncAndParamNames() is then called again using the same FUNCDESC and the pointer to the wide-character string array (“ppstrNameArray_MyObject_Propery”).

6.19 In this way, using “funcdesc_MyObject_PropertyPutRef” and “funcdesc_MyObject_PropertyGet” and “ppstrNameArray_MyObject_Propery”, the MyObject propputref and propget accessor functions are added to ITestInterface.

6.20 The pointer to the unmanaged wide-character string array (“ppstrNameArray_MyObject_Propery”) is used in the call to pICreateTypeInfo.SetFuncAndParamNames(). SetFuncAndParamNames() expects this string array to contain an array of names the first of which is intended to the the name of the function. Subsequent names are meant to be parameter names.

6.21 With reference to point 3.4, for the MyObject accessor functions, only the name of the function is required. The propget function returns a VARIANT and does not require any parameter. The propputref function does take a VARIANT parameter but its name is not important and “rhs” is the default name given.

6.22 With reference to point 3.13, the same will be true for the new propput function as it is for the propputref function.

6.23 For the propput function, we re-use “funcdesc_MyObject_PropertyPutRef” and modify its invkind field from INVOKE_PROPERTYPUTREF to INVOKE_PROPERTYPUT. We then call pICreateTypeInfo.AddFuncDesc() and pICreateTypeInfo.SetFuncAndParamNames() are then called as usual.

 6.24 The GCHandle that was used to pin the “pstrNameArray_MyObject_Propery” unmanaged wide string array is then freed and we free all the wide strings in the “pstrNameArray_MyObject_Propery” array using FreeUnmanagedStrArray().

6.25 A brand new ITestInterface element is now officially added to the type library.

7. In Summary.

7.1 In summary for now, note that the entry point to type library customization via the TypeLibConverter class is straight-forward, i.e., just get the return object from the call to TypeLibConverter.ConvertAssemblyToTypeLib() and then perform customization actions using it.

7.2 The actual customization process itself, however, can get complicated. It all depends on the requirements of the customization.

7.3 As can be seen in the PerformCustomAction_ITestInterface() function, modification of a type sometimes require its complete deletion and re-creation. This can get very convoluted even for a relatively small change. Such is the nature of type library management.

7.4 In part two, I shall proceed to expound on the PerformCustomAction_CoClass_TestClass() function. The important purpose that it serves (i.e. modifications to the TestClass coclass type) will be explained.

7.5 I shall also demonstrate the positive changes that the CustomizedTypeLibConverter program eventually makes to the output type library of CSTestInterfaceClass.dll by demonstration of a sample VB 6.0 client application.

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

2 thoughts on “Using the TypeLibConverter to Customize Type Library Creation from an Assembly Part 1.

  1. Hi,
    out team had the need for a lot of costumization of the created TLB. We tried a similar solution as you gave but even with the help of Microsoft support we weren’t able to fullfill our needs.
    The solution we found was to create an IDL-file with the help of reflection and than use the midl-compiler to create the TLB-file.
    We are very happy with this solution. You can easily proof read the result and manipulations are implemented very easily.

    Posted by Martin Demberger | February 3, 2014, 11:44 am

Trackbacks/Pingbacks

  1. Pingback: Using the TypeLibConverter to Customize Type Library Creation from an Assembly Part 2. | limbioliong - February 3, 2014

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: