导出成员转换

本主题说明导出过程如何转换以下成员:

  • 方法

  • 属性

  • 事件

方法

COM 客户端需要调用方法,以传递熟悉的 COM 数据类型(如参数)并接收 HRESULT。 但在 .NET 环境中,您的类对返回类型不存在这样的限制(实际上不使用 HRESULT)。

为了满足这两种模型,托管类型的每个方法都具有一个 .NET 签名和一个暗含的 COM 签名。 这两个签名通常具有很大的差异。 .NET 客户端使用 .NET 签名与服务器进行交互,而(可能在同一时间)COM 客户端使用 COM 签名与服务器进行交互。 服务器将实现带有 .NET 签名的方法,而运行时封送处理服务将负责提供带有 COM 签名的存根 (stub),该存根会将调用委托给托管方法。

28w1w83f.collapse_all(zh-cn,VS.110).gifHRESULT 转换

通过将托管返回值更改为 [out, retval] 参数,并将非托管返回值的类型更改为 HRESULT,可以将托管签名转换为非托管签名。 例如,DoSomething 方法可能具有以下签名:

28w1w83f.collapse_all(zh-cn,VS.110).gif托管签名

short DoSomething(short i);

28w1w83f.collapse_all(zh-cn,VS.110).gif非托管签名

HRESULT DoSomething([in] short i, [out, retval] short *rv);

请注意,COM 签名返回一个 HRESULT,而且对于返回值具有附加的 out 参数。 来自托管实现的返回值始终会作为添加到非托管签名末尾的 [out, retval] 参数来返回,而非托管签名始终返回 HRESULT。 如果托管方法具有 void 返回值,运行时将省略 [out, retval] 参数。 例如:

28w1w83f.collapse_all(zh-cn,VS.110).gif托管签名

void DoSomething(short i);

28w1w83f.collapse_all(zh-cn,VS.110).gif非托管签名

HRESULT DoSomething([in] short i);

某些情况下,最好使托管签名保持不变。 您可以使用 PreserveSigAttribute 来实现这一目的。 例如:

28w1w83f.collapse_all(zh-cn,VS.110).gif托管签名

[PreserveSig] short DoSomething(short i);

28w1w83f.collapse_all(zh-cn,VS.110).gif非托管签名

short DoSomething ([in] short i);

由于具有两种不同的方法签名,很容易无缝地使用 COM 和 .NET 客户端中的类。 此外,COM 和 .NET 客户端可以同时使用一个 .NET 类。 作为类的作者,您仅实现托管签名。 利用 Tlbexp.exe(或等效 API),可以将签名自动导出到为类创建的类型库中。

28w1w83f.collapse_all(zh-cn,VS.110).gif重载方法

虽然 .NET 支持重载方法,但 IDispatch 接口仅依赖于方法名称(而不是完整的方法签名)来进行绑定。 因此,该接口无法支持重载方法。 不过,为了提供对类型的重载方法的访问权,Tlbexp.exe 将用一个序号来修饰重载方法的名称,以便使每个方法名称都是唯一的。

以下托管和非托管签名显示了包括序号的情况:

28w1w83f.collapse_all(zh-cn,VS.110).gif托管签名

interface INew {
public:
    void DoSomething();
    void DoSomething(short s);
    void DoSomething(short l);
    void DoSomething(float f);
    void DoSomething(double d);
}

28w1w83f.collapse_all(zh-cn,VS.110).gif非托管签名

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] 函数,如以下类型库表示形式所示。

28w1w83f.collapse_all(zh-cn,VS.110).gif类型库表示形式

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 接口标记为源接口。

28w1w83f.collapse_all(zh-cn,VS.110).gif类型库表示形式

    disinterface Class1Event {
        properties:
        methods:
        [id(0x60020000)]
        HRESULT Click();
    };

    coclass Class1
    {
    …
    [default, source] Class1Event;
    };

请参见

概念

导出程序集转换

导出模块转换

导出类型转换

导出参数转换

其他资源

有关从程序集转换到类型库的摘要