导出类型转换
更新:2007 年 11 月
本主题描述导出过程如何转换以下类型:
类
接口
值类型
枚举
总的说来,导出的类型会保留它们在程序集中具有的相同名称,但与托管名称相关联的命名空间除外。例如,下面的代码示例中的 A.B.IList 类型转换为导出类型库中的 IList。COM 客户端可以将类型当作 IList(而不是 A.B.IList)来引用。
Namespace A
Namespace B
Interface IList
…
End Interface
End Namespace
End Namespace
namespace A {
namespace B {
interface IList {
…
}
}
}
当采用这种方法时,由于不同命名空间中的类型可以具有相同的名称,一个程序集中的类型名称可能会发生冲突。当导出过程检测到冲突时,它将保留命名空间,以避免不明确的命名。下面的代码示例显示两个具有同一类型名称的命名空间。
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 {…}
}
以下类型库表示形式显示了每个类型名称的解析过程。此外,由于句点在类型库名称中无效,导出过程会将每个句点替换为下划线。
类型库表示形式
library Widgets
{
[…]
coclass LinkedList
{
interface A_B_IList
};
[…]
interface A_B_IList {…};
[…]
interface C_IList {…};
};
导出过程还会自动通过组合命名空间和类型名称来生成编程标识符 (ProgId)。例如,为以上示例所示的托管 LinkedList 类生成的 ProgId 是 A.B.LinkedList。
组合命名空间和类型名称后,可能会生成无效的 ProgId。一个 ProgId 仅限于 39 个字符,并且不能包含句点之外的任何标点字符。若要避免这些限制,可以通过应用 ProgIdAttribute 在源代码中指定 ProgId,而不是让导出过程为您生成标识符。
类
导出进程会将程序集中的每个公共类(省略 ComVisible (false) 属性)转化为类型库中的 coclass。导出的 coclass 既不具有方法,也不具有属性;但是,它将保留托管类的名称并实现所有由托管类显式实现的接口。
下面的代码示例显示 IShape 接口以及实现 IShape 的 Circle 类的定义。该代码示例之后是转换的类型库表示形式。
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);
}
类型库表示形式
[ uuid(…), dual, odl, oleautomation ]
interface IShape : IDispatch {
HRESULT Draw();
HRESULT Move(int x, int y);
}
[ uuid(…) ]
coclass Circle {
interface IShape;
}
每个 coclass 都可以实现一个其他的接口,该接口称作类接口,可以由导出过程自动生成。类接口将公开原托管类中的所有可用方法和属性,从而使 COM 客户端能够通过类接口调用并访问这些方法和属性。
通过紧接在托管类定义之上应用 GuidAttribute,可以给托管类分配特定的通用唯一标识符 (UUID)。在转换过程中,导出过程会将给 GuidAttribute 提供的值传递给类型库中的 UUID。否则,导出过程将从包括完整类名(包括命名空间)的哈希中获取 UUID。通过使用完整名称,可以确保给定命名空间中具有给定名称的类始终会生成同一个 UUID,而名称不同的两个类决不会生成同一个 UUID。
抽象类和没有公共、默认构造函数的类用 noncreatable 类型库属性来标记。将不设置其他适用于 coclass 的类型库属性,如 licensed、hidden、restricted 和 control。
接口
导出过程会将托管接口转换为与托管接口具有相同方法和属性的 COM 接口,但它们的方法签名却存在很大的差异。
接口标识
COM 接口包含接口标识符 (IID),用以互相区别。通过应用 GuidAttribute 属性,可以将固定的 IID 分配给任何托管接口。如果省略此属性并且未分配固定的 IID,导出过程将在转换时自动分配一个 IID。由运行库分配的 IID 包括接口名称(包括命名空间)和在该接口内定义的所有方法的完整签名。通过对托管接口的方法重新排序或更改方法参数和返回类型,可以更改分配给该接口的 IID。更改方法名称不会影响 IID。
利用运行库所实现的 QueryInterface 方法,COM 客户端可以获得具有固定 IID 或由运行库分配的 IID 的接口。由运行库分配的 IID 不会在元数据中为该类型一直存在。
接口类型
除非另作指定,导出过程会将所有托管接口转换为类型库中的双绑定接口。双绑定接口使 COM 客户端能够在早期绑定和后期绑定之间进行选择。
您可以选择将 InterfaceTypeAttribute 属性应用于接口,以指示应将该接口导出为双绑定接口、IUnknown 导出接口或仅调度接口 (dispinterface)。所有导出的接口都将直接从 IUnknown 或 IDispatch 扩展,无论它们在托管代码中处于何种继承层次结构。
下面的代码示例显示用于控制接口类型的可选值。当导出到类型库之后,这些选项将产生类型库表示形式(在代码示例之后)所示的结果。
' 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();
}
类型库表示形式
[ 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();
};
在导出进程中,大多数接口都用 odl 以及 oleautomation 类型库属性来标记。(调度接口属于例外)。双绑定接口用 dual 类型库属性来标记。双重接口从 IDispatch 接口导出,但它也对其方法公开 vtable 槽。
类接口
如需有关类接口的完整说明以及用法建议,请参见类接口简介。导出过程可以代表托管类自动生成此接口,而无须在托管代码中显式定义接口。COM 客户端无法直接访问类方法。
下面的代码示例显示一个基类和一个派生类。这两个类都不实现显式接口。导出过程将为这两个托管类提供类接口。
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;}
}
类型库表示形式
[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();
}
导出的类接口具有下列特性:
每个类接口都保留托管类的名称,但带有下划线前缀。当某个接口名称与先前定义的接口名称发生冲突时,新的名称将附加一个下划线和一个递增的数字。例如,_ClassWithClassInterface 后的下一个可用名称为 _ClassWithClassInterface_2。
导出过程始终会生成新的接口标识符 (IID)。您不能显式地设置类接口的 IID。
默认情况下,两种类接口都从 IDispatch 接口导出。
这些接口具有 ODL、dual、hidden、nonextensible 和 oleautomation 属性。
两种接口都具有其基类 (System.Object) 的所有公共成员。
它们不包含类的私有或内部成员。
将自动为每个成员分配一个唯一的 DispId。可以通过将 DispIdAttribute 应用于类的成员来显式设置 DispId。
方法签名将经过转换,返回 HRESULT 并具有 [out, retval] 参数。
属性和字段将转换为 [propget]、[propput] 和 [propputref]。
默认接口
COM 具有默认接口的概念。默认接口的成员将由类似于 Visual Basic 的后期绑定语言当作类的成员来进行处理。在 .NET Framework 中,由于类本身就可以具有成员,因此无须使用默认接口。但是,当向 COM 公开类时,如果类具有默认接口,使用起来会容易得多。
当托管类作为 coclass 导出到类型库时,通常会将一个接口标识为该类的默认接口。如果没有将任何接口标识为类型库中的默认接口,大多数 COM 应用程序将假定所实现第一个接口为该 coclass 的默认接口。
如下面的代码示例所示,导出过程将转换没有类接口的托管类,并将所实现的第一个接口标识为导出类型库中的默认接口。该代码示例之后是所转换类的类型库表示形式。
<ClassInterface(ClassInterfaceType.None)> _
Public Class ClassWithNoClassInterface
Implements IExplicit
Implements IAnother
Sub M()
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class ClassWithNoClassInterface : IExplicit, IAnother {
void M();
}
类型库表示形式
coclass ClassWithNoClassInterface {
[default] IExplicit;
IAnother;
}
无论类显式地实现了其他哪些接口,导出进程始终会将类接口标记为类的默认接口。下面的示例显示了两个类。
<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();
}
类型库表示形式
// ClassWithAutoDispatch: IDispatch
coclass ClassWithAutoDispatch {
[default] _ClassWithAutoDispatch;
interface _Object;
IExplicit;
IAnother;
}
interface _ClassWithAutoDual {…}
coclass ClassWithAutoDual {
[default] _ClassWithAutoDual;
IExplicit;
IAnother;
}
值类型
值类型(扩展 System.Value 的类型)将当作带有类型定义的 C 样式结构导出到类型库中。结构成员的布局通过应用于该类型的 StructLayoutAttribute 属性来控制。仅导出值类型的字段。如果值类型具有方法,则无法从 COM 访问这些方法。
例如:
[StructLayout(LayoutKind.Sequential)]
public struct Point {
int x;
int y;
public void SetXY(int x, int y){
this.x = x;
this.y = y;
}
};
Point 值类型当作点 typedef 导出到 COM 中,如下例所示:
typedef
[uuid(…)]
struct tagPoint {
short x;
short y;
} Point;
请注意,此转换过程从 typedef 中移除了 SetXY 方法。
枚举
导出过程会将托管枚举当作已更改成员名称的枚举添加到类型库中,成员名称在经过更改后可确保成员命名的唯一性。为了确保每个成员名称都是唯一的,Tlbexp.exe 将在导出时在每个成员的枚举名称前加上下划线前缀。例如,以下简单枚举会生成一组类型库表示形式。
Enum DaysOfWeek {
Sunday = 0;
Monday;
Tuesday;
…
};
类型库表示形式
enum DaysOfWeek {
DaysOfWeek_Sunday = 0;
DaysOfWeek_Monday;
DaysOfWeek_Tuesday;
…
};
运行库将托管枚举成员的范围限制在它们所属的枚举。例如,对 DaysOfWeek 枚举中 Sunday 的所有引用(如上例所示)必须用 DaysOfWeek 来限定。您不能引用 Sunday 来替代 DaysOfWeek.Sunday。成员命名的唯一性是 COM 枚举的一项要求。