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

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

1. Introduction.

1.1 This article is a follow up from part 1.

1.2 In part 1, we presented C# code for a class library named CSTestInterfaceClass.dll in which an interface “ITestInterface” and a coclass “TestClass” is defined.

1.3 ITestInterface contains a single property “MyObject” of type object. Being of type object (a reference type) means that, when transformed into a COM type library, it will be represented by a pair of propget and propputref accessor functions.

1.3 Our aim is to control the assembly to type library transformation process so that the in the final type library output, MyObject will be represented by propget, propputref and propput accessor functions.

1.4 To this end, we presented the CustomizedTypeLibConverter C# program – a heavily customized program that is designed and developed to process the CSTestInterfaceClass.dll class library to achieve such a transformation.

1.5 CustomizedTypeLibConverter defines two major functions : PerformCustomAction_ITestInterface() and PerformCustomAction_CoClass_TestClass().

1.6 We have gone through in-depth the workings of the PerformCustomAction_ITestInterface() function in part 1. We saw that this function re-constructs the ITestInterface to include a propput accessor function.

1.7 In this part 2, we shall go through PerformCustomAction_CoClass_TestClass() and will explain why modification of the “TestClass” coclass is still necessary despite already making changes to ITestInterface.

2. PerformCustomAction_CoClass_TestClass().

2.1 This function takes as parameter (“type_lib_converter”) the object that was returned from the call to TypeLibConverter.ConvertAssemblyToTypeLib().

2.2 It then casts this object into its ICreateTypeLib2 and ITypeLib interfaces.

2.3 The game plan for the PerformCustomAction_CoClass_TestClass() is as follows :

  • Go through all attributes of the “TestClass” coclass type.
  • For each attribute, check to see if its name is ITestInterface.
  • If so, skip it. If not, add it to an array of ITypeInfo objects.
  • The idea is to keep an array of ITypeInfo objects other than ITestInterface which are attributes of “TestClass”.
  • We will then delete the data associated with “TestClass” and re-create a new one.
  • The attributes we collected are the ones we want to keep for the new “TestClass”.
  • We will then obtain an ITypeInfo of the ITestInterface interface which we have previously re-created (in function PerformCustomAction_ITestInterface()).
  • Using this ITypeInfo, we add ITestInterface as an interface attribute of “TestClass”.

2.4 Using the guid (“B3CC27E0-FFFB-42c9-84E0-DC61874F73AC”) of the “TestClass” coclass, pITypeLib.GetTypeInfoOfGuid() is called to obtain the ITypeInfo of the “TestClass” coclass (stored in “pITypeInfo_CoClass_TestClass”).

2.5 pITypeInfo_CoClass_TestClass.GetTypeAttr() is then called to obtain a pointer to the (unmanaged) TYPEATTR of the “TestClass” coclass. Marshal.PtrToStructure() is called to transform this unmanaged TYPEATTR into its managed equivalent (“typeattr_coclass”).

2.6 As usual, we call pITypeInfo_CoClass_TestClass.ReleaseTypeAttr() to release memory resources associated with the unmanaged TYPEATTR. The data inside the managed “typeattr_coclass” struct, however, will remain until it is reclaimed by the garbage collector.

2.7 We then declare an array of ITypeInfo objects. The count of its element is one less the number in “typeattr_coclass.cImplTypes”. That is, one minus the count of all the attributes in the “TestClass” coclass. One minus because recall that we do not want to include the attribute associated with ITestInterface.

2.8 We then enter a loop and go through all the implemented interfaces of the “TestClass” coclass. For each implemented interface, we call pITypeInfo_CoClass_TestClass.GetRefTypeOfImplType() to retrieve its type description which is referenced by a handle (returned in “dwRefType”).

2.9 This is the heart of the reason why we cannot simply delete ITestInterface and then re-create it in the hope that coclass’es like “TestClass” will continue to function just fine as long as some ITestInterface exists in the type library.

2.10 Type libraries do not work that way. Coclass’es which implement interfaces hold references to these interfaces. Programmatically, these references are managed by a handle.

