Exported Type Conversion

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

  • Classes

  • Interfaces

  • Value types

  • Enumerations

In general, exported types retain the same name they had within an assembly, excluding the namespace associated with the managed name. For example, the type A.B.IList in the following code sample converts to IList in the exported type library. A COM client can refer to the type as IList instead of A.B.IList.

Namespace A
    Namespace B
        Interface IList
               …       
        End Interface
    End Namespace
End Namespace
namespace A {
    namespace B {
        interface IList {
               …
        }
    }
}

With this approach, type names within an assembly can potentially collide because types in different namespaces can have the same name. When the export process detects a collision, it retains the namespace to eliminate naming ambiguities. The following code sample shows two namespaces with the same type name.

Namespace A
    Namespace B
        Public Class LinkedList
            Implements IList
        End Class
      
        Public Interface IList
        End Interface 
    End Namespace
End Namespace

Namespace C
    Public Interface IList
    End Interface
End Namespace
namespace A {
    namespace B {
        public class LinkedList : IList {…}
        public interface IList {…}
    }
}
   namespace C {
       public interface IList {…}
}

The following type library representation shows the resolution of each type name. Also, since periods are not valid in type library names, the export process replaces each period with underscores.

Type library representation

library Widgets 
{
    […]
    coclass LinkedList 
    {
        interface A_B_IList
    };

    […]
    interface A_B_IList {…};
    […]
    interface C_IList {…};
};

The export process also automatically generates a programmatic identifier (ProgId) by combining the namespace and type name. For example, the ProgId generated for the managed LinkedList class shown in the previous examples is A.B.LinkedList.

Combining the namespace and type name can result in an invalid ProgId. A ProgId is limited to 39 characters and can contain no punctuation characters other than periods. To avoid these limitations, you can specify a ProgId in your source code by applying the ProgIdAttribute, rather than allowing the export process to generate an identifier for you.

Classes

The export process converts each public class (that omits the ComVisible (false) attribute) in an assembly to a coclass in a type library. An exported coclass has neither methods nor properties; however, it retains the name of the managed class and implements all interfaces explicitly implemented by the managed class.

The following code example shows the definition of the IShape interface and the Circle class, which implements IShape. The converted type library representation follows the code example.

Public Interface IShape
    Sub Draw()
    Sub Move(x As Integer, y As Integer)
End Interface

Class Circle
    Implements IShape
    Sub Draw Implements IShape.Draw
    …    
    Sub Move(x As Integer, y As Integer) Implements IShape.Move
    …
    Sub Enlarge(x As Integer)
    …
End Class
public interface IShape {
    void Draw();
    void Move(int x, int y);
}
class Circle : IShape  {
    void Draw();
    void Move(int x, int y);
    void Enlarge(int x);
}

Type library representation

[ uuid(…), dual, odl, oleautomation ]
interface IShape : IDispatch {
    HRESULT Draw();
    HRESULT Move(int x, int y);
}

[ uuid(…) ]
coclass Circle {
    interface IShape;
}

Each coclass can implement one other interface, called the class interface, which the export process can generate automatically. The class interface exposes all methods and properties available in the original managed class, thereby enabling COM clients to access them by calling through the class interface.

You can assign a specific universal unique identifier (UUID) to the class by applying the GuidAttribute immediately above the managed class definition. During the conversion process, the export process transfers the value provided to the GuidAttribute to the UUID in the type library. Otherwise, the export process obtains UUIDs from a hash that includes the complete name of the class, including the namespace. Using the full name ensures that a class with a given name in a given namespace always generates the same UUID, and that two classes with different names never generate the same UUID.

Abstract classes and classes without public, default constructors are marked with the noncreatable type library attribute. Other type library attributes that apply to coclasses, such as licensed, hidden, restricted, and control are not set.

Interfaces

The export process converts managed interfaces to COM interfaces with the same methods and properties as the managed interface, but the method signatures differ considerably.

Interface Identities

