Assembly to Type Library 之 Exported Type Conversion (Describes how the export process converts classes, interfaces, val)
This topic describes how the export process converts the following types:
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 [C#]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 [C#]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 [C#]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 [C#]// 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 [C#]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 [C#][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 [C#][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.