在桌面应用中调用 Windows 运行时 API

本主题介绍如何设置桌面应用项目以使用 Windows OS 提供的 Windows 运行时 (WinRT) API,并将新式 Windows 11 和 Windows 10 体验添加到桌面应用。

桌面应用中不支持某些 Windows 运行时 (WinRT) API。 有关详细信息,请参阅桌面应用中不支持的 Windows 运行时 API

修改 .NET 项目以使用 Windows 运行时 API

有几个用于 .NET 项目的选项:

  • 从 .NET 6 开始,可以在项目文件中指定目标框架名字对象 (TFM),用于访问 WinRT API。 此选项支持面向 Windows 10 1809 版或更高版本的项目。
  • 对于早期版本的 .NET,可以安装 Microsoft.Windows.SDK.Contracts NuGet 包,以便将所有必要的引用添加到项目中。 此选项支持面向 Windows 10 1803 版或更高版本的项目。
  • 如果你的项目同时面向 .NET 6(或更高版本)和早期版本的 .NET 等多个目标,可将项目文件配置为同时使用这两个选项。

.NET 6 及更高版本:使用“目标框架名字对象”选项

只可在使用 .NET 6(或更高版本)和面向 Windows 10 版本 1809 或更高版本操作系统的项目中使用此选项。 通过在项目文件中指定特定于 Windows OS 版本的 TFM,可以将引用添加到相应的 Windows SDK 目标包中。 有关此方案的详细背景信息,请参阅博客文章在 .NET 中调用 Windows API

  1. 在 Visual Studio 中打开项目后,在“解决方案资源管理器”中右键单击该项目,然后选择“编辑项目文件”。 项目文件的呈现效果将与此类似。

    注意

    以下示例显示了 WinExeOutputType,它指定 Windows GUI 可执行文件(并在应用运行时阻止打开控制台窗口)。 如果应用没有 GUI,则 OutputType 将具有不同的值。 可以从 Windows GUI 应用、控制台应用和库调用 WinRT API。 此外,TargetFramework 的值可能与以下示例不完全匹配。

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
      </PropertyGroup>
    </Project>
    
  2. 将所有其他设置保留原样,将 TargetFramework 元素的值替换为以下字符串之一:

    • net6.0-windows10.0.17763.0:如果应用面向 Windows 10 版本 1809。
    • net6.0-windows10.0.18362.0:如果应用面向 Windows 10 版本 1903。
    • net6.0-windows10.0.19041.0:如果应用面向 Windows 10 版本 2004。
    • net6.0-windows10.0.22000.0:如果应用面向 Windows 11。

    例如,以下元素适用于面向 Windows 10 2004 版的项目。

    <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
    

    在更高版本的 .NET 中,可将值替换为相关版本,例如 net6.0-windows10.0.19041.0

  3. 保存更改并关闭项目文件。

在 .NET 6 或更高版本中不受支持的 WinRT API

在 .NET 6 及更高版本中,Windows.UI 命名空间中有多个不受支持的 Windows 运行时 (WinRT) API。 对于下面列出的 API,WinUI (Microsoft.UI) 命名空间(例如 Microsoft.UI.Text)中存在这些 API 的等效版本。 .NET 6 及更高版本支持以下 WinRT API:

支持多个 Windows OS 版本

Windows OS 版本特定的 TargetFramework 属性决定了用来编译应用的 Windows SDK 的版本。 此属性确定生成时的一组可访问的 API,请为 TargetPlatformVersionTargetPlatformMinVersion 提供默认值(如果未显式设置)。 无需在项目文件中显式定义 TargetPlatformVersion 属性,因为这由 TargetFramework OS 版本自动设置。

可将 TargetPlatformMinVersion 替代为小于 TargetPlatformVersion(由 TargetFramework 属性中的版本确定)。 这使得应用可在更低版本的 OS 上运行。 例如,你可在项目文件中设置以下内容,来支持低于 Windows 10 版本 1809 的应用。

<Project Sdk="Microsoft.NET.Sdk">
 <PropertyGroup>
   <OutputType>WinExe</OutputType>
   <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
   <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
 </PropertyGroup>
</Project>

请注意,如果将 TargetPlatformMinVersion 设置为小于 TargetPlatformVersion 的版本,可能会导致调用不可用的 API。 调用在所有受支持的 OS 版本上均不可用的 WinRT API 时,建议使用 ApiInformation 检查来保护这些调用。 有关详细信息,请参阅版本自适应应用

早期版本的 .NET:安装 Microsoft.Windows.SDK.Contracts NuGet 包

如果应用使用 .NET Core 3.x 或 .NET Framework,请使用此选项。 此选项支持面向 Windows 10 1803 版或更高版本的项目。

  1. 确保已启用包引用

    1. 在 Visual Studio 中,单击“工具”->“NuGet 程序包管理器”->“程序包管理器设置”。
    2. 确保为“默认包管理格式”选择 PackageReference
  2. 在 Visual Studio 中打开项目后,在“解决方案资源管理器”中右键单击该项目,然后选择“管理 NuGet 包”。

  3. 在“NuGet 包管理器”窗口中,选择“浏览”选项卡,然后搜索 Microsoft.Windows.SDK.Contracts

  4. 找到 Microsoft.Windows.SDK.Contracts 包后,在“NuGet 包管理器”窗口的右窗格中,根据要面向的 Windows 10 版本,选择要安装的包的“版本”:

    • 10.0.19041.xxxx:对于 Windows 10 版本 2004,请选择此版本。
    • 10.0.18362.xxxx:对于 Windows 10 版本 1903,请选择此版本。
    • 10.0.17763.xxxx:对于 Windows 10 版本 1809,请选择此版本。
    • 10.0.17134.xxxx:对于 Windows 10 版本 1803,请选择此版本。
  5. 单击“安装”。

配置面向多个不同 .NET 版本的项目

如果你的项目同时面向 .NET 6(或更高版本)和早期版本(包括 .NET Core 3.x 和 .NET Framework),可配置项目文件,让其使用目标框架名字对象 (TFM) 自动拉取 .NET 6(或更高版本)的 WinRT API 参考,并为早期版本使用 Microsoft.Windows.SDK.Contracts NuGet 包。

  1. 在 Visual Studio 中打开项目后,在“解决方案资源管理器”中右键单击该项目,然后选择“编辑项目文件”。 下面的示例展示使用 .NET Core 3.1 的应用的项目文件。

    注意

    以下示例显示了 WinExeOutputType,它指定 Windows GUI 可执行文件(并在应用运行时阻止打开控制台窗口)。 如果应用没有 GUI,则 OutputType 将具有不同的值。 可以从 Windows GUI 应用、控制台应用和库调用 WinRT API。 此外,TargetFramework 的值可能与以下示例不完全匹配。

    <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <UseWindowsForms>true</UseWindowsForms>
      </PropertyGroup>
    </Project>
    
  2. 将文件中的 TargetFramework 元素替换为 TargetFrameworks 元素(请注意复数形式)。 在此元素中,为要面向的所有 .NET 版本(以分号分隔)指定目标框架名字对象 (TFM)。

    • 对于 .NET 6 或更高版本,请使用以下目标框架名字对象 (TFM) 之一:
      • net6.0-windows10.0.17763.0:如果应用面向 Windows 10 版本 1809。
      • net6.0-windows10.0.18362.0:如果应用面向 Windows 10 版本 1903。
      • net6.0-windows10.0.19041.0:如果应用面向 Windows 10 版本 2004。
    • 对于 .NET Core 3.x,请使用 netcoreapp3.0netcoreapp3.1
    • 对于 .NET Framework,请使用 net46

    下面的示例演示如何同时面向 .NET Core 3.1 和 .NET 6(适用于 Windows 10 2004 版)。

    <TargetFrameworks>netcoreapp3.1;net6.0-windows10.0.19041.0</TargetFrameworks>
    
  3. PropertyGroup 元素后面,添加一个 PackageReference 元素,它包含一个条件语句,该语句将为你的应用面向的任何 .NET Core 3.x 版本或 .NET Framework 安装 NuGet 包 Microsoft.Windows.SDK.ContractsPackageReference 元素必须是 ItemGroup 元素的子元素。 下面的示例演示如何面向 .NET Core 3.1 执行此操作。

    <ItemGroup>
      <PackageReference Condition="'$(TargetFramework)' == 'netcoreapp3.1'"
                        Include="Microsoft.Windows.SDK.Contracts"
                        Version="10.0.19041.0" />
    </ItemGroup>
    

    完成后,项目文件的呈现效果与此类似。

    <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFrameworks>netcoreapp3.1;net6.0-windows10.0.19041.0</TargetFrameworks>
        <UseWPF>true</UseWPF>
      </PropertyGroup>
      <ItemGroup>
        <PackageReference Condition="'$(TargetFramework)' == 'netcoreapp3.1'"
                         Include="Microsoft.Windows.SDK.Contracts"
                         Version="10.0.19041.0" />
      </ItemGroup>
    </Project>
    
  4. 保存更改并关闭项目文件。

修改 C++ 桌面 (Win32) 项目以使用 Windows 运行时 API

借助 C++/WinRT 使用 WinRT API。 C++/WinRT 是 WinRT API 的完全标准新式 C++17 语言投影,以基于标头文件的库的形式实现,旨在为你提供对新式 Windows API 的一流访问。

若要为 C++/WinRT 配置项目,请执行以下操作:

有关这些选项的更多详细信息,请参阅 C++/WinRT 和 VSIX 的 Visual Studio 支持

添加 Windows 10 体验

现在一切已准备就绪,可以添加用户在 Windows 10 上运行你的应用程序时可享受的新式体验。 使用此设计流。

首先,确定要添加的体验

有很多选择。 例如,可通过使用盈利 API 来简化你的采购订单流,或在要分享有趣的内容时(例如其他用户发布了新图片)吸引用户对应用程序的注意

Toast 通知

即使用户忽略或关闭你的消息,他们仍可在操作中心中再次看到该消息,然后单击该消息打开你的应用。 这可以加强用户与应用程序的互动,并使你的应用程序看似已与操作系统深度集成。 稍后,我们将在本文中向你演示用于实现该体验的代码。

请访问 UWP 文档以获取更多创意。

决定是增强还是扩展

你经常会听到我们使用术语“增强”和“扩展”,因此我们需要花些时间来说明一下这两个术语的确切含义。

我们使用术语“增强”来描述可以直接从桌面应用(无论是否是打包的应用)对其进行调用的 WinRT API。 当你选择 Windows 10 体验后,请确定创建它所需的 API,然后查看该 API 是否出现在此列表中。 这是你可以直接从桌面应用中调用的 API 的列表。 如果你的 API 未出现在此列表中,那是因为与该 API 关联的功能只在 UWP 进程内运行。 通常情况下,其中包括呈现 UWP XAML(例如 UWP 地图控件或 Windows Hello 安全提示)的 API。

注意

尽管通常不能直接从桌面调用呈现 UWP XAML 的 API,但你可以使用其他方法。 如果要托管 UWP XAML 控件或其他自定义视觉体验,则可以使用 XAML 岛(从 Windows 10 版本 1903 开始)和可视化层(从 Windows 10 版本 1803 开始)。 可以在打包或未打包的桌面应用中使用这些功能。

如果已选择打包桌面应用,则另一种选择是通过向解决方案中添加 UWP 项目来“扩展”应用程序。 桌面项目仍是应用程序的入口点,但 UWP 项目使你可以访问此列表中未显示的所有 API。 桌面应用可以使用应用服务来与 UWP 进程通信,我们可针对如何进行相关设置提供很多指导。 如果你要添加的体验需要 UWP 项目,请参阅使用 UWP 组件进行扩展

引用 API 协定

如果你可以直接从桌面应用中调用 API,请打开浏览器并搜索该 API 的引用主题。 在 API 的摘要下,你会找到一个描述用于该 API 的 API 协定的表。 下面是该表的一个示例:

API 协定表

如果你有基于 .NET 的桌面应用,请添加对该 API 协定的引用,然后将该文件的 Copy Local 属性设置为 False。 如果你有一个基于 C++ 的项目,请将包含此协定的文件夹的路径添加到“附加包含目录”中。

调用 API 以添加你的体验

以下代码用于显示我们之前看到的通知窗口。 此列表中显示了这些 API,因此你可以将此代码添加到桌面应用中并立即执行此代码。

using Windows.Foundation;
using Windows.System;
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
...

private void ShowToast()
{
    string title = "featured picture of the day";
    string content = "beautiful scenery";
    string image = "https://picsum.photos/360/180?image=104";
    string logo = "https://picsum.photos/64?image=883";

    string xmlString =
    $@"<toast><visual>
       <binding template='ToastGeneric'>
       <text>{title}</text>
       <text>{content}</text>
       <image src='{image}'/>
       <image src='{logo}' placement='appLogoOverride' hint-crop='circle'/>
       </binding>
      </visual></toast>";

    XmlDocument toastXml = new XmlDocument();
    toastXml.LoadXml(xmlString);

    ToastNotification toast = new ToastNotification(toastXml);

    ToastNotificationManager.CreateToastNotifier().Show(toast);
}
#include <sstream>
#include <winrt/Windows.Data.Xml.Dom.h>
#include <winrt/Windows.UI.Notifications.h>

using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::Data::Xml::Dom;

void UWP::ShowToast()
{
    std::wstring const title = L"featured picture of the day";
    std::wstring const content = L"beautiful scenery";
    std::wstring const image = L"https://picsum.photos/360/180?image=104";
    std::wstring const logo = L"https://picsum.photos/64?image=883";

    std::wostringstream xmlString;
    xmlString << L"<toast><visual><binding template='ToastGeneric'>" <<
        L"<text>" << title << L"</text>" <<
        L"<text>" << content << L"</text>" <<
        L"<image src='" << image << L"'/>" <<
        L"<image src='" << logo << L"'" <<
        L" placement='appLogoOverride' hint-crop='circle'/>" <<
        L"</binding></visual></toast>";

    XmlDocument toastXml;

    toastXml.LoadXml(xmlString.str().c_str());

    ToastNotificationManager::CreateToastNotifier().Show(ToastNotification(toastXml));
}
using namespace Windows::Foundation;
using namespace Windows::System;
using namespace Windows::UI::Notifications;
using namespace Windows::Data::Xml::Dom;

void UWP::ShowToast()
{
    Platform::String ^title = "featured picture of the day";
    Platform::String ^content = "beautiful scenery";
    Platform::String ^image = "https://picsum.photos/360/180?image=104";
    Platform::String ^logo = "https://picsum.photos/64?image=883";

    Platform::String ^xmlString =
        L"<toast><visual><binding template='ToastGeneric'>" +
        L"<text>" + title + "</text>" +
        L"<text>"+ content + "</text>" +
        L"<image src='" + image + "'/>" +
        L"<image src='" + logo + "'" +
        L" placement='appLogoOverride' hint-crop='circle'/>" +
        L"</binding></visual></toast>";

    XmlDocument ^toastXml = ref new XmlDocument();

    toastXml->LoadXml(xmlString);

    ToastNotificationManager::CreateToastNotifier()->Show(ref new ToastNotification(toastXml));
}

若要了解有关通知的详细信息,请参阅自适应和交互式 toast 通知

支持 Windows XP、Windows Vista 和 Windows 7/8 安装库

你可以为适用于 Windows 10 的应用程序增加新式体验,而无需创建新分支和维护不同代码库。

如果要为 Windows 10 用户生成单独的二进制文件,请使用条件编译。 如果你希望生成要部署到所有 Windows 用户的一组二进制文件,请使用运行时检查。

让我们快速查看一下每个选项。

条件编译

你可以保留一个代码库,并编译一组仅面向 Windows 10 用户的二进制文件。

首先,向项目中添加新的生成配置。

生成配置

为该生成配置创建一个常量,来标识调用 WinRT API 的代码。

对于基于 .NET 的项目,该常量称为“条件编译常量”。

条件编译常量

对于基于 C++ 的项目,该常量称为“预处理器定义”。

预处理器定义常量

在任意 UWP 代码块前添加该常量。

[System.Diagnostics.Conditional("_UWP")]
private void ShowToast()
{
 ...
}
#if _UWP
void UWP::ShowToast()
{
 ...
}
#endif

仅当在活动生成配置中定义了该常量时,编译器才会生成该代码。

运行时检查

可以不考虑用户所运行的 Windows 版本而为所有 Windows 用户编译一组二进制文件。 仅当用户在 Windows 10 上以打包的应用程序形式运行应用程序时,应用程序才会调用 WinRT API。

向代码中添加运行时检查的最简单方法是安装此 Nuget 包:桌面桥帮助程序,然后使用 IsRunningAsUWP() 方法关闭调用 WinRT API 的所有代码。 有关更多详细信息,请参阅此博客文章:桌面桥 - 标识应用程序的上下文

查找问题的答案

有问题? 请在 Stack Overflow 上向我们提问。 我们的团队会监视这些标记。 你还可以在我们的论坛上提问。