当前位置:天才代写 > tutorial > C语言/C++ 教程 > 借助 C++ 举办 Windows 开拓:Windows 运行时应用措施模子

借助 C++ 举办 Windows 开拓:Windows 运行时应用措施模子

2017-11-02 08:00 星期四 所属: C语言/C++ 教程 浏览:688

副标题#e#

我们的糊口布满了抽象。作为开拓人员,假如我们不相识抽象的界说就去利用它,则凡是会让我们 陷入逆境。抽象有时是零星的,且无法完全埋没根基巨大性。别误解我的意思,其实抽象是很有用的。 它们能为用户和开拓人员提供辅佐,而假如您深入研究您凡是依赖的抽象来相识其运作方法,则会让您 受益匪浅。另外,认可这一现实的库凡是比不认可这一现实的库更为乐成,部门是因为前者答允您在必 要时绕过抽象。

Windows 运行时 (WinRT) 就是一个这样的抽象,在本月的专栏中,我将通过研究 WinRT 焦点应用 措施模子来说明此抽象。此抽象以 CoreWindow 类为中心,而且每个“新型”Windows 应用商店和 Windows Phone 应用中都包括一个该类的实例。可是很少有开拓人员知道该实例的存在,更不必说该实 例的运作方法了。这大概是对抽象乐成的最好证明。

自 Windows 8 API 最初于 2011 年宣布以来,已有大量关于通过 Windows 运行时提供抽象的各类 语言投射的报道和文章。可是,相识 Windows 运行时的最佳方法是避开各类语言投射(包罗 C++/CX) 并利用尺度 C++ 和经典 COM。只有 C++ 能让您透过表象看到实际环境(从技能上说,C 也可以,但会 造成一些不须要的贫苦)。您仍可以选择利用这样或那样的语言投射(进展是 C++/CX),因为您大概 应该这样做,但您至少要更清楚地相识实际环境。

若要开始此操纵,请打开 Visual Studio 2012 并为 Windows 应用商店或 Windows Phone 应用创 建新的 Visual C++ 项目。随便您利用哪种模板。加载后,转到办理方案资源打点器并删除一切不重要 的内容。假如您选取了基于 XAML 的模板,则删除所有 XAML 文件。您也可以删除所有 C++ 源文件。 您大概需要保存预编译头,但请务必删除个中包括的所有内容。应保存的内容是陈设应用、图像、证书 和 XML 清单所需的包资产。

接下来,打开项目标属性页并选择编译器属性 – 树中左侧的 C/C++ 节点。找到称作“利用 Windows 运行时扩展”的 /ZW 编译器选项对应的行,并选择“否”以禁用 C++/CX 语言扩展。这样, 您就可以确保不会有比尺度 C++ 编译器更神秘的对象了。到了这一步,您也可以将编译器的告诫级别 配置为 /W4。

假如您实验编译项目,则应会收到一个链接器错误,奉告您找不到项目标 WinMain 进口点函数。将 新的 C++ 源文件添加到项目,您要做的第一件事是添加缺少的 WinMain 函数:

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
}

如您所见,这是用于基于 C 运行时库 (CRT) 的 Windows 应用措施的旧 WinMain 函数。 虽然,HINSTANCE 和 PWSTR 不是根基 C++ 范例,因此您将需要包括 Windows 头:

#include <windows.h>
           

假如您保存了项目标预编译头,则可在此处包括它。 另外,由 于我还将利用 Windows 运行时 C++ 模板库 (WRL) 中的 ComPtr,因此此刻最好是包括它:

#include <wrl.h>
           

在接下来的几个专栏中,我将更具体地先容 WRL。 此刻,我将 操作 ComPtr 类模板来维护 COM 接口指针。 在这一阶段中,您只需记着 WRL ComPtr 只是一个 COM 接口智能指针。 尽量它提供了某些特定于 Windows 运行时的成果,但我不会在本月的专栏中探讨这些 成果。 您可以很轻松地改用勾当模板库 (ATL) CComPtr 或您选择的任何 COM 接口智能指针。 WRL ComPtr 是在 Microsoft::WRL 定名空间中界说的:

 using namespace Microsoft::WRL;
           

我还将利用 ASSERT 宏和 HR 函数来举办错误处理惩罚。 由于我之前已接头这些,因此在这 里不再报告。 假如您对这些步调不确定,请参阅我在 2013 年 5 月撰写的专栏“Direct2D 1.1 简介 ”(msdn.microsoft.com/magazine/dn198239)。

最后,若要利用本专栏中提到的任何 WinRT 函 数,您需要为链接器提供 .lib 文件的名称:

