将 winapp CLI 与 C++ 和 CMake 配合使用

本指南演示如何将 winapp CLI 与 C++ 应用程序配合使用,以包标识进行调试,并将应用程序打包为 MSIX。

包标识是Windows app模型中的核心概念。 它允许应用程序访问特定的Windows API(例如通知、安全、AI API 等),具有干净的安装/卸载体验等。

标准可执行文件(如使用 cmake --build创建的可执行文件)没有包标识。 本指南演示如何添加它进行调试,然后将其打包以供分发。

先决条件

  1. 生成工具:使用 CMake 支持的编译器工具链。 此示例使用Visual Studio。 可以安装社区版(如果已安装则更新):

    winget install --id Microsoft.VisualStudio.Community --source winget --override "--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --passive --wait"
    

    安装后重新启动。

  2. CMake:安装 CMake(或更新(如果已安装):

    winget install Kitware.CMake --source winget
    
  3. winapp CLI:通过 winget 安装 winapp cli(或更新(如果已安装):

    winget install Microsoft.winappcli --source winget
    

1.创建新的 C++ 应用

首先创建简单的 C++ 应用程序。 为您的项目创建新目录。

mkdir cpp-app
cd cpp-app

创建一个包含基本“Hello, world!”程序的main.cpp文件:

#include <iostream>

int main() {
    std::cout << "Hello, world!" << std::endl;
    return 0;
}

创建一个CMakeLists.txt 文件以配置构建:

cmake_minimum_required(VERSION 3.20)
project(cpp-app)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(cpp-app main.cpp)

生成并运行它以确保一切正常工作:

cmake -B build
cmake --build build --config Debug
.\build\Debug\cpp-app.exe

输出应为“Hello, world!”

2. 更新代码以检查标识

我们将更新应用,以检查它是否使用包标识运行。 这将帮助我们验证标识在后续步骤中是否正常工作。 我们将使用 Windows 运行时 C++ API 访问包 API。

首先,将以下行添加到 CMakeLists.txt 末尾,以链接到Windows 应用模型库:

# Link Windows Runtime libraries
target_link_libraries(cpp-app PRIVATE WindowsApp.lib OneCoreUap.lib)

接下来,将整个内容 main.cpp 替换为以下代码。 此代码尝试使用 Windows 运行时 API 检索当前包标识。 如果成功,则会打印包系列名称;否则,它将打印“未打包”。

#include <iostream>
#include <windows.h>
#include <appmodel.h>

int main() {
    UINT32 length = 0;
    LONG result = GetCurrentPackageFamilyName(&length, nullptr);
    
    if (result == ERROR_INSUFFICIENT_BUFFER) {
        // We have a package identity
        std::wstring familyName;
        familyName.resize(length);
        
        result = GetCurrentPackageFamilyName(&length, familyName.data());
        
        if (result == ERROR_SUCCESS) {
            std::wcout << L"Package Family Name: " << familyName.c_str() << std::endl;
        } else {
            std::wcout << L"Error retrieving Package Family Name" << std::endl;
        }
    } else {
        // No package identity
        std::cout << "Not packaged" << std::endl;
    }

    return 0;
}

3.在没有标识的情况下运行

现在,像往常一样重新生成并运行应用:

cmake --build build --config Debug
.\build\Debug\cpp-app.exe

应会看到输出“未打包”。 这确认标准可执行文件在没有任何包标识的情况下正在运行。

4.使用 winapp CLI 初始化Project

winapp init 命令一次性设置好所需的一切:应用清单、资产和可用于 C++ 开发的(可选)Windows 应用 SDK 标头。

运行以下命令并按照提示操作:

winapp init .

出现提示时:

  • 程序包名称:按 Enter 接受默认 (cpp-app)
  • 发布者名称:按 Enter 接受默认值或输入名称
  • 版本:按 Enter 接受 1.0.0.0
  • 入口点:按 Enter 接受默认值(cpp-app.exe)
  • Setup SDK:选择“稳定 SDK”以下载Windows 应用 SDK并生成 C++ 标头

此命令将:

  • 创建 Package.appxmanifest — 定义您应用身份的清单
  • 创建 Assets 文件夹 - MSIX 打包和应用商店提交所需的图标
  • 使用Windows 应用 SDK标头和库创建 .winapp 文件夹
  • 创建一个 winapp.yaml 配置文件用于锁定 SDK 版本

可以打开 Package.appxmanifest 以进一步自定义属性,如显示名称、发布者和功能。

添加执行别名(适用于控制台应用)

执行别名允许用户从任何终端(如 cpp-app)按名称运行应用。 它还可在开发期间启用 winapp run --with-alias ,这将在当前终端中保留控制台输出,而不是打开新窗口。

可以自动添加一个:

winapp manifest add-alias

或者手动:打开 Package.appxmanifest,如果缺少的话,将 uap5 命名空间添加到 <Package> 标记中,然后在 <Applications><Application><Extensions>... 中添加扩展:

<Package
  ...
  xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
+ xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
  IgnorableNamespaces="uap uap2 uap3 rescap desktop desktop6 uap10">

  ...
  <Applications>
    <Application ...>
      ...
+     <Extensions>
+       <uap5:Extension Category="windows.appExecutionAlias">
+         <uap5:AppExecutionAlias>
+           <uap5:ExecutionAlias Alias="cpp-app.exe" />
+         </uap5:AppExecutionAlias>
+       </uap5:Extension>
+     </Extensions>
    </Application>
  </Applications>
</Package>

使用身份进行调试

若要测试需要标识(如通知)且未完全打包应用的功能,可以使用 winapp run。 这会注册松散布局包(就像真正的 MSIX 安装一样),并在一个步骤中启动应用。 调试不需要证书或签名。

  1. 生成可执行文件

    cmake --build build --config Debug
    
  2. 使用标识运行

    winapp run .\build\Debug --with-alias
    

--with-alias 标志通过其执行别名启动应用,以便控制台输出保留在当前终端中。 这需要我们在步骤 4 中添加的 uap5:ExecutionAlias

小窍门

winapp run 还会在您的系统中注册该软件包。 这就是为什么在步骤 8 后面尝试安装 MSIX 时,MSIX 可能显示为“已安装”。 使用 winapp unregister 清理开发包以完成工作。

现在应会看到类似于以下内容的输出:

Package Family Name: cpp-app_12345abcde

这确认你的应用正在运行,并具有有效的包标识!

替代方法:稀疏包标识

如果需要特别稀疏包行为(标识而不复制文件),可以改用 create-debug-identity

winapp create-debug-identity .\build\Debug\cpp-app.exe
.\build\Debug\cpp-app.exe

小窍门

有关高级调试工作流(附加调试器、IDE 设置、启动调试),请参阅 调试指南

6. 使用 Windows 应用 SDK (可选)

如果选择在 winapp init 期间设置 SDK,则现在可以访问 .winapp/include 文件夹中的Windows 应用 SDK标头。 这样,你就可以访问新式Windows API,例如通知、窗口化、设备上的 AI 等。 如果您只需要分发包标识,可以跳到步骤 7。

让我们添加一个简单的示例,用于打印Windows 应用运行时版本。

更新 CMakeLists.txt

将以下行添加到 CMakeLists.txt 末尾以包含Windows 应用 SDK标头:

# Add Windows App SDK include directory
target_include_directories(cpp-app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/.winapp/include)

更新main.cpp

替换 main.cpp 的整个内容以使用 Windows 应用 运行时 API:

#include <iostream>
#include <windows.h>
#include <appmodel.h>
#include <winrt/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.h>

int main() {
    // Initialize WinRT
    winrt::init_apartment();
    
    UINT32 length = 0;
    LONG result = GetCurrentPackageFamilyName(&length, nullptr);
    
    if (result == ERROR_INSUFFICIENT_BUFFER) {
        // We have a package identity
        std::wstring familyName;
        familyName.resize(length);
        
        result = GetCurrentPackageFamilyName(&length, familyName.data());
        
        if (result == ERROR_SUCCESS) {
            std::wcout << L"Package Family Name: " << familyName.c_str() << std::endl;
            
            // Get Windows App Runtime version using the API
            auto runtimeVersion = winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::RuntimeInfo::AsString();
            std::wcout << L"Windows App Runtime Version: " << runtimeVersion.c_str() << std::endl;
        } else {
            std::wcout << L"Error retrieving Package Family Name" << std::endl;
        }
    } else {
        std::cout << "Not packaged" << std::endl;
    }
    
    return 0;
}

构建和运行

使用Windows 应用 SDK标头重新生成应用程序:

cmake --build build --config Debug
winapp run .\build\Debug --with-alias

现在应会看到如下所示的输出:

Package Family Name: cpp-app_12345abcde
Windows App Runtime Version: 1.8-stable (1.8.0)

.winapp/include 目录包含Windows 应用 SDK所需的所有标头,包括:

  • winrt/ - 用于访问Windows 运行时 API 的 WinRT C++ 投影标头
  • Microsoft.UI.*.h - 新式 UI 组件的 WinUI 3 标头
  • MddBootstrap.h - Windows 应用 SDK引导过程
  • WindowsAppSDK-VersionInfo.h - 版本信息
  • 以及更多的 Windows 应用 SDK 组件

有关更高级Windows 应用 SDK用法,请查看 Windows 应用 SDK 文档

7. 根据需要还原标头

自动地由 winapp init 将文件夹 .winapp 添加到 .gitignore,因此不会检入源代码管理。 当其他人克隆你的项目时,他们需要在构建之前恢复这些文件。

手动设置

克隆存储库后运行以下两个命令:

# Restore Windows App SDK headers
winapp restore

# Generate development certificate (optional - only if planning to package the app and sideload)
winapp cert generate --if-exists skip

然后,您可以正常地使用 cmake -B buildcmake --build build --config Debug 进行构建和运行。

使用 CMake 进行自动化设置

或者,您可以通过将设置逻辑添加到您的CMakeLists.txt来自动化此过程。 完整的 CMakeLists.txt 包括自动化、正确链接以及采用最少的 C++20 标准:

cmake_minimum_required(VERSION 3.20)
project(cpp-app)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Download winapp CLI if not available in PATH
find_program(WINAPP_CLI winapp)
if(NOT WINAPP_CLI)
    set(WINAPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.winapp-tools")
    set(WINAPP_CLI "${WINAPP_DIR}/winapp.exe")
    
    if(NOT EXISTS "${WINAPP_CLI}")
        message(STATUS "Downloading winapp CLI...")
        
        # Determine architecture
        if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64")
            set(WINAPP_ARCH "arm64")
        else()
            set(WINAPP_ARCH "x64")
        endif()
        
        # Download and extract
        set(WINAPP_ZIP "${CMAKE_CURRENT_BINARY_DIR}/winappcli.zip")
        file(DOWNLOAD 
            "https://github.com/microsoft/WinAppCli/releases/latest/download/winappcli-${WINAPP_ARCH}.zip"
            "${WINAPP_ZIP}"
            SHOW_PROGRESS
        )
        
        file(ARCHIVE_EXTRACT INPUT "${WINAPP_ZIP}" DESTINATION "${WINAPP_DIR}")
        file(REMOVE "${WINAPP_ZIP}")
        message(STATUS "winapp CLI downloaded to ${WINAPP_DIR}")
    endif()
endif()

# Automatically restore Windows App SDK headers and generate certificate if needed
# This runs once during CMake configuration, not on every build
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.winapp/include")
    message(STATUS "Restoring Windows App SDK headers...")
    execute_process(
        COMMAND "${WINAPP_CLI}" restore
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        RESULT_VARIABLE RESTORE_RESULT
    )
    if(NOT RESTORE_RESULT EQUAL 0)
        message(WARNING "Failed to restore Windows App SDK. Run 'winapp restore' manually.")
    endif()
endif()

if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/devcert.pfx")
    message(STATUS "Generating development certificate...")
    execute_process(
        COMMAND "${WINAPP_CLI}" cert generate --if-exists skip
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        RESULT_VARIABLE CERT_RESULT
    )
    if(NOT CERT_RESULT EQUAL 0)
        message(WARNING "Failed to generate certificate. Run 'winapp cert generate' manually.")
    endif()
endif()

add_executable(cpp-app main.cpp)

# Link Windows Runtime libraries
target_link_libraries(cpp-app PRIVATE WindowsApp.lib OneCoreUap.lib)

# Add Windows App SDK include directory
target_include_directories(cpp-app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/.winapp/include)

通过此设置:

  • 当有人克隆存储库并运行 cmake -B build时,如果 PATH 中找不到,则会自动下载 winapp
  • Windows 应用 SDK标头和证书会自动恢复
  • 这些命令只在配置时运行一次,而不是在每次构建时,因为它们会检查文件是否已存在。
  • 如果命令失败,CMake 会显示一条警告,其中包含手动运行这些命令的说明
  • 下载的 WinApp 存储在.winapp-tools/中(如果需要,可将其添加到.gitignore

8. 使用 MSIX 打包

准备好分发应用后,可以使用同一清单将其打包为 MSIX。 MSIX 提供干净安装/卸载、自动更新和受信任的安装体验。

准备好包目录

首先,在发布模式下生成应用程序以实现最佳性能:

cmake --build build --config Release

然后,创建一个仅包含分发所需文件的目录,并复制发布的可执行文件:

mkdir dist
copy .\build\Release\cpp-app.exe .\dist\

生成开发证书

必须对 MSIX 包进行签名。 对于本地测试,请生成自签名开发证书:

winapp cert generate --if-exists skip

小窍门

证书的发布者必须与 Package.appxmanifest 中的 Publisher相匹配。 该 cert generate 命令会自动从清单中读取此内容。

签名和打包

现在你可以打包并签名:

# package and sign the app with the generated certificate
winapp pack .\dist --cert .\devcert.pfx 

小窍门

pack 命令会自动使用当前目录中的 Package.appxmanifest,并在打包之前将其复制到目标文件夹。 生成的 .msix 文件将位于当前目录中。

安装证书

在安装 MSIX 包之前,需要信任计算机上的开发证书。 以管理员身份运行此命令(每个证书只需执行此操作一次):

winapp cert install .\devcert.pfx

安装和运行

小窍门

如果在步骤 5 中使用 winapp run ,则可能已在系统上注册包。 首先使用 winapp unregister 删除开发注册,然后安装发布包。

winapp pack 命令在项目根目录中生成 MSIX 文件。 双击生成的 .msix 文件或使用 PowerShell 安装包:

Add-AppxPackage .\cpp-app_1.0.0.0_x64.msix

小窍门

MSIX 文件名包括版本和体系结构(例如)。 cpp-app_1.0.0.0_arm64.msix 检查目录的确切文件名。

现在,可以通过键入以下内容从终端中的任意位置运行应用:

cpp-app

应该看到“程序包系列名称”输出,以确认它已安装并以标识运行。

小窍门

如果需要重新打包应用(例如代码更改后),请在再次运行winapp pack之前,在Package.appxmanifest中递增Version。 Windows需要更高的版本号才能更新已安装的包。

提示

  1. 准备好分发后,可以使用证书颁发机构的代码签名证书对 MSIX 进行签名,以便用户无需安装自签名证书。
  2. Azure 受信任签名 服务是安全地管理证书并将登录集成到 CI/CD 管道的好方法。
  3. Microsoft Store将为你签名 MSIX,无需在提交之前进行签名。
  4. 您可能需要为每个支持的体系结构(x64, Arm64)分别创建一个 MSIX 包。 使用相应的生成器和体系结构标志配置 CMake。

后续步骤

  • 通过 winget 分发:将 MSIX 提交到 Windows 程序包管理器 社区存储库
  • 发布到 Microsoft Store:使用 winapp store 提交包
  • 设置 CI/CD:使用 GitHub Action 在流水线中自动打包
  • 探索 Windows APIs:使用包标识,现在可以使用通知设备端 AI和其他身份依赖的 API