COM interfaces include an interface identifier (IID) to distinguish one interface from another. You can assign a fixed IID to any managed interface by applying the GuidAttribute attribute. If you omit this attribute and do not assign a fixed IID, the export process automatically assigns one during the conversion. A runtime-assigned IID comprises the interface name (including namespace) and the complete signature of all methods defined within the interface. By reordering the methods on the managed interface or by changing method argument and return types, you change the IID assigned to that interface. Changing a method name does not affect the IID.

Using the QueryInterface method implemented by the runtime, COM clients can obtain an interface having either a fixed IID or runtime-assigned IID. IIDs generated by the runtime are not persisted within the metadata for the type.

Interface Types

Unless you specify otherwise, the export process converts all managed interfaces to dual interfaces in a type library. Dual interfaces enable COM clients to choose between early and late binding.

You can apply the InterfaceTypeAttribute attribute to an interface to selectively indicate that the interface should be exported as a dual interface, an IUnknown-derived interface, or a dispatch-only interface (dispinterface). All exported interfaces extend directly from either IUnknown or IDispatch, regardless of their inheritance hierarchy in managed code.

The following code example shows the optional values for controlling the interface type. Once exported to a type library, these options produce the results shown in the type library representation that follows the code.

' Creates a Dual interface by default.
Public Interface InterfaceWithNoInterfaceType
    Sub test()
End Interface

' Creates a Dual interface explicitly.
<InterfaceType(ComInterfaceType.InterfaceIsDual)> _
Public Interface InterfaceWithInterfaceIsDual
    Sub test()
End Interface

' Creates an IUnknown interface (not dispatch).
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
Public Interface InterfaceWithInterfaceIsIUnknown
    Sub test()
End Interface

' Creates a Dispatch-only interface (dispinterface).
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface InterfaceWithInterfaceIsIDispatch
    Sub test()
End Interface
// Creates a Dual interface by default.
public interface InterfaceWithNoInterfaceType {
    void test();
}
// Creates a Dual interface explicitly.
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface InterfaceWithInterfaceIsDual {
    void test();
}
// Creates an IUnknown interface (not dispatch).
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface InterfaceWithInterfaceIsIUnknown {
    void test();
}
// Creates a Dispatch-only interface(dispinterface).
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface InterfaceWithInterfaceIsIDispatch {
    void test();
}

Type library representation

[ odl, uuid(…), dual, oleautomation ]
interface InterfaceWithNoInterfaceType : IDispatch {
    HRESULT test();
};
[ odl, uuid(…), dual, oleautomation ]
interface InterfaceWithInterfaceIsDual : IDispatch {
     HRESULT test();
};
[ odl, uuid(…), oleautomation ]
interface InterfaceWithInterfaceIsIUnknown : IUnknown {
     HRESULT test();
};
[ uuid(…) ]
dispinterface InterfaceWithInterfaceIsIDispatch {
     properties:
     methods:
         void test();
};

Most interfaces are marked with the odl, and oleautomation type library attributes during the export process. (Dispinterfaces are the exception). Dual interfaces are marked with the dual type library attribute. A dual interface derives from the IDispatch interface, but it also exposes vtable slots for its methods.

Class Interfaces

For a complete description of the class interface and for usage recommendations, see Introducing the Class Interface. The export process can generate this interface automatically on behalf of a managed class without an interface explicitly defined in managed code. COM clients cannot access class methods directly.

The following code example shows a base class and a derived class. Neither class implements an explicit interface. The export process provides a class interface for both managed classes.

Public Class BaseClassWithClassInterface
    Private Shared StaticPrivateField As Integer
    Private PrivateFld As Integer
   
    Private Property PrivateProp() As Integer
        Get
            Return 0
        End Get
        Set
        End Set
    End Property
   
    Private Sub PrivateMeth()
        Return
    End Sub 
    Friend Shared StaticInternalField As Integer
    Friend InternalFld As Integer
   
    Friend Property InternalProp() As Integer
        Get
            Return 0
        End Get
        Set
        End Set
    End Property
   
    Friend Sub InternalMeth()
        Return
    End Sub 
    Public Shared StaticPublicField As Integer
    Public PublicFld As Integer
   
    Public Property PublicProp() As Integer
        Get
            Return 0
        End Get
        Set
        End Set
    End Property
   
    Public Sub PublicMeth()
        Return
    End Sub