#pragma comment(lib, "RuntimeObject.lib")


#p#副标题#e#

应用措施模子首先需要的是多线程单位 (MTA)。 是的,这就是 COM 单 元模子地址的位置。 Windows 运行时提供了 RoInitialize 函数,它是 CoInitializeEx 的薄包装:

HR(RoInitialize (RO_INIT_MULTITHREADED));
           

尽量利用 CoInitializeEx 凡是已足够,但我照旧发起您利用 RoInitialize。 操作此函数,您未来可以对 Windows 运行时举办改造而不消包袱粉碎经典 COM 的风险。 RoInitialize 雷同于 OleInitialize, 后者也称为 CoInitializeEx 等等。 就我看来,您的应用措施的主线程没什么神秘的。 独一大概有点 让人受惊的是它不是单线程单元 (STA)。 别担忧,您的应用措施的窗口仍将从 STA 线程中运行,但 Windows 运行时将是其建设者。 实际上,此 STA 是略微差异的应用措施 STA (ASTA),我今后会具体 先容它。

#p#分页标题#e#

接下来的内容会有一些棘手。 Windows 运行时放弃了利用基于 GUID 的类标识符 的传统 COM 激活模子,而回收了按照文本类标识符激活类的模子。 文本名称基于 Java 和 Microsoft .NET Framework 提出的以定名空间为浸染域的类名称,但在您对注册表嗤之以鼻并暗示终于获得摆脱 之前,请记着这些新的类标识符仍存储在注册表中。 从技能上说,仅第一方范例是在注册表中注册的 ,而第三方范例仅在每个应用措施清单中注册。 这种变革有利也有弊。 缺点之一是,在挪用 WinRT 函数时描写类标识符有点坚苦。 Windows 运行时界说了一个新的长途字符串范例来替代传统的 BSTR 字符串范例,任何类标识符都需要通过此新前言提供。 挪用 HSTRING 时呈现的错误比挪用 BSTR 时出 现的错误要少得多,这主要是因为前者是牢靠稳定的。 利用 WindowsCreateString 函数建设 HSTRING 最为轻松:

wchar_t buffer[] = L"Poultry.Hatchery";
HSTRING string;
HR(WindowsCreateString(buffer,
                       _countof(buffer) - 1,
                       &string));

WindowsCreateString 将分派足够的内存来存储 源字符串的副本和终止 null 字符,然后将源字符串复制到此缓冲区。 若要释放后备缓冲区,您必需 记着挪用 WindowsDeleteString,除非字符串的所有权返回到挪用函数:

HR(WindowsDeleteString(string));
           

假如利用的是 HSTRING,您可以利用 WindowsGetStringRawBuffer 函数获取指向其后备缓冲区的指针:

wchar_t const * raw = WindowsGetStringRawBuffer(string, nullptr);
           

可选的第二个参数将返回字符 串的长度。 该长度与字符串存储在一起,这样您无需扫描字符串即可确定其长度。 在运行和写入 C++ 包装类之前,您需要留意的是,在为字符串文本或常量数组生成代码时,C++/CX 编译器不会滋扰 WindowsCreateString 和 WindowsDelete­String。 相反,它会利用所谓的快速通报字符串,以避 免我之前提到的太过的内存分派和复制。 这还将制止内存泄漏的风险。 WindowsCreate­StringReference 函数将建设快速通报字符串:

          

HSTRING_HEADER header;
HSTRING string;
HR(WindowsCreateStringReference(buffer,
                                _countof(buffer) - 1,
                                &header,
                                &string));

此函数利用挪用方提供的 HSTRING_HEADER 来制止仓库分派。在这种环境下,HSTRING 的后备缓冲区是源字符串自己,因此您必 须确保源字符串(以及标头)在 HSTRING 的整个生命周期内保持稳定。当您需要将字符串返回到挪用 函数时,此要领明明没有任何用处,但当您需要将字符串作为输入通报到保留期受仓库限制的其他函数 (非异步函数)时,此要领值得优化。WRL 还为 HSTRING 和快速通报函数提供了包装。

RoGetActivationFactory 只是这样一个函数,用于获取给定类 的激活工场或静态接口。这与 COM CoGetClassObject 函数雷同。假如此函数凡是用于由 MIDL 编译器 生成的常量数组,则写入简朴函数模板来提供快速通报字符串包装会很有用。图 1 显示了其大概的外观。

图 1 GetActivationFactory 函数模板

          template <typename T, unsigned Count>
