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

Understanding Implementations for ITypeLibExporterNotifySink Part 1.

1. Introduction.

1.1 This article is a follow up to my last blog Programmatically Register Assemblies in C#.

1.2 In that article, I mentioned that I will be writing more to explain implementations for the ITypeLibExporterNotifySink interface as used in the context of a call to TypeLibConverter.ConvertAssemblyToTypeLib().

1.3 I will dive straight into exploring this interface and will not be going over the whole background of TypeLibConverter.ConvertAssemblyToTypeLib(). For background information, please refer to my last article.

1.4 Throughout this current blog, some of the things expounded in my last blog will be mentioned again.

1.5 In particular, I will refer to the RegisterAssembly program source codes, specifically the ConversionEventHandler class.

1.6 A test managed class library will be provided and will be processed by RegisterAssembly.exe.

1.7 This is intended to demonstrate the inner workings of ConversionEventHandler.

1.8 All programs (including RegisterAssemblies.exe) are compiled in 32-bits.

2. The Demonstration Programs.

2.1 The whole purpose of registering an assembly is to export its managed types to the unmanaged world as COM types.

2.2 The ultimate goal is to allow an unmanaged COM client (an application or another COM server) to use these COM types.

2.3 Other than having information stored in the registry, they must also be contained inside a type library (either as a .tlb file or embedded as a resource in a DLL).

2.4 This type library must be produced and registered in order to be used in the COM world.

2.5 To demonstrate the production and registration of a type library that originates from a managed source, we shall provide a managed class library that defines a class to be exported to COM.

2.6 This class is designed such that it implements an interface which is defined in a separate managed class library.

2.7 The interface defined in the dependency assembly is also to be COM exported.

2.8 We thus have 2 managed assemblies both of which need to have type libraries produced in order to be referenced by an unmanaged COM client.

2.9 The type library production and registration process will be demonstrated by RegisterAssembly’s ConversionEventHandler class.

2.10 This demonstration will also include an unmanaged COM client application.

2.11 The source codes for the test programs presented can be found in CodePlex under “Test Programs for ITypeLibExporterNotifySink”.

3. The ManagedCOMDefinitions Class Library.

3.1 ManagedCOMDefinitions is a class library which contains the definition of a single interface named IManagedInterface.

3.2 The IManagedInterface interface is listed below :

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

namespace ManagedCOMDefinitions
{
    [ComVisible(true)]
    [Guid("F290B613-B5B3-4B80-BB98-88F5985AB63C")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IManagedInterface
    {
        void TestMethod01();
    }
}
  • The name “IManagedInterface” is intended to mean an interface that is originally defined in a managed class library.
  • It does not mean that only managed code can use this interface.
  • It is given its own GUID.
  • When exported to COM, we intend for it to be a dual interface (i.e. it implements IDispatch).
  • A single method TestMethod01() is contained in the interface.

3.3 When compiled, ManagedCOMDefinitions.dll will be produced.

3.4 If you download the source codes and opened the ManagedCOMDefinitions project in Visual Studio, make sure that in the project properties, the “Register for COM interop” check box is not selected (see the red-colored box in the pic below) :

properties_registerforcominterop

3.5 In the next section, we will present a managed class library with a class that implements IManagedInterface.

4. The ManagedImplementation Class Library.

4.1 ManagedImplementation is a class library which contains the definition of a class named ManagedImpl01.

4.2 The ManagedImpl01 class is listed below :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using ManagedCOMDefinitions;

namespace ManagedImplementation
{
    [ComVisible(true)]
    [Guid("15EE143E-7DAD-4C84-A417-AAB2495D3302")]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("ManagedImplementation.ManagedImpl01")]
    public class ManagedImpl01 : IManagedInterface
    {
        public ManagedImpl01()
        {
        }

        #region IManagedInterface implementation.
        public void TestMethod01()
        {
            Console.WriteLine("ManagedImplementation.ManagedImpl01 IManagedInterface.TestMethod01()");
        }
        #endregion IManagedInterface implementation.
    }
}
  • ManagedImplementation references the ManagedCOMDefinitions assembly.
  • In the code above, the ManagedCOMDefinitions namespace is referenced.
  • ManagedImpl01 is also exported to COM.
  • It implements IManagedInterface.

4.3 When compiled, ManagedImplementation.dll will be produced.

4.4 Just like it was for the ManagedCOMDefinitions project, if you download the source codes and opened the ManagedImplementation project in Visual Studio, make sure that in the project properties, the “Register for COM interop” check box is not selected.

5. Registering ManagedImplementation.Dll.

5.1 The unmanaged COM client application, that we will meet later on, works by instantiating the ManagedImpl01 class found in ManagedImplementation.dll as an implementation of IManagedInterface. This is accomplished by COM interop.