2.11 We then use this reference handle to retrieve the actual ITypeInfo of the implemented interface. This is done with the call to pITypeInfo_CoClass_TestClass.GetRefTypeInfo(). The returned ITypeInfo is stored in “pRefTypeInfo”.

2.12 We use “pRefTypeInfo” to obtain the name of the implemented interface by calling its GetDocumentation() method.

2.13 The name of the interface is then compared with the string “ITestInterface”. If the name does not match, we store “pRefTypeInfo” inside the “pTypeInfo_CoClassAttributes” array. If the name matches, we skip it.

2.14 In this way, we build up the “pTypeInfo_CoClassAttributes” array to contain the ITypeInfo of “TestClass” implemented interfaces which are anything but “ITestInterface”.

2.15 The ITypeInfo object that is associated with the “TestClass” coclass (i.e. “pITypeInfo_CoClass_TestClass”) is then released via Marshal.ReleaseComObject().

2.16 Just like the ITypeInfo object associated with ITestInterface that we saw in part 1, the release of “pITypeInfo_CoClass_TestClass” is important because with this release, the “TestClass” coclass cannot be deleted from the current type library.

2.17 pICreateTypeLib2.DeleteTypeInfo() is then called to delete away the type information associated with “TestClass”.

2.18 pICreateTypeLib2.CreateTypeInfo() is then called to re-create a coclass type named “TestClass”. The return ICreateTypeInfo is stored in “pICreateTypeInfo_CoClass”.

2.19 The same guid is set on this “TestClass” coclass. This coclass is also marked with the flag TYPEFLAGS.TYPEFLAG_FCANCREATE. This flag is important because without it, by default the “TestClass” coclass will be marked with the noncreatable flag which means that such a coclass cannot be instantiated.

2.20 We then loop through all the elements of “pTypeInfo_CoClassAttributes” (the array of implemented interfaces of the previous “TestClass” coclass, minus ITestInterface) and for each ITypeInfo in the array, we call pICreateTypeInfo_CoClass.AddRefTypeInfo() on it.

2.21 This is done to make the new “TestClass” coclass hold a reference to previous implemented interfaces of the old “TestClass” coclass, minus ITestInterface. A handle to the reference is rerurned from this call to AddRefTypeInfo().

2.22 pICreateTypeInfo_CoClass.AddImplType() is then called to officially add the new implementation interface and assigned the official position of this implementation interface relative to other implementation interfaces.

2.23 A ITypeInfo (i.e. “pITypeInfo_ITestInterface”) is then declared to hold the return ITypeInfo object from a following call to pITypeLib.GetTypeInfoOfGuid(). The guid used in the call to GetTypeInfoOfGuid() is that of ITestInterface.

2.24 We saw in the PerformCustomAction_ITestInterface() function that a new ITestInterface has been created and so this call to pITypeLib.GetTypeInfoOfGuid() will return the ITypeInfo of this new ITestInterface type.

2.25 Finally, we call pICreateTypeInfo_CoClass.AddRefTypeInfo() and pICreateTypeInfo_CoClass.AddImplType() to officially make the new “TestClass” coclass type hold a reference to “ITestInterface” and set ITestInterface at position 1 in the list of implementation interfaces of “TestClass”.

2.26 Incidentally position 0 is the interface _Object and at position 1, ITestInterface is officially below _Object.

2.27 pICreateTypeInfo_CoClass.SetImplTypeFlags() is then called on the implementation interface at position 1 to be flagged as IMPLTYPEFLAGS.IMPLTYPEFLAG_FDEFAULT. This effectively sets ITestInterface to be the default interface of the “TestClass” coclass.

3. The Reason Why PerformCustomAction_CoClass_TestClass() is Important.

3.1 With reference to two short points highlighted in 2.9 and 2.10 of the last section, I present here a fuller explanation of the crucial principle involved pertaining to why the “TestClass” coclass must be deleted and re-created :

  • Coclass’es implement interfaces.
  • Programmatically, these interfaces are referenced by the coclass.
  • Programmatically, each reference is managed by a handle.
  • This handle value is stored as part of the binary code of the coclass in the type library.
  • With the ITestInterface deleted and re-created, if the existing “TestClass” coclass was not similarly deleted and re-created, it would potentially hold an invalid reference to ITestInterface.