auto GetActivationFactory(WCHAR const (&classId)[Count]) -> ComPtr<T>
{
  HSTRING_HEADER header;
  HSTRING string;
  HR(WindowsCreateStringReference(classId,
                                  Count - 1,
                                  &header,
                                  &string));
  ComPtr<T> result;
  HR(RoGetActivationFactory(string,
    __uuidof(T),
    reinterpret_cast<void **>(result.GetAddressOf())));
  return result;
}

#p#副标题#e#

GetActivationFactory 函数模板将自动揣度字符串长度,从而消除了易堕落的缓冲区长度 参数或本钱奋发的运行时扫描。然后,该模板会在挪用实际 RoGetActivationFactory 函数前筹备快速 通报字符串。此时该函数模板将再次揣度接口标识符,并安详地返回在 WRL ComPtr 中包装的生成的 COM 接口指针。

您此刻可以利用此辅佐措施函数来获取 ICoreApplication 接口:

          using namespace ABI::Windows::ApplicationModel::Core;
auto app = GetActivationFactory<ICoreApplication>(
  RuntimeClass_Windows_ApplicationModel_Core_CoreApplication);

ICoreApplication 接口用来让您的应用措施开始运行。 若要利用此 COM 接口,您需要包括应用措施模子标头:

             #include <Windows.ApplicationModel.Core.h>
           

#p#分页标题#e#

此标头界说了 ABI::Windows::ApplicationModel::Core 定名空间内的 ICoreApplication,以及 Core­Application 的文本类标识符。 您真正需要思量的独一接口要领 是 Run 要领。

在继承接头之前,有须要赞赏一下 Windows 运行时为您的应用措施带来的机动 性。 正如我前面提到的,Windows 运行时只不外将您视为您本身的历程中的一个宾客。 这与 Windows 处事多年来的事情道理雷同。 对付 Windows 处事,Windows 处事节制打点器 (SCM) 利用 CreateProcess 函数或其变体之一启动处事。 该打点器随后会期待历程挪用 StartServiceCtrlDispatcher 函数。 此函数将成立返回 SCM 的毗连,该处事和 SCM 可通过它举办通 信。 譬喻,假如该处事未能实时挪用 StartService­CtrlDispatcher,则 SCM 将认为堕落并终止 历程。 StartServiceCtrl­Dispatcher 函数仅在该处事终止后返回,因此 SCM 需要为处事建设辅 助线程以便吸收回调通知。 该处事很少对事件举办响应且受 SCM 的支配。 正如您将发明的,这与 WinRT 应用措施模子极其相似。

Windows 运行时期待历程获取 ICore­Application 接口并 挪用其 Run 要领。 与 SCM 雷同,假如历程未能实时执行此操纵,则 Windows 运行时将认为存在错误 并终止历程。 幸运的是,假如毗连了调试器,则 Windows 运行时将发出通知并禁用超时,这一点与 SCM 差异。 可是,其模子是沟通的。 Windows 运行时认真应用措施并在事件产生时对运行时建设的线 程挪用应用措施。 虽然,Windows 运行时是基于 COM 的,它期望应用措施提供 Run 要领与可用于调 用应用措施的 COM 接口,而不是提供回调函数(这与 SCM 沟通),而且 Windows 运行时依赖历程生 命周期打点器 (PLM) 做到这一点。

您的应用措施必需提供 IFramework­ViewSource 的实 现(也来自 ABI::Windows::ApplicationModel::Core 定名空间);在建设您的应用措施的 UI 线程后 ,CoreApplication 将挪用其独立的 CreateView 要领。 IFrameworkViewSource 不会真的将 CreateView 视为要领。 IFrameworkViewSource 派生自 WinRT 基接口 IInspectable。 反过来, IInspectable 又派生自 COM 基接口 IUnknown。

WRL 为实现 COM 类提供了遍及的支持,不外 关于这方面的内容,我将留到接下来的专栏中讲授。 此刻,我想先容 Windows 运行时如何真正来历于 COM,以及哪一种要领比实现 IUnknown 更能展示这一点。 对付我而言,记着这一点很有用:将实现 IFrameworkViewSource 和其他一些接口的 C++ 类的保留期通过仓库来界说。 根基说来,应用措施的 WinMain 函数可以归纳为:

          HR(RoInitialize(RO_INIT_MULTITHREADED));