End Class
 
Public Class DerivedClassWithClassInterface
    Inherits BaseClassWithClassInterface
   
    Public Sub Test()
        Return
    End Sub
End Class
public class BaseClassWithClassInterface {
    private  static int  StaticPrivateField;
    private  int  PrivateFld;
    private  int  PrivateProp{get{return 0;} set{;}}
    private  void PrivateMeth() {return;}

    internal static int  StaticInternalField;
    internal int  InternalFld;
    internal int  InternalProp{get{return 0;} set{;}}
    internal void InternalMeth() {return;}

    public   static int  StaticPublicField;
    public   int  PublicFld;
    public   int  PublicProp{get{return 0;} set{;}}
    public   void PublicMeth() {return;}
}

public class DerivedClassWithClassInterface : BaseClassWithClassInterface {
    public void Test() {return;}
}

Type library representation

[odl,uuid(…), hidden, dual, nonextensible, oleautomation]
interface _BaseClassWithClassInterface : IDispatch {
     [id(00000000),propget]   HRESULT ToString([out, retval] BSTR* p);
     [id(0x60020001)]         HRESULT Equals([in] VARIANT obj, 
                                [out, retval] VARIANT_BOOL* p);
     [id(0x60020002)]         HRESULT GetHashCode([out,retval] long* p);
     [id(0x60020003)]         HRESULT GetType([out, retval] _Type** p);
     [id(0x60020004),propget] HRESULT PublicProp([out,retval] long* p);
     [id(0x60020004),propput] HRESULT PublicProp([in] long p);
     [id(0x60020006)]         HRESULT PublicMeth();
     [id(0x60020007),propget] HRESULT PublicFld([out, retval]long* p);
     [id(0x60020007),propput] HRESULT PublicFld([in] long p);
};
[odl,uuid(…), hidden, dual, nonextensible, oleautomation]
interface _DerivedClassWithClassInterface : IDispatch {
     [id(00000000),propget]   HRESULT ToString([out, retval] BSTR* p);
     [id(0x60020001)]         HRESULT Equals([in] VARIANT obj, 
                                [out, retval] VARIANT_BOOL* p);
     [id(0x60020002)]         HRESULT GetHashCode([out,retval] long* p);
     [id(0x60020003)]         HRESULT GetType([out, retval] _Type** p);
     [id(0x60020004),propget] HRESULT PublicProp([out,retval] long* p);
     [id(0x60020004),propput] HRESULT PublicProp([in] long p);
     [id(0x60020006)]         HRESULT PublicMeth();
     [id(0x60020007),propget] HRESULT PublicFld([out, retval]long* p);
     [id(0x60020007),propput] HRESULT PublicFld([in] long p);
     [id(0x60020008)]         HRESULT Test();
}

The exported class interfaces have the following characteristics:

  • Each class interface retains the name of the managed class, but is prefixed with an underscore. When an interface name conflicts with a previously defined interface name, the new name is appended with an underscore and an incremental number. For instance, the next available name for _ClassWithClassInterface is _ClassWithClassInterface_2.

  • The export process always generates new interface identifiers (IID). You cannot explicitly set the IID of the class interface.

  • By default, both class interfaces derive from IDispatch interfaces.

  • The interfaces have ODL, dual, hidden, nonextensible and oleautomation attributes.

  • Both interfaces have all the public members of its base class (System.Object).

  • They do not contain the private or internal members of the class.

  • Each member is automatically assigned a unique DispId. The DispIds can be set explicitly by applying DispIdAttribute to the member of the class.

  • Method signatures are transformed to return HRESULTs and have [out, retval] parameters.

  • The properties and fields are transformed into [propget], [propput], and [propputref].

Default Interface

COM has the notion of a default interface. Members of the default interface are treated as the members of the class by late-bound languages like Visual Basic. In the .NET Framework, there is no need for a default interface because classes themselves can have members. However, when exposing classes to COM, classes are much easier to use if they have a default interface.

When a managed class is exported to a type library as a coclass, one interface is typically identified as the default interface for the class. If no interface is identified as the default in the type library, most COM applications assume that the first implemented interface is the default interface of that coclass.

As the following code example shows, the export process converts a managed class that has no class interface and marks the first implemented interface as the default in the exported type library. The type library representation of the converted class follows the code example.

<ClassInterface(ClassInterfaceType.None)> _
Public Class ClassWithNoClassInterface
    Implements IExplicit
    Implements IAnother
    Sub M()
    …
End Class
[ClassInterface(ClassInterfaceType.None)]
public class ClassWithNoClassInterface : IExplicit, IAnother {
    void M();   
}

Type library representation

coclass ClassWithNoClassInterface {
    [default] IExplicit; 
    IAnother;
}

The export process always marks the class interface as the default interface for the class, regardless of any other interface that the class implements explicitly. The following example shows two classes.

<ClassInterface(ClassInterfaceType.AutoDispatch)> _
Public Class ClassWithAutoDispatch
    Implements IAnother
    Sub M()
    …
End Class 
 
<ClassInterface(ClassInterfaceType.AutoDual)> _
Public Class ClassWithAutoDual
    Implements IAnother
    Sub M()
    …
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class ClassWithAutoDispatch : IExplicit, IAnother {
    void M();   
}

[ClassInterface(ClassInterfaceType.AutoDual)]
public class ClassWithAutoDual : IExplicit, IAnother {
    void M();   
}

Type library representation

// ClassWithAutoDispatch: IDispatch
coclass ClassWithAutoDispatch {
    [default] _ClassWithAutoDispatch;
    interface _Object;
    IExplicit;
    IAnother;
}
interface _ClassWithAutoDual {…}

coclass ClassWithAutoDual {
    [default] _ClassWithAutoDual; 
    IExplicit;
    IAnother;
}

Value Types

Value types (types that extend System.Value) are exported to type libraries as C-style structures with the type definition. The layout of the structure members is controlled with the StructLayoutAttribute attribute, which is applied to the type. Only the fields of the value type are exported. If a value type has methods, they are inaccessible from COM.

For example:

[StructLayout(LayoutKind.Sequential)]
public struct Point {
    int x;
    int y;
    public void SetXY(int x, int y){ 
        this.x = x;
        this.y = y;
    }
};

The Point value type is exported to COM as a point typedef, as shown in the following example:

typedef 
[uuid(…)]
struct tagPoint {
    short x;
    short y;
} Point;

Notice that the conversion process removes the SetXY method from the typedef.

Enumerations

The export process adds a managed enumeration to type libraries as an enumeration with the member names altered to ensure the unique member naming. To ensure that each member name is unique, Tlbexp.exe prefixes an underscore to the name of the enumeration to each member during the export process. For example, the following simple enumeration produces a set of type library representations.

Enum DaysOfWeek {
    Sunday = 0;
    Monday;
    Tuesday;
    …
};

Type library representation

enum DaysOfWeek {
    DaysOfWeek_Sunday = 0;
    DaysOfWeek_Monday;
    DaysOfWeek_Tuesday;
    …
};

The runtime scopes managed enumeration members to the enumeration in which they belong. For instance, all references to Sunday in the DaysOfWeek enumeration, shown in the previous example, must be qualified with DaysOfWeek. You cannot reference Sunday in place of DaysOfWeek.Sunday. Unique member naming is a requirement of COM enumerations.

See Also

Concepts

Exported Assembly Conversion

Exported Module Conversion

Exported Member Conversion

Exported Parameter Conversion

Other Resources

Assembly to Type Library Conversion Summary