Imported Type Conversion

This topic describes how the import process converts the following types:

  • Interfaces

  • Classes

  • Structures

  • Enumerations

  • Constants

  • Typedefs

In general, Tlbimp.exe imports types with the same name they had in the original type library. Names within a type library must be unique, thereby eliminating naming conflicts during the conversion process. All valid type-library names are valid assembly names.

Imported types are scoped by the namespace to which they belong, which is the same as the original type library. Types are individually identified by their complete namespace and type names.

You can explicitly control the managed name of an imported type by using a type library attribute in the type library. This user-defined attribute identifier is 0F21F359-AB84-41e8-9A78-36D110E6D2F9. The following type library shows the addition of the user-defined attribute.

Type library representation

[  uuid(…),
    version(1.0)
]
library AcmeLib {
    interface Widget {};
    [custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9, 
     "Acme.WidgetLib.Slingshot")]
    coclass Slingshot {};
};

Even though Tlbimp.exe imports the type library into the AcmeLib namespace, the Slingshot class becomes Acme.WidgetLib.Slingshot.

Interfaces

When the import process converts an interface, it strips out all IUnknown and IDispatch methods. The conversion applies the GuidAttribute to the interface to retain the interface identifier (IID) assigned in the type library and the InterfaceTypeAttribute unless the interface is dual (an interface deriving from IDispatch).

Type library representation

[uuid(…), ]
interface IWidget : IUnknown {
    HRESULT New()
    HRESULT Start()
};
[uuid(…), ]
interface IGadget : IWidget {
    HRESULT Baz()
};

During conversion, the import process adds the methods of the base interface to the derived interface. In the following example, New and Start are added to the IGadget interface:

<Guid(…), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
Interface IWidget
    Sub [New]()
    Sub Start()
End Interface

<Guid(…), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
Interface IGadget
    Inherits IWidget
    Shadows Sub [New]()
    Shadows Sub Start()
    Sub Baz()
End Interface
[Guid(…), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IWidget {
    void New();
    void Start();
};
[Guid(…), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IGadget : IWidget {
    new void New();
    new void Start();
    void Baz();
};

Classes

The import process creates a managed class to represent each COM coclass, giving the managed class the same name as the original coclass appended with Class. For example, the NewNewer coclass becomes NewNewerClass. The conversion adds the GuidAttribute to the class to capture the class identifier (CLSID) of the coclass.

In addition to the managed class, the import process adds an interface with the same name as the coclass and applies the CoClassAttribute to identify the CLSID of the original coclass. This interface has the same IID as the default interface for the coclass. With this interface, clients can always register as event sinks.

Unlike a COM coclass, a managed class can contain class members. In keeping with the .NET Framework approach, the conversion adds to each class the members associated with each interface implemented by the coclass. Users of the managed class can invoke methods and properties of the managed type without first casting to a specific interface. The import process also adds a default constructor to each converted coclass. A constructor makes it possible to create the class from managed code. (Classes without a constructor cannot be created). The default constructor has no arguments; its implementation calls the base class constructor. If the noncreatable type library attribute is applied to the coclass, the import process does not create a default constructor for the class.

Since interface member names are not always unique, name and DispId collisions among members can occur. TlbImp.exe resolves name collisions by prefixing the interface name and an underscore to the name of each colliding member of the class. Where the names of members collide, the first interface listed in the coclass statement remains unchanged.

When DispId collisions occur, the import process assigns DispIds to members of the coclass's default interface and none to the conflicting class members. However, the import process always assigns DispIds to members of the interface.

Type library representation

[uuid(…)]
interface INew : IDispatch {
    [id(0x100)] HRESULT DoFirst();
    [id(0x101)] HRESULT DoSecond();
}
[uuid(…)]
interface INewer : IDispatch {
    [id(0x100)] HRESULT DoNow();
    [id(0x101)] HRESULT DoSecond();
}
[uuid(…)]
coclass NewNewer  {
    [default] interface INew;
    interface INewer;
}

The converted types appear as follows:

<Guid(…)> Public Interface INew
    …
End Interface

<Guid(…)> Public Interface INewer
    …
End Interface

<Guid(…)> Public Interface NewNewer
Inherits INew
    …
End Interface

<Guid(…)> Public Class NewNewerClass
Implements INew   
Implements INewer
Implements NewNewer
' Method implementation
     <DispId(100)> _
      …
End Class  
[Guid(…)]
public interface INew {…}

[Guid(…)]
public interface INewer {…}

[Guid(…)]
public interface NewNewer : INew {…}

[Guid(…)]
public class NewNewer : INew, INewer, NewNewer{
// Method implementation.
     [DispId(100)]…
}

Structures

Structures defined within a type library are imported as metadata. If a field of a struct is a reference type, Tlbimp.exe imports the type as an IntPtr and applies the ComConversionLossAttribute. The attribute indicates that information was lost during the import process.

Enumerations

Type Library Importer (Tlbimp.exe) imports unmanaged enumerations as managed Enum types.

Constants

Constants will not be imported from type library in this release.

Typedefs

Type definitions (typedefs) within a type library are not imported. Instead, parameters and fields are imported as the underlying types. For example, a parameter of type BUTTON_COLOR is imported as type integer, since BUTTON_COLOR is an alias for an integer.

Once imported, parameters and fields carry information that associates them with their original type in the ComAliasNameAttribute. The conversion process applies ComAliasNameAttribute to associate a field, parameter, or return value with the name of the type library and the type within the library that was used as an alias.

The following type library representation shows that the cl parameter is typed as a BUTTON_COLOR, which is an alias for an integer.

Type library representation

library MyLib {
    typedef [public] int BUTTON_COLOR;

    interface ISee {
        HResult SetColor([in] BUTTON_COLOR cl);
        HResult GetColor([out, retval] BUTTON_COLOR *cl);
    };
   
    coclass See {
        [default] interface ISee
    };
};

The converted types appear as follows:

public interface ISee {
    void SetColor([ComAliasName("MyLib.BUTTON_COLOR")]] int cl);
    [return: ComAliasName("MyLib.BUTTON_COLOR")] int GetColor();
};

public class See {
    public void SetColor([ComAliasName("MyLib.BUTTON_COLOR")]] int cl);
    [return: ComAliasName("MyLib.BUTTON_COLOR")] int GetColor();
};

Note that the actual type BUTTON_COLOR is not defined in the resulting metadata. Instead, the parameters typed as BUTTON_COLOR in the type library are typed as the underlying type, int, and are attributed with the ComAliasNameAttribute. This conversion process also applies the attribute to arguments on class methods.

See Also

Concepts

Imported Library Conversion

Imported Module Conversion

Imported Member Conversion

Imported Parameter Conversion

Other Resources

Type Library to Assembly Conversion Summary