使用 C++/WinRT 创建 Windows 运行时组件

本主题说明如何通过 C++/WinRT 创建和使用 Windows 运行时组件 - 一种可以从使用任何 Windows 运行时语言生成的通用 Windows 应用调用的组件。

用 C++/WinRT 生成 Windows 运行时组件有多种原因。

  • 在复杂或计算密集型操作中发挥 C++ 的性能优势。
  • 重复使用已编写和测试的标准 C++ 代码。
  • 向用 C# 等语言编写的通用 Windows 平台 (UWP) 应用公开 Win32 功能。

通常,在创作 C++/WinRT 组件时,可以使用标准 C++ 库中的类型和内置类型,但应用程序二进制接口 (ABI) 边界除外,在该边界内,你将与另一个 .winmd 包中的代码进行双向数据传递。 在 ABI 上,使用 Windows 运行时类型。 此外,在 C++/WinRT 代码中,使用委派和事件等类型实现可从组件引发并使用另一种语言处理的事件。 有关 C++/WinRT 的详细信息,请参阅 C++/WinRT

本主题的其余部分介绍如何使用 C++/WinRT 创作 Windows 运行时组件,以及如何从应用程序使用该组件。

你将在本主题中生成的 Windows 运行时组件包含一个表示温度计的运行时类。 本主题还演示了一个核心应用,该应用使用 thermometer 运行时类并调用函数来调节温度。

注意

有关安装和使用 C++/WinRT Visual Studio 扩展 (VSIX) 和 NuGet 包(两者共同提供项目模板,并生成支持)的信息,请参阅适用于 C++/WinRT 的 Visual Studio 支持

重要

有关支持你了解如何利用 C++/WinRT 来使用和创作运行时类的基本概述和术语,请参阅通过 C++/WinRT 使用 API通过 C++/WinRT 创作 API

Windows 运行时组件 dll 的命名最佳做法

重要

本部分介绍建议用于.dll生成Windows 运行时组件的文件(DLL)的命名约定。 使用来自Windows 运行时组件的运行时类时,C++/WinRT 遵循的激活序列。

激活类工厂时,C++/WinRT 首先尝试调用 RoGetActivationFactory 如果失败,C++/WinRT 会尝试查找直接加载的 DLL。 Windows 运行时激活始终基于完全限定的类名。 逻辑是删除类名(从该完全限定的类名),然后查找为保留的完整命名空间命名的 DLL。 如果未找到,请删除最具体的段名称,然后重复。

例如,如果激活的类具有 Contoso.Instruments.热计WRC.温度计的完全限定名称,并且 RoGetActivationFactory 失败,我们将首先查找一个Contoso.Instruments.ThermometerWRC.dll 如果未找到,我们将查找 Contoso.Instruments.dll,然后查找 Contoso.dll

找到 DLL(在该序列中),我们将使用该 DLL 的 DllGetActivationFactory 入口点直接获取工厂(而不是间接通过我们首次尝试的 RoGetActivationFactory 函数)。 即便如此,最终结果与调用方和 DLL 不区分。

此过程是完全自动的,无需注册或工具。 如果要创作Windows 运行时组件,则只需对刚描述的过程使用 DLL 的命名约定即可。 如果你使用的是Windows 运行时组件,并且它未正确命名,则可以按说明选择重命名它。

创建 Windows 运行时组件 (ThermometerWRC)

首先在 Microsoft Visual Studio 中创建新项目。 创建一个 Windows 运行时组件 (C++/WinRT) 项目,然后将其命名为 ThermometerWRC(针对“温度计 Windows 运行时组件”)。 请确保未选中“将解决方案和项目放在同一目录中”。 面向 Windows SDK 的最新正式发布(非预览)版本。 将项目命名为 ThermometerWRC 会让你在执行本主题的其余步骤时拥有最轻松的体验。

暂时不要生成该项目。

该新建项目包含一个名为 Class.idl 的文件。 在解决方案资源管理器中,将该文件重命名为 Thermometer.idl(重命名 .idl 文件还会自动重命名从属的 .h.cpp 文件)。 将 Thermometer.idl 中的内容替换为下表。

// Thermometer.idl
namespace ThermometerWRC
{
    runtimeclass Thermometer
    {
        Thermometer();
        void AdjustTemperature(Single deltaFahrenheit);
    };
}

保存文件。 该项目目前不会完全生成,但是现在生成很有助益,因为它会生成源代码文件,而你将在该文件中实现 Thermometer 运行时类。 因此,继续生成(此阶段可能发生的生成错误与找不到 Class.hClass.g.h 有关)。

在生成过程中,midl.exe 工具会运行以创建组件的 Windows 运行时元数据文件(即 \ThermometerWRC\Debug\ThermometerWRC\ThermometerWRC.winmd)。 然后,cppwinrt.exe 工具运行(具有 -component 选项)以生成源代码文件,从而为你在创作组件时提供支持。 这些文件包含存根,可用于开始实现在 IDL 中声明的 Thermometer 运行时类。 这些存根是 \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.hThermometer.cpp

右键单击项目节点,然后单击“打开文件资源管理器中的文件夹”。 执行此操作,将在文件资源管理器中打开项目文件夹。 将存根文件 Thermometer.hThermometer.cpp 从文件夹 \ThermometerWRC\ThermometerWRC\Generated Files\sources\ 复制到包含项目文件的文件夹(即 \ThermometerWRC\ThermometerWRC\),并替换目标中的文件。 现在,让我们打开 Thermometer.hThermometer.cpp 并实现运行时类。 在 Thermometer.h 中,将一个新的私有成员添加到 Thermometer 的实现(而不是工厂实现)

// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
    struct Thermometer : ThermometerT<Thermometer>
    {
        ...

    private:
        float m_temperatureFahrenheit { 0.f };
    };
}
...

Thermometer.cpp 中实现 AdjustTemperature 方法,如下面的列表所示。

// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
    void Thermometer::AdjustTemperature(float deltaFahrenheit)
    {
        m_temperatureFahrenheit += deltaFahrenheit;
    }
}

你会在 Thermometer.hThermometer.cpp 的顶部看到 static_assert(需要删除)。 现在将生成项目。

如果任何警告阻止你进行生成,请处理这些警告或将项目属性“C/C++”>“常规”>“将警告视为错误”设置为“否(/WX-)”,然后重新生成该项目

创建核心应用 (ThermometerCoreApp) 以测试 Windows 运行时组件

现在创建新项目(在 ThermometerWRC 解决方案中,或在一个新解决方案中)。 创建核心应用 (C++/WinRT) 项目,然后将其命名为 ThermometerCoreApp。 如果两个项目位于同一个解决方案中,则将 ThermometerCoreApp 设置为启动项目

注意

如前文所述,文件夹 \ThermometerWRC\Debug\ThermometerWRC\ 中创建了 Windows 运行时组件的 Windows 运行时元数据文件(其项目命名为 ThermometerWRC)。 该路径的第一段是包含解决方案文件的文件夹的名称;下一段是名为 Debug 的子目录;最后一段是为 Windows 运行时组件命名的子目录。 如果未将项目命名为 ThermometerWRC,则元数据文件将位于 \<YourProjectName>\Debug\<YourProjectName>\ 文件夹中。

现在,在核心应用项目 (ThermometerCoreApp) 中添加一个引用,然后浏览到 \ThermometerWRC\Debug\ThermometerWRC\ThermometerWRC.winmd(或者,如果同一解决方案中有两个项目,则添加一个项目到项目的引用)。 单击“添加”,然后单击“确定”。 现在生成 ThermometerCoreApp。 在极少数情况下,如果看到一个显示有效负载文件 readme.txt 不存在的错误,请从 Windows 运行时组件项目中排除该文件,重新生成该文件,然后重新生成 ThermometerCoreApp

生成过程期间,cppwinrt.exe 工具会运行以将引用的 .winmd 文件处理到包含投影类型的源代码文件中,从而为你在使用组件时提供支持。 组件的运行时类的投影类型的标头(名为 ThermometerWRC.h)将生成到文件夹 \ThermometerCoreApp\ThermometerCoreApp\Generated Files\winrt\ 中。

App.cpp 中包含该标头。

// App.cpp
...
#include <winrt/ThermometerWRC.h>
...

此外,在 App.cpp 中,添加以下代码以实例化 Thermometer对象(使用投影类型的默认构造函数),并对 thermometer 对象调用方法。

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    ThermometerWRC::Thermometer m_thermometer;
    ...
    
    void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
    {
        m_thermometer.AdjustTemperature(1.f);
        ...
    }
    ...
};

每次单击窗口时,thermometer 对象的温度都会增加。 如果要单步执行代码,以确认应用程序确实正在调用 Windows 运行时组件,则可以设置断点。

后续步骤

若要向 C++/WinRT Windows 运行时组件添加更多功能或新的 Windows 运行时类型,可以遵循上面显示的相同模式。 首先,使用 IDL 定义要公开的功能。 然后在 Visual Studio 中生成项目以生成存根实现。 然后,根据需要完成实现。 在 IDL 中定义的任何方法、属性和事件对使用 Windows 运行时组件的应用程序都是可见的。 有关 IDL 的详细信息,请参阅 Microsoft 接口定义语言 3.0 简介

有关如何将事件添加到 Windows 运行时组件的示例,请参阅使用 C++/WinRT 创作事件

故障排除

症状 纠正方法
在 C++/WinRT 应用中,当使用利用 XAML 的 C# Windows 运行时组件时,编译器会生成一个错误,格式为:“'MyNamespace_XamlTypeInfo': 不是 'winrt::MyNamespace' 的成员”,其中 MyNamespace 是 Windows 运行时组件命名空间的名称。 在 C++/WinRT 应用中的 pch.h 中,根据需要添加 #include <winrt/MyNamespace.MyNamespace_XamlTypeInfo.h> 来替换 MyNamespace。