5.2 Hence ManagedImplementation.dll must be registered as an assembly. The result of such registration is that COM information for the ManagedImpl01 class be created in the registry.

5.3 Another important thing is to create a type library from ManagedImplementation.dll and register it.

5.4 We thus run RegisterAssembly.exe against ManagedImplementation.dll with the TYPELIB flag.

5.5 At runtime, the DoWork() method in RegisterAssemby will be called :

static bool DoWork()
{
    try
    {
        if (m_bVerbose)
        {
            Console.WriteLine(string.Format("Target Assembly File : [{0:S}].", m_strTargetAssemblyFilePath));
        }

        if (m_bUnregister)
        {
            if (PerformAssemblyUnregistration(m_strTargetAssemblyFilePath) == false)
            {
                return false;
            }

            if (m_bTypeLib == true)
            {
                return PerformTypeLibUnRegistration(m_strTargetAssemblyFilePath);
            }
            else
            {
                return true;
            }
        }
        else
        {
            if (PerformAssemblyRegistration(m_strTargetAssemblyFilePath, m_bCodeBase) == false)
            {
                return false;
            }

            if (m_bTypeLib == true)
            {
                return PerformTypeLibCreationAndRegistration(m_strTargetAssemblyFilePath);
            }
            else
            {
                return true;
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(string.Format("An exception occurred. Exception description : [{0:S}].", ex.Message));
        return false;
    }
}

5.6 PerformAssemblyRegistration() will be called and will succeed.

5.7 As the TYPELIB flag is used, m_bTypeLib will be true and so PerformTypeLibCreationAndRegistration() will be called :

static bool PerformTypeLibCreationAndRegistration(string strTargetAssemblyFilePath)
{
    try
    {
        string strTargetAssemblyDirectory = Path.GetDirectoryName(strTargetAssemblyFilePath);
        string strTargetAssemblyFileNameWithoutExtension = Path.GetFileNameWithoutExtension(strTargetAssemblyFilePath);
        string strTargetTypeLibFullPath = strTargetAssemblyDirectory + "\\" + strTargetAssemblyFileNameWithoutExtension + ".tlb";

        TypeLibConverter converter = new TypeLibConverter();
        Assembly assembly = Assembly.LoadFrom(strTargetAssemblyFilePath);
        TypeLibExporterFlags flags;

        if (Is32Bits())
        {
            flags = TypeLibExporterFlags.ExportAs32Bit;
        }
        else if (Is64Bits())
        {
            flags = TypeLibExporterFlags.ExportAs64Bit;
        }
        else
        {
            Console.WriteLine(string.Format("Unknown bit-ness."));
            return false;
        }

        ICreateTypeLib create_typeLib = (ICreateTypeLib)(converter.ConvertAssemblyToTypeLib
            (assembly, strTargetTypeLibFullPath, flags, m_pITypeLibExporterNotifySink));

        // SaveAllChanges() will create the TypeLib physical file
        // based on strTargetTypeLibFullPath.
        create_typeLib.SaveAllChanges();

        ITypeLib typelib = (ITypeLib)create_typeLib;

        UInt32 uiRetTemp = RegisterTypeLib(typelib, strTargetTypeLibFullPath, null);

        if (uiRetTemp == 0)
        {
            Console.WriteLine(string.Format("TypeLib File [{0:S}] registered.", strTargetTypeLibFullPath));
        }
        else
        {
            Console.WriteLine(string.Format("Failed to register TypeLib File [{0:S}]. Error code : [{1:D}]",
                strTargetTypeLibFullPath, uiRetTemp));
            return false;
        }

        return true;
    }
    catch (Exception ex)
    {
        Console.WriteLine(string.Format("An exception occurred. Exception description : [{0:S}].", ex.Message));
        return false;
    }
}

5.8 When control reaches the following line :

ICreateTypeLib create_typeLib = (ICreateTypeLib)(converter.ConvertAssemblyToTypeLib
  (assembly, strTargetTypeLibFullPath, flags, m_pITypeLibExporterNotifySink));
  • The .NET framework will aim to create a type library from the COM-exported types contained inside ManagedImplementation.dll.
  • The library attribute to be generated for the type library will be based on :
    • The assembly’s GuidAttribute (for the LIBID).
    • The assembly’s AssemblyVersionAttribute (for the version information).
  • The important type exported to COM will be the ManagedImpl01 class (due to ManagedImpl01 being declared with ComVisibleAttribute).
  • The ManagedImpl01’s coclass information will thus be generated.
  • The various COM-related information associated with ManagedImpl01 will be used to generate the associated type information for the ManagedImpl01 coclass.
  • These include:
    • The CLSID (based from the class’ GuidAttribute),
    • The interfaces implemented by the class (based on the interfaces that ManagedImpl01 implements and the ClassInterfaceAttribute).
  • Now when the framework detects that ManagedImpl01 implements IManagedInterface, it will note that it too is exported to COM.
  • The framework will then seek out the type library which contains the definition of IManagedInterface.
  • It needs this in order to generate the importlib attribute for the current type lib (ManagedImplementation.tlb) that it is generating.
  • From the metadata for IManagedInterface, the framework will detect that IManagedInterface originated from the ManagedCOMDefinitions assembly.
  • From the metadata of the ManagedCOMDefinitions assembly, its GuidAttribute and AssemblyVersionAttribute will be extracted.
  • The framework will search the registry for information on the type library associated with the ManagedCOMDefinitions assembly.
  • Unless a type library for ManagedCOMDefinitions has previously been created and registered (e.g. via RegAsm.exe or via RegistryAssembly.exe), this type library information will not be found.
  • This will prompt the framework to invoke the ResolveRef() method of m_pITypeLibExporterNotifySink.
  • This works out to ConversionEventHandler.ResolveRef().

5.9 The code for ConversionEventHandler.ResolveRef() is listed below :

public Object ResolveRef(Assembly asm)
{
    try
    {
        // Resolve the reference here and return a correct type library.
        if (m_bVerbose)
        {
            Console.WriteLine("ConversionEventHandler.ResolveRef() [assembly : {0:S}]", asm.FullName);
        }

        string strAssemblyDirectory = Path.GetDirectoryName(asm.Location);
        string strAssemblyFileNameWithoutExtension = Path.GetFileNameWithoutExtension(asm.Location);
        string strTypeLibFullPath = strAssemblyDirectory + "\\" + strAssemblyFileNameWithoutExtension + ".tlb";
        TypeLibConverter converter = new TypeLibConverter();
        ConversionEventHandler eventHandler = new ConversionEventHandler(m_bVerbose);
        TypeLibExporterFlags flags;

        if (Program.Is32Bits())
        {
            flags = TypeLibExporterFlags.ExportAs32Bit;
        }
        else if (Program.Is64Bits())
        {
            flags = TypeLibExporterFlags.ExportAs64Bit;
        }
        else
        {
            Console.WriteLine(string.Format("Unknown bit-ness."));
            return null;
        }

        ICreateTypeLib create_typeLib = null;

        try
        {
            create_typeLib = (ICreateTypeLib)(converter.ConvertAssemblyToTypeLib
                (asm, strTypeLibFullPath, flags, eventHandler));
        }
        catch (Exception ex)
        {
            Console.WriteLine(string.Format("Unable to convert assembly [{0:S}] into a Type Lib. Exception description : [{1:S}]",
                strAssemblyFileNameWithoutExtension, ex.Message));
            return null;
        }

        try
        {
            // SaveAllChanges() will create the TypeLib physical file
            // based on strTargetTypeLibFullPath.
            create_typeLib.SaveAllChanges();
        }
        catch (Exception ex)
        {
            Console.WriteLine(string.Format("Unable to save TypeLib File [{0:S}] registered. Exception description : [{1:S}]", 
               strTypeLibFullPath, ex.Message));
            return null;
        }

        ITypeLib typelib = (ITypeLib)create_typeLib;
        UInt32 uiRetTemp = Program.RegisterTypeLib(typelib, strTypeLibFullPath, null);

        if (uiRetTemp == 0)
        {
            Console.WriteLine(string.Format("TypeLib File [{0:S}] registered.", strTypeLibFullPath));
        }
        else
        {
            Console.WriteLine(string.Format("Failed to register TypeLib File [{0:S}]. Error code : [{1:D}]",
                strTypeLibFullPath, uiRetTemp));
            return null;
        }

        return typelib;
    }
    catch (Exception ex)
    {
        Console.WriteLine(string.Format("An exception occurred. Exception description : [{0:S}].", 
             ex.Message));
        return null;
    }
}
  • The intention of this method is to create and save an unmanaged COM type library for the assembly passed as parameter.
  • In our current case, the asm parameter will be the ManagedCOMDefinitions assembly.
  • A full path to the target type library will be formulated and this is assumed to be the same location as the ManagedCOMDefinitions assembly.
  • The name of the type library is assumed to be the name of the assembly, i.e. ManagedCOMDefinitions.tlb.
  • ResolveRef() in fact is very similar to PerformTypeLibCreationAndRegistration() with the exception that when TypeLibConverter.ConvertAssemblyToTypeLib() is called, the ITypeLibExporterNotifySink parameter is hard-coded to a new instance of ConversionEventHandler.
    • This means that if the ManagedCOMDefinitions assembly also references other assemblies for types which are COM-visible, ConversionEventHandler.ResolveRef() will again be called.
    • This time, the latest referenced assembly will be passed as parameter and the cycle re-starts.
    • In our current sample, ManagedCOMDefinitions does not reference any other assemblies.
    • Hence the current ResolveRef() will simply return after the call to TypeLibConverter.ConvertAssemblyToTypeLib().
  • The object returned from the ConvertAssemblyToTypeLib() call will implement the ICreateTypeLib interface and we will use it to call SaveAllChanges().
  • This will cause the physical creation of ManagedCOMDefinitions.tlb.
  • The object returned from the call to ConvertAssemblyToTypeLib() will then be cast to ITypeLib.
  • The RegisterTypeLib() API is then called to register the newly minted type library.
  • Now that ManagedCOMDefinitions.tlb has been created and registered, the framework will generate the importlib attribute into ManagedImplementation.tlb, i.e. :
// TLib :  : {E6EC9A6A-D559-4D06-B3A5-8AAE06729133}
importlib("ManagedCOMDefinitions.tlb");

5.10 As ConversionEventHandler.ResolveRef() returns, we are brought back to PerformTypeLibCreationAndRegistration() with the following remaining code to execute :

// SaveAllChanges() will create the TypeLib physical file
// based on strTargetTypeLibFullPath.
create_typeLib.SaveAllChanges();

ITypeLib typelib = (ITypeLib)create_typeLib;

UInt32 uiRetTemp = RegisterTypeLib(typelib, strTargetTypeLibFullPath, null);

if (uiRetTemp == 0)
{
    Console.WriteLine(string.Format("TypeLib File [{0:S}] registered.", strTargetTypeLibFullPath));
}
else
{
    Console.WriteLine(string.Format("Failed to register TypeLib File [{0:S}]. Error code : [{1:D}]",
        strTargetTypeLibFullPath, uiRetTemp));
    return false;
}

return true;
  • SaveAllChanges() will be called to create the ManagedImplementation.tlb type library file.
  • The RegisterTypeLib() API will be invoked and ManagedImplementation.tlb will be registered.

6. Test COM Client Application.

6.1 The following is a listing of the client program (a console application) :

#include "stdafx.h"
#import "..\ManagedImplementation\bin\Debug\ManagedCOMDefinitions.tlb" raw_interfaces_only no_implementation 
using namespace ManagedCOMDefinitions;
#import "..\ManagedImplementation\bin\Debug\ManagedImplementation.tlb" raw_interfaces_only no_implementation 
using namespace ManagedImplementation;

void DoTest()
{
    IManagedInterfacePtr spIManagedInterface = NULL;

    spIManagedInterface.CreateInstance(__uuidof(ManagedImpl01));

    if (spIManagedInterface)
    {
        spIManagedInterface->TestMethod01();
    }
}

int main()
{
    ::CoInitialize(NULL);

    DoTest();

    ::CoUninitialize();

    return 0;
}

  • ManagedCOMDefinitions.tlb and ManagedImplementation.tlb are both imported.
  • This code will only compile if both type libraries have been generated and registered.
  • The DoTest() method contains the main testing code.
  • DoTest() simply instantiates an IManagedInterface pointer to the ManagedImpl01 coclass.
  • And then IManagedInterface.TestMethod01() is called.

At runtime, the following console output will be displayed :

ManagedImplementation.ManagedImpl01 IManagedInterface.TestMethod01()

7. A Simple Experiment.

7.1 Now that ManagedCOMDefinitions.tlb has been created and registered, let’s re-run RegisterAssembly.exe with the TYPELIB flag using the Visual Studio debugger.

7.2 This time, inside PerformTypeLibCreationAndRegistration(), when we reach the line :

ICreateTypeLib create_typeLib = (ICreateTypeLib)(converter.ConvertAssemblyToTypeLib
  (assembly, strTargetTypeLibFullPath, flags, m_pITypeLibExporterNotifySink));
  • ConversionEventHandler.ResolveRef() will not be called by the framework.
  • This is because the ManagedCOMDefinitions.tlb type library has been created and registered.
  • Its information can thus be used in the generation of ManagedImplementation.tlb.

7.3 Now, try deleting ManagedCOMDefinitions.tlb or temporarily rename it.

7.4 Re-run RegisterAssembly.exe with the TYPELIB flag using the Visual Studio debugger.

7.5 This time ConversionEventHandler.ResolveRef() will be called again.

8. In Summary.

8.1 At the end of the day a type library (associated with a type exported from a managed source) will be required in order that the type be referenced from an unmanaged client application.

8.2 If there are any other types exported from referenced assemblies, then the referenced assemblies must be each be registered and their type libraries created and registered.

8.3 It is not sufficient that the main assembly be registered.

8.4 In this blog, we have explored how to perform type library creation for referenced assemblies which are (for want of a better way) “true” assemblies (i.e. assemblies which contain both metadata and code).

8.5 In Part 2 of this series of blogs, we shall look at referenced assemblies which are Interop Assemblies (i.e. assemblies which originate from a COM type library).

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: