看到了吗 C++ [CX]第0部分[N]简介 有关本系列的介绍以及本系列每一篇文章的链接目录。
在本文中,我们将了解如何构造运行时类。我们将使用以下方法 Widget
贯穿本文的运行时类:
public ref class Widget sealed { public: Widget() : _number(0) { } Widget(int number) : _number(number) { } int GetNumber() { return _number; } private: int _number; };
此类型同时具有默认构造函数和具有 int
参数。C++ /CX运行时类构造函数与普通C++类的构造函数大体相同。与普通成员函数一样,作为运行时类公共接口一部分的任何构造函数只能在其签名中使用Windows运行时类型。此规则适用于公共运行时类的公共构造函数和受保护构造函数,因为它们构成运行时类的接口。否则,关于运行时类构造函数就没什么可说的了。
C++/CX增加了一个新的操作符, ref new
,用于构造运行时类的实例。例如,我们可以很容易地构造一个 Widget
使用其构造函数之一的实例:
Widget^ widgetZero = ref new Widget(); Widget^ widgetAnswer = ref new Widget(42);
行为 ref new
可与 new
:它接受要构造的运行时类和要传递给该运行时类的构造函数的一组参数,并构造该类型的实例。鉴于 new T()
产生 T*
指向新对象, ref new T()
产生 T^
. 在这方面, ref new
类似于 make_shared
可以用来安全地构造 shared_ptr
.
正如我们在前面的文章中看到的,除了告诉编译器 Widget
是Windows运行时类型(例如。, ^
和 ref
这个代码看起来和普通C++类型的C++代码完全一样。构造函数的声明方式相同,并且 ref new
在很大程度上与 new
已使用。然而,这种语法的简单性隐藏了相当多的复杂机制,这就是我们在这里要研究的。
因为C++ +CX隐藏了这里的所有复杂性,我们将使用WRL来解释对象构造是如何工作的。这样我们就有地方开始了,我们要翻译我们的 Widget
键入WRL。首先是声明 Widget
运行时类型及其默认接口, IWidget
:
[exclusiveto(Widget)] [uuid(ada06666-5abd-4691-8a44-56703e020d64)] [version(1.0)] interface IWidget : IInspectable { HRESULT GetNumber([out] [retval] int* number); } [version(1.0)] runtimeclass Widget { [default] interface IWidget; }
以及C++的定义 Widget
类型:
class Widget : public RuntimeClass<IWidget> { InspectableClass(RuntimeClass_WRLWidgetComponent_Widget, BaseTrust) public: Widget() : _number(0) { } Widget(int number) : _number(number) { } STDMETHODIMP GetNumber(int* number) { *number = _number; return S_OK; } private: INT32 _number; };
请注意:为简洁起见,本文中的大多数示例都省略了错误处理代码。在编写实际代码时,请确保处理错误条件,包括空指针和失败的HRESULT。
而这个C++ Widget
类定义了两个构造函数,这些构造函数是 实施细则 的 Widget
类型。召回 第1部分 我们只通过接口指针与运行时类对象交互:因为构造函数不是由任何接口声明的,所以它们不是运行时类的公共接口的一部分。
小部件来自哪里?
运行时类的结构是用于实现运行时类的特定语言和框架的实现细节;因此,这种类型的对象的构造方式也是一个实现细节,因为构造必然与类型的结构相联系。Windows运行时组件可以从任何支持Windows运行时的语言中使用,因此我们需要一种与语言无关的机制来构造运行时类对象。
Windows运行时使用 活化工厂 用于构造运行时类对象。激活工厂是一个运行时类,其目的是构造特定运行时类类型的对象。我们将定义一个 WidgetFactory
构造的激活工厂 Widget
物体。
与任何其他运行时类一样,激活工厂实现一组接口。每个激活工厂必须执行 IActivationFactory
接口,声明单个成员函数: ActivateInstance
. 这个 ActivateInstance
接口函数不接受任何参数,并返回一个默认的构造对象。激活工厂还可以实现定义其他“构造”功能的用户定义工厂接口。为了我们的 WidgetFactory
,我们将使用以下工厂界面:
[exclusiveto(Widget)] [uuid(5b197688-2f57-4d01-92cd-a888f10dcd90)] [version(1.0)] interface IWidgetFactory : IInspectable { HRESULT CreateInstance([in] int value, [out] [retval] Widget** widget); }
工厂接口只能声明工厂函数:每个函数必须采用一个或多个参数,并且必须返回运行时类的实例。这个 Widget
运行时类只有一个非默认构造函数,所以我们只需要在这里声明一个工厂函数,但是可以根据需要定义任意多的工厂函数。使用C++时,编译器会自动为每个公众生成一个工厂接口。 ref class
,其参数与 ref class
.
除了为 Widget
类型,我们还需要在IDL中将其注释为 可激活的 . 我们使用 activatable
属性,它有两种形式,我们将使用这两种形式 Widget
类型:
[activatable(1.0)] [activatable(IWidgetFactory, 1.0)]
第一个表单将类型声明为可构造的默认类型。第二种形式声明 IWidgetFactory
是运行时类的工厂接口(这个 1 在每个版本中都有一个版本号;当midlrt编译器将IDL文件编译成Windows元数据(WinMD)文件时,它将使用这些属性为运行时类的元数据添加正确的构造函数集。
接下来,我们需要实施 WidgetFactory
实现两个 IActivationFactory
以及 IWidgetFactory
接口。而不是使用WRL RuntimeClass
基类模板,我们将使用 ActivationFactory
基类模板,用于支持激活工厂。
class WidgetFactory : public ActivationFactory<IWidgetFactory> { InspectableClassStatic(RuntimeClass_WRLWidgetComponent_Widget, BaseTrust) public: STDMETHODIMP ActivateInstance(IInspectable** widget) override { *widget = Make<Widget>().Detach(); return *widget != nullptr ? S_OK : E_OUTOFMEMORY; } STDMETHODIMP CreateInstance(int value, IWidget** widget) override { *widget = Make<Widget>(value).Detach(); return *widget != nullptr ? S_OK : E_OUTOFMEMORY; } };
ActivationFactory
提供的默认实现 IActivationFactory
接口;这个默认实现只是定义 ActivateInstance
作为回报 E_NOTIMPL
. 这适用于不可默认构造的运行时类;对于默认可构造的运行时类(如 Widget
),我们需要覆盖 ActivateInstance
构造一个对象。
Make<Widget>()
实际上相当于 new (nothrow) Widget()
:它动态地为 Widget
并将提供的参数传递给 Widget
建造师。就像 new (nothrow)
,它产生 nullptr
如果分配失败(记住,我们不能从实现接口的函数中抛出异常,我们必须返回一个HRESULT)。它返回一个 ComPtr<Widget>
; 因为我们返回接口指针,所以我们只需分离指针并返回它(调用者负责调用 Release
在所有返回的接口指针上)。
这就是我们要实现的目标 WidgetFactory
激活工厂。如果我们能得到工厂的一个实例,我们就可以很容易地创建 Widget
物体。例如,
void Test(ComPtr<IWidgetFactory> const& factory) { ComPtr<IWidget> widget; factory->CreateInstance(42, widget.GetAddressOf()); // Hooray, we have a widget! }
小部件工厂来自哪里?
使 Widget
对象,我们建立了一个 WidgetFactory
,以便 WidgetFactory
对象,我们将建立一个 WidgetFactoryFactory
. 那么,为了建造那些…哈!开玩笑而已。
每个可激活的运行时类都在一个模块(DLL)中定义。定义一个或多个可激活运行时类的每个模块必须导出一个名为 DllGetActivationFactory
. 声明如下:
HRESULT WINAPI DllGetActivationFactory(HSTRING activatableClassId, IActivationFactory** factory);
从某种意义上说,这个函数是激活工厂的工厂:它将运行时类的名称作为参数( activatableClassId
)它通过out参数返回 factory
命名类型的激活工厂的实例。如果模块没有指定类型的激活工厂,它将返回一个失败错误代码( 旁白: HSTRING
是Windows运行时字符串类型,我们将在以后的文章中讨论。 )
从概念上讲,我们可以认为函数是这样实现的:
HRESULT WINAPI DllGetActivationFactory(HSTRING activatableClassId, IActivationFactory** factory) { // Convert the HSTRING to a C string for easier comparison: wchar_t const* className = WindowsGetStringRawBuffer(activatableClassId, nullptr); // Are we being asked for the Widget factory? If so, return an instance: if (wcscmp(className, L"WidgetComponent.Widget") == 0) { *factory = Make<WidgetFactory>().Detach(); return S_OK; } // If our module defines other activatable types, we'd check for them here. // Otherwise, we return that we failed to satisfy the request: *factory = nullptr; return E_NOINTERFACE; }
在实践中,我们不应该做太多的工作来实现这一功能。当使用C++或CX构建组件时,编译器将自动实现该函数,如果 _WINRT_DLL
宏已定义(默认情况下,此宏是在VisualStudio中的Windows运行时组件项目模板中定义的)。对于WRL,需要做一些工作,但这非常简单。每个可激活类都必须使用以下方法之一向WRL注册: 这个 ActivatableClass
宏 . 例如,注册我们的 Widget
类型及其属性 WidgetFactory
激活工厂,我们可以使用 ActivatableClassWithFactory
宏:
ActivatableClassWithFactory(Widget, WidgetFactory)
因为许多类型只允许默认构造,而且默认构造使用 IActivationFactory
接口,不需要任何自定义的、特定于类型的逻辑,WRL还提供了一种有用的宏形式, ActivatableClass
. 此宏生成一个允许默认构造的简单激活工厂,并注册生成的激活工厂。在第1部分中,我们在翻译 Number
从C++到CX到WRL类。
如果所有可激活的运行时类都注册了WRL,那么 DllGetActivationFactory
授权给WRL,让WRL做所有的努力工作。
HRESULT WINAPI DllGetActivationFactory(HSTRING activatibleClassId, IActivationFactory** factory) { auto &module = Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule(); return module.GetActivationFactory(activatibleClassId, factory); }
在这一点上,我们拥有了使运行时类可构造的一切:我们有一个工厂,可以构造运行时类的实例,并且我们有一个定义良好的方法来获取任何可激活的运行时类的工厂,只要我们知道定义该运行时类的模块。
创建实例
我们已经完成了可激活运行时类的实现;现在让我们看看 ref new
当我们创建一个 Widget
实例。在本文的开头,我们从以下内容开始:
Widget^ widget = ref new Widget(42);
我们可以将它转换成使用WRL而不是C++ +CX的C++代码:
HStringReference classId(RuntimeClass_WidgetComponent_Widget); ComPtr<IWidgetFactory> factory; RoGetActivationFactory( classId.Get(), __uuidof(IWidgetFactory), reinterpret_cast<void**>(factory.GetAddressOf())); ComPtr<IWidget> widget; factory->CreateInstance(42, widget.GetAddressOf());
实例化是一个分两步的过程:首先我们需要为 Widget
类型,然后我们可以构造一个 Widget
使用该工厂的实例。这两个步骤在WRL代码中非常清楚。 RoGetActivationFactory
是Windows运行时本身的一部分。信息技术:
- 查找定义命名运行时类型的模块,
- 加载模块(如果尚未加载),
- 获取指向模块的
DllGetActivationFactory
入口点, - 这叫什么
DllGetActivationFactory
函数来获取激活工厂的实例, - 电话
QueryInterface
在工厂上获取指向请求接口的指针,以及 - 返回结果接口指针。
其中大多数都是直截了当的,无需进一步评论。例外是第一项:Windows运行时如何准确地确定要加载哪个模块来实例化特定类型?虽然有一个要求 元数据 对于在名称与类型名类似的WinMD文件中定义的类型,对模块的命名没有这样的要求: Widget
类型可以在任何模块中定义。
每个Windows应用商店应用都包含一个名为AppXManifest.xml的文件。此清单包含有关应用程序的各种重要信息,包括其标识、名称和徽标。清单还包含一个包含 扩展 :此部分包含定义可激活类型的所有模块的列表,以及每个模块定义的所有可激活类型的列表。例如,下面的条目类似于 Widget
类型:
<Extension Category="windows.activatableClass.inProcessServer">
<InProcessServer>
<Path>WidgetComponent.dll</Path> <ActivatableClass ActivatableClassId="WidgetComponent.Widget" ThreadingModel="both" /> </InProcessServer> </Extension>
该列表仅包括由应用程序包中包含的模块定义的类型;Windows提供的类型(即 Windows命名空间 )在注册表中全局注册,并且不包括在AppXManifest.xml清单中。
对于大多数项目,此清单是作为在生成应用程序后运行的应用程序打包任务的一部分创建的。extensions部分的内容是通过检查WinMD文件中的任何引用组件和来自任何引用组件的清单来自动填充的 扩展SDK . 当我们的应用程序调用时 RoGetActivationFactory
,Windows运行时使用此列表查找需要为 Widget
类型。
应该注意的是,为了提高性能,激活工厂可以缓存在ABI边界的两侧:我们定义 Widget
类型实际上只需要创建 WidgetFactory
; 它不需要每次请求工厂时都创建一个新实例。类似地,我们的应用程序可以缓存它从中取回的工厂 RoGetActivationFactory
避免每次需要构造 Widget
. 如果我们的应用创造了很多 Widget
s、 这可能会带来巨大的变化。对于这个缓存,WRL和C++ +CX都是相当聪明的。
总之
可以说,C++/CX语法隐藏了大量的复杂性!我们从四条有效的C++/CX行开始:声明构造函数的两行,以及两个语句来演示这些构造函数的使用。我们的结局远不止这些。对于这两个构造器,我们有一个 Widget
-特定工厂接口,实现该接口和 IActivationFactory
接口和创建工厂的模块入口点。对于 ref new
表达式,我们有一个通过Windows运行时基础设施的往返过程。
注意,这里描述的所有内容都适用于在一个模块中定义可构造类型并从另一个模块实例化该类型的一般情况。也就是说,这是通过ABI构造对象的机制。如果该类型与实例化该类型的代码在同一模块中定义,则编译器能够避免跨ABI边界调用所需的大量开销。 以后的文章将讨论如何在单个模块中工作,但要知道,在这种情况下,事情往往要简单得多。
如果你觉得这些文章有用,我们很乐意在评论中听到你的意见! 让我们知道,如果你有任何问题,想法,或请求,无论是关于这一系列文章或关于C++ +CX的一般。 你也可以在Twitter上关注我 ( @詹姆斯奈利斯 在这里,我在推特上讨论了广泛的C++相关主题。