auto app = GetActivationFactory<ICoreApplication>( ...
          SampleWindow window;
HR(app->Run(&window));

剩下来要做的只是写入 SampleWindow 类,以便让它正确实 现 IFrameworkViewSource。尽量 CoreApplication 不存眷本身的实现位置,但至少来说,您的应用程 序不单需要实现 IFrameworkViewSource,并且需要实现 IFrameworkView 和 IActivatedEventHandler 接口。在本示例中,SampleWindow 类可以将它们全部实现:

          struct SampleWindow :
  IFrameworkViewSource,
  IFrameworkView,
  IActivatedEventHandler
{};

另外,IFrameworkView 接口也是在 ABI::Windows::ApplicationModel::Core 定名空间 中界说的,但更难约束 IActivatedEvent­Handler。我在下面给出了本身所做的界说:

          using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::ApplicationModel::Activation;
typedef ITypedEventHandler<CoreApplicationView *, 
  IActivatedEventArgs *>
  IActivatedEventHandler;

假如您有一些利用 COM 的履历,则大概会认为这看起来不足正 统 – 您是对的。正如您期望的,ITypedEventHandler 只是一个类模板,而界说 COM 接口的要领很是 奇怪 – 最明明的问题是您无法知道要将其归于哪种接口标识符。幸运的是,所有这些接口都由 MIDL 编译器生成,该编译器将认真专用化每个接口,并基于这些专用化来附加暗示接口标识符的 GUID。该 编译器将界说直接派生自 IUnknown 的 COM 接口并提供一个称为 Invoke 的要领,这与之前的 typedef 大概呈现的环境一样巨大。

我有几个要实现的接口要领,那么让我们开始吧。首先是 IUnknown 和强大的 QueryInterface 要领。我在这里不想在 IUnknown 和 IInspectable 上花太多的时间,因为我将在接下来的专栏中具体 先容它们。图 2 提供了针对基于仓库的类的简朴 QueryInterface 实现,如下所示 。

#p#分页标题#e#

图 2 SampleWindow QueryInterface 要领

          auto __stdcall QueryInterface(IID const & id,
                              void ** result) -> HRESULT
{
  ASSERT(result);
  if (id == __uuidof(IFrameworkViewSource) ||
      id == __uuidof(IInspectable) ||
      id == __uuidof(IUnknown))
  {
    *result = static_cast<IFrameworkViewSource *>(this);
  }
  else if (id == __uuidof(IFrameworkView))
  {
    *result = static_cast<IFrameworkView *>(this);
  }
  else if (id == __uuidof(IActivatedEventHandler))
  {
    *result = static_cast<IActivatedEventHandler *>(this);
  }
  else
  {
    *result = nullptr;
    return E_NOINTERFACE;
  }
  // static_cast<IUnknown *>(*result)->AddRef();
  return S_OK;
}

#p#副标题#e#

对付此实现,有一些方面值得留意。首先,该要领断言其参数是有效的。矫正确的实现大概 返回 E_POINTER,但人们认为此类错误是可在开拓进程中办理的 Bug,因此无需在运行时挥霍特另外周 期。这通过导致会见斗嘴和很是易于阐明的瓦解转储提供了大概最好的行为。假如您返回了 E_POINTER ,间断的挪用方大概会直接忽略它。最佳计策是提早失败。实际上,许多实现都回收了该位置,包罗 DirectX 和 Windows 运行时。对付正确实现 QueryInterface 举办了深入地研究。COM 类型很出格, 这使得 COM 类将始终正确和一致地提供某些工具标识担保。假如 if 语句链看起来令人生畏,别担忧 。我将在适当的时候先容它。

关于此实现,值得提及的最后一点是挪用 AddRef 不会很贫苦。凡是,在返回前,QueryInterface 必需在生成的 IUnknown 接口指针上挪用 AddRef。可是,由于 SampleWindow 类主流位于仓库上,因 此没有须要举办引用计数。同样,实现 IUnknown AddRef 和 Release 要领也很简朴:

          auto __stdcall AddRef()  -> ULONG { return 2; }
auto __stdcall Release() -> ULONG { return 1; }

这些要领的功效只是发起,因此您 可以操作此事实,任何非零值都行。这里需要留意的一点是: 您大概但愿包围运算符 new 和 delete 以便明晰暗示该类仅用于仓库。可能,您也可以直接实现引用计数,以防万一。

接下来,我需要实现 IInspectable,但由于它不会用于此简朴应用,我将略施小计,以使其要领不 实现,如图 3 所示。这不是切合尺度的实现,不能担保必然有效。再次声明,我将 在接下来的专栏中先容 IInspectable,但这还不敷以让 SampleWindow IInspectable 派生的接口启动 和运行。

图 3 SampleWindow IInspectable 要领

          auto __stdcall GetIids(ULONG *,
                       IID **) -> HRESULT
{
  return E_NOTIMPL;
}
auto __stdcall GetRuntimeClassName(HSTRING *) -> HRESULT
{
  return E_NOTIMPL;
}
auto __stdcall GetTrustLevel(TrustLevel *) -> HRESULT
{
  return E_NOTIMPL;
}

接下来,我需要实现 IFrameworkViewSource 及其 CreateView 要领。由于 SampleWindow 类也将实现 IFrameworkView,因此上述实现很简朴。再次提醒,在返回前,您凡是需要在生成的 IUnknown 派生接口指针上挪用 AddRef。您大概但愿在下列函数的主体中挪用 AddRef,以防万一:

          auto __stdcall CreateView(IFrameworkView ** result) -> HRESULT
{
  ASSERT(result);
  *result = this;
  // (*result)->AddRef();
  return S_OK;
}

IFrameworkView 接口是让应用措施最终变得有趣的处所。在挪用 CreateView 以从应用程 序检索接口指针后,Windows 运行时将依次快速挪用其大大都要领。您必需快速响应这些挪用,因为它 们在用户期待您的应用措施启动的这段时间内将举办计数。第一个称为 Initialize,这是应用措施必 须注册 Activated 事件的处所。Activated 事件暗示应用措施已激活,但要由该应用措施激活其 CoreWindow。Initialize 要领很是简朴:

          auto __stdcall Initialize(ICoreApplicationView * view) -> HRESULT
{
  EventRegistrationToken token;
  HR(view->add_Activated(this, &token));
  return S_OK;
}

随后将挪用 SetWindow 要领,用来为应用措施提供实际的 ICoreWindow 实现。 ICoreWindow 仅对 Windows 运行时内的通例桌面 HWND 建模。与之前的应用措施模子接口差异。 ICoreWindow 是在 ABI::Windows::UI::Core 定名空间中界说的。在 SetWindow 要领中,您应复制接 口指针,因为您很快会用到它:

          using namespace ABI::Windows::UI::Core;
ComPtr<ICoreWindow> m_window;
auto __stdcall SetWindow(ICoreWindow * window) -> HRESULT
{
  m_window = window;
  return S_OK;
}

Load 要领是下一个要领,您应该将所有代码粘贴到这里,以便筹备应用措施以举办初始呈 现:

          auto __stdcall Load(HSTRING) -> HRESULT
{
  return S_OK;
}

查察本栏目

#p#副标题#e#

#p#分页标题#e#

您至少应注册与窗口巨细和可见性变动以及对 DPI 缩放的变动相关的事件。您也可以抓住时机来创 建各类 DirectX 工场工具、加载与设备无关的资源等。此处之所以是完成所有这些操纵的长处所,是 因为用户在这里通过您的应用措施的启动画面泛起。

当 Load 要领返回时,Windows 运行时将认为您的应用措施已作好激活筹备并触发 Activated 事件 ,我将通过实现 IActivatedEventHandler Invoke 要领处理惩罚该事件,如下所示:

          auto __stdcall Invoke(ICoreApplicationView *,
                      IActivatedEventArgs *) -> HRESULT
{
  HR(m_window->Activate());
  return S_OK;
}

激活窗口后,应用措施便做好了运行筹备:

          auto __stdcall Run() -> HRESULT
{
  ComPtr<ICoreDispatcher> dispatcher;
  HR(m_window->get_Dispatcher(dispatcher.GetAddressOf()));
  HR(dispatcher->ProcessEvents(CoreProcessEventsOption_ProcessUntilQuit));
  return S_OK;
}

可通过许多要领实现这一点。我在这里回收的步伐是检索窗口的 ICoreDispatcher 接口,它暗示窗 口的动静泵。最后尚有一个 Uninitialize 要领,它大概偶尔被挪用,但在其他环境下可以被安详地忽 略:

          auto __stdcall Uninitialize() -> HRESULT
{
  return S_OK;
}

就是这样。您此刻可以编译和运行应用措施。虽然,您在这里不会真的绘制什么对象。您可以从 dx.codeplex.com 得到 dx.h 的副本并开始添加一些 Direct2D 泛起代码(有关更多信息,请拜见我在 2013 年 6 月的专栏“ 一个用于 DirectX 编程的现代库”,网址为 msdn.microsoft.com/magazine/dn201741),可能比及我的下一个专栏,我将为您演示如何通过最好的方法将 Direct2D 与 WinRT 焦点应用程 序模子集成。

 

    关键字:

天才代写-代写联系方式