导出成员转换
更新:2007 年 11 月
本主题说明导出过程如何转换以下成员:
方法
属性
事件
方法
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 interop 中的事件模型,请参见托管和非托管事件。托管类型使用基于委托的事件模型来实现事件。例如,下面的代码示例中的 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;
};