3.2 In fact, if PerformCustomAction_CoClass_TestClass() was not called in the CustomizedTypeLibConverter program, the created type library would hold a coclass type named “TestClass” which internally may hold an incorrect handle value which references the ITestInterface type.

3.3 If you used OLEVIEW.EXE to open this type library, it may hang when you attempt to click on the ITestInterface entry of the TestClass coclass :

OleViewHang

3.4 This is because internally the type library holds inconsistent data due to a “missing” type (i.e. the ITestInterface type).

4. The Final Type Library Output.

4.1 Once CustomizedTypeLibConverter.exe has been successfully compiled and run with an example command line as follows :

CustomizedTypeLibConverter CSTestInterfaceClass.dll CSTestInterfaceClass.tlb

we may examine the CSTestInterfaceClass.tlb type library using OLEVIEW.EXE :

// 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");

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

    [
      uuid(2BC5C974-E70B-48C4-AA1F-B4EA15E957F7)
    ]
    dispinterface ITestInterface {
        properties:
        methods:
            [id(00000000), propput]
            void MyObject([in] VARIANT rhs);
            [id(00000000), propget]
            VARIANT MyObject();
            [id(00000000), propputref]
            void MyObject([in] VARIANT rhs);
    };

    [
      uuid(B3CC27E0-FFFB-42C9-84E0-DC61874F73AC)
    ]
    coclass TestClass {
        interface _Object;
        [default] dispinterface ITestInterface;
    };
};

4.2 As can be seen from the above IDL, the ITestInterface dispinterface now possesses 3 property accessors (one for propput, one for propget and one for propputref).

5. Example VB 6.0 Client Code.

5.1 Listed below is a sample VB 6.0 client code that will take full advantage of the propput accessor :

Dim TestClassObj As New TestClass
Dim TestClassObj2 As New TestClass

Private Sub Form_Load()

    TestClassObj.MyObject = "ABC"

    MsgBox TestClassObj.MyObject

    TestClassObj2.MyObject = "DEF"

    Set TestClassObj.MyObject = TestClassObj2

    MsgBox TestClassObj.MyObject.MyObject
End Sub

The VB 6.0 program references CSTestInterfaceClass.tlb.

5.2 Here is a synopsis of the test program :

  • 2 TestClass objects (TestClassObj and TestClassObj2) are instantiated.
  • The MyObject property of TestClassObj is assigned the string value “ABC”.
  • This invokes the propput accessor and the assignment will go through successfully.
  • A message box is used to verify this.
  • Then the MyObject property of TestClassObj2 is assigned the string value “DEF”.
  • As with the previous case, this assignment succeeds.
  • Next, the MyObject property of TestClassObj is assigned to point to TestClassObj2.
  • Then a real test of MyObject property accessing is done with a call to display the value of the MyObject property of the MyObject property of TestClassObj.
  • This means that we want to show the value of the MyObject property of TestClassObj2.
  • The message box will display “DEF” which is the correct value.

6. In summary.

6.1 I hope readers have enjoyed the two parts of this long treatise.

6.2 People who can benefit from customized type library transformation using the TypeLibConverter class include Release Engineers who have to do automated creation of type libraries from managed assemblies.

6.3 Manual type library creation can certainly be done by :

  • Opening a target type library using OLEVIEW.EXE.
  • Followed by manual modification of this IDL.
  • Finally followed by re-compilation using MIDL.EXE.

6.4 My aim is to provide an example how this may be achieved programmatically, albeit it certainly is non-trivial and can get very complicated indeed even for a small change.

6.5 Due to the COM-centric nature of type library programming, familiarity with the relevant COM interfaces, e.g. ITypeInfo, ICreateTypeInfo, ICreateTypeLib2, etc, is a must.

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

No comments yet.

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: