本指南演示如何将 winapp CLI 与 C++ 应用程序配合使用,以包标识进行调试,并将应用程序打包为 MSIX。
包标识是Windows app模型中的核心概念。 它允许应用程序访问特定的Windows API(例如通知、安全、AI API 等),具有干净的安装/卸载体验等。
标准可执行文件(如使用 cmake --build创建的可执行文件)没有包标识。 本指南演示如何添加它进行调试,然后将其打包以供分发。
先决条件
生成工具:使用 CMake 支持的编译器工具链。 此示例使用Visual Studio。 可以安装社区版(如果已安装则更新):
winget install --id Microsoft.VisualStudio.Community --source winget --override "--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --passive --wait"安装后重新启动。
CMake:安装 CMake(或更新(如果已安装):
winget install Kitware.CMake --source wingetwinapp CLI:通过 winget 安装
winappcli(或更新(如果已安装):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 安装一样),并在一个步骤中启动应用。 调试不需要证书或签名。
生成可执行文件:
cmake --build build --config Debug使用标识运行:
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 build 和 cmake --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需要更高的版本号才能更新已安装的包。
提示
- 准备好分发后,可以使用证书颁发机构的代码签名证书对 MSIX 进行签名,以便用户无需安装自签名证书。
- Azure 受信任签名 服务是安全地管理证书并将登录集成到 CI/CD 管道的好方法。
- Microsoft Store将为你签名 MSIX,无需在提交之前进行签名。
- 您可能需要为每个支持的体系结构(x64, Arm64)分别创建一个 MSIX 包。 使用相应的生成器和体系结构标志配置 CMake。