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