导出成员转换
本主题说明导出过程如何转换以下成员:
方法
属性
事件
方法
COM 客户端需要调用方法,以传递熟悉的 COM 数据类型(如参数)并接收 HRESULT。 但在 .NET 环境中,您的类对返回类型不存在这样的限制(实际上不使用 HRESULT)。
为了满足这两种模型,托管类型的每个方法都具有一个 .NET 签名和一个暗含的 COM 签名。 这两个签名通常具有很大的差异。 .NET 客户端使用 .NET 签名与服务器进行交互,而(可能在同一时间)COM 客户端使用 COM 签名与服务器进行交互。 服务器将实现带有 .NET 签名的方法,而运行时封送处理服务将负责提供带有 COM 签名的存根 (stub),该存根会将调用委托给托管方法。
HRESULT 转换
通过将托管返回值更改为 [out, retval] 参数,并将非托管返回值的类型更改为 HRESULT,可以将托管签名转换为非托管签名。 例如,DoSomething 方法可能具有以下签名:
托管签名
short DoSomething(short i);
非托管签名
HRESULT DoSomething([in] short i, [out, retval] short *rv);
请注意,COM 签名返回一个 HRESULT,而且对于返回值具有附加的 out 参数。 来自托管实现的返回值始终会作为添加到非托管签名末尾的 [out, retval] 参数来返回,而非托管签名始终返回 HRESULT。 如果托管方法具有 void 返回值,运行时将省略 [out, retval] 参数。 例如:
托管签名
void DoSomething(short i);
非托管签名
HRESULT DoSomething([in] short i);
某些情况下,最好使托管签名保持不变。 您可以使用 PreserveSigAttribute 来实现这一目的。 例如:
托管签名
[PreserveSig] short DoSomething(short i);
非托管签名
short DoSomething ([in] short i);
由于具有两种不同的方法签名,很容易无缝地使用 COM 和 .NET 客户端中的类。 此外,COM 和 .NET 客户端可以同时使用一个 .NET 类。 作为类的作者,您仅实现托管签名。 利用 Tlbexp.exe(或等效 API),可以将签名自动导出到为类创建的类型库中。
重载方法
虽然 .NET 支持重载方法,但 IDispatch 接口仅依赖于方法名称(而不是完整的方法签名)来进行绑定。 因此,该接口无法支持重载方法。 不过,为了提供对类型的重载方法的访问权,Tlbexp.exe 将用一个序号来修饰重载方法的名称,以便使每个方法名称都是唯一的。
以下托管和非托管签名显示了包括序号的情况:
托管签名
interface INew {
public:
void DoSomething();
void DoSomething(short s);
void DoSomething(short l);
void DoSomething(float f);
void DoSomething(double d);
}
非托管签名
interface INew {
void DoSomething();
void DoSomething_2(short s);
void DoSomething_3(short l);
void DoSomething_4(float f);
void DoSomething_5(double d);
}
方法的 COM 签名显示为一系列修饰的 DoSomething_x 方法之后的单个 DoSomething 方法,其中 x 从 2 开始,为方法的每一种重载形式进行递增。 请注意,某些重载方法可以从基类型继承。 但是,无法保证重载方法将在类型版本向前发展时仍保留相同的数字。
虽然 .NET 客户端可以使用方法的重载形式,但 COM 客户端必须访问修饰的方法。 对象浏览器将显示具有方法签名的修饰方法的所有形式,以便于您选择正确的方法。 后期绑定的客户端也可以调用 IDispatch::GetIdsOfNames,并传入修饰名称,以获取任何重载方法的 DispID。
属性
托管类和接口可以具有属性。 托管属性具有特定的数据类型,该类型可能具有关联的获取方法和设置方法。 这些方法将与其他任何方法一样分别进行定义。 下面的代码示例显示一个包含 Height 属性的接口。 要为该属性提供获取和设置方法,必须使用实现接口的类。
interface IMammal {
IMammal Mother{get;set;}
IMammal Father{get;set;}
int Height{get;set;}
int Weight{get;set;}
}
class Human : IMammal
{
int weight;
int height;
IMammal father;
IMammal mother;
public IMammal Mother { get { return mother; } set { mother = value; } }
public IMammal Father { get { return father; } set { father = value; } }
public int Height { get { return height; } set { height = value; } }
public int Weight { get { return weight; } set { weight = value; } }
}
在导出过程中,Tlbexp.exe 将属性设置方法转换为 [propput],将获取方法转换为 [propget]。 COM 中的属性名称仍然与托管属性名称相同。 此规则具有以下例外:
如果属性类型(值类型除外)为类或接口,属性设置方法将成为 [propputref],从而为参数提供更高程度的非间接性。
如果属性不具有获取或设置方法,Tlbexp.exe 将从类型库中省略该属性。
与属性类似,托管字段将导出到类型库中。 运行时封送处理服务将自动为所有公共字段生成获取和设置方法。 在转换过程中,Tlbexp.exe 将为每个字段生成一个 [propput](或 [propputref])函数和一个 [propget] 函数,如以下类型库表示形式所示。
类型库表示形式
interface IMammal : IDispatch {
[propget] HRESULT Mother([out, retval] IMammal** pRetVal);
[propputref] HRESULT Mother([in] IMammal* pRetVal);
[propget] HRESULT Father([out, retval] IMammal** pRetVal);
[propputref] HRESULT Father([in] IMammal* pRetVal);
[propget] HRESULT Height([out, retval] long* pRetVal);
[propput] HRESULT Height([in] long pRetVal);
[propget] HRESULT Weight([out, retval] long* pRetVal);
[propput] HRESULT Weight([in] long pRetVal);
[propget] HRESULT Age([out, retval] long* pRetVal);
[propput] HRESULT Age([in] long pRetVal);
};
事件
如果您不熟悉 COM 互操作 中的事件模型,请参见托管和非托管事件。 托管类型使用基于委托的事件模型来实现事件。 例如,下面的代码示例中的 Class1Events 接口引发 Click 事件。
Public Delegate Sub ClickDelegate()
<GuidAttribute("1A585C4D-3371-48dc-AF8A-AFFECC1B0967"), _
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)>
Public Interface Class1Event
Sub Click ()
End Interface
<ComSourceInterfaces("Class1Event, EventSrc")> _
Public Class Class1
Public Event Click As ClickDelegate
End Class
public delegate void Click();
public interface Class1Event
{
void Click();
}
[ComSourceInterfaces("Class1Event, EventSrc")]
public class Class1
{
public event ClickDelegate Click;
}
在导出过程中,Tlbexp.exe 将事件接口标记为其 coclass 中的源。 如下面的类型库表示形式所示,导出的 ComClass1Events 接口标记为源接口。
类型库表示形式
disinterface Class1Event {
properties:
methods:
[id(0x60020000)]
HRESULT Click();
};
coclass Class1
{
…
[default, source] Class1Event;
};