「发布版只有那个人的电脑才能构建出来」──在关于 WinForms 或 WPF 业务应用的咨询中,这是一句真的经常听到的话。在本地的 Visual Studio 上做 Release 构建,压缩成 zip,放进共享文件夹。虽然能用,但没有人能回答「那个开发者休假的那天,能不能发布一个修复版本」这个问题。
明明 Web 应用的 CI/CD 资料到处都是,一到桌面应用话题就骤然变少。这固然有其道理——「部署对象不是服务器,而是客户那边的 PC」这一根本差异,确实意味着无法把 Web 那套文章原样照搬。不过,仅就构建与测试的自动化而言,桌面应用几乎可以用和 Web 相同的工作量来搭建。真正的门槛出现在再往后的签名与发布环节,而这里的现实解会因发布方式的不同而不同。
在本博客中,我们已经通过「Windows 应用发布方式怎么选 - MSI / MSIX / ClickOnce / xcopy / 自定义 updater 判断指南」梳理了如何选择发布方式,通过「Windows 出现「Windows 已保护你的电脑」提示的原因」整理了签名方面的思路。本文将以这两篇为前提,从实务角度整理用 GitHub Actions 能把 WinForms / WPF 应用的构建、测试、版本编号、签名、发布物制作自动化到什么程度。
1. 先说结论
- 最大的风险是「只能在某个开发者的 PC 上构建」这种状态。CI/CD 的第一目标并不是让发布全自动化,而是让构建不依赖任何特定的人的电脑就能复现出来。
- 仅有构建+测试自动化的最小配置,本身就已经很有价值。用
windows-latest+actions/checkout+actions/setup-dotnet+dotnet build / test+actions/upload-artifact,一个 YAML 文件就能搭起来。12 - WinForms / WPF 以 Windows Runner 为前提。由于它们以
net8.0-windows这类 Windows 专用 TFM 为目标3,若要在 CI 中执行测试,就需要 Windows 环境。 - 版本编号以标签驱动为落脚点。通过 push
v1.2.3这样的标签来触发发布构建,并把标签的值注入 MSBuild 的Version属性。4 - 签名是自动化中最大的门槛。自 2023 年 6 月起,公开 OV 证书的私钥被要求必须保存在 HSM 中,「把 PFX 放进 Secret、用 signtool 签名」这一以往的标准做法已经无法照旧使用。若想在 CI 中完整闭环,经由云签名服务是现实的做法。5
- 发布形式不同,接入 CI 的难易度差异很大。顺序依次是:xcopy(zip)最简单,MSIX 必须签名6,MSI 需要在 CLI 中调用配套工具,ClickOnce 则需要
msbuild /target:publish且颇有一些棱角。7 - 不要把 UI 自动化测试当成 CI 的必经关卡。把单元测试作为 CI 的必经关卡,UI 测试限定为冒烟测试并放在单独的作业中执行,这是现实可行的做法。
2. 桌面应用的 CI/CD 与 Web 有什么不同
首先梳理一下,为什么不能把 Web 应用的 CI/CD 模式(push → 构建 → 测试 → 部署到服务器)原样搬到桌面应用上。
| 视角 | Web 应用 | WinForms / WPF 桌面应用 |
|---|---|---|
| 部署对象 | 自己管理的服务器 | 客户・现场的 PC(不受管理) |
| 发布单位 | 在服务器上统一切换 | MSI / MSIX / ClickOnce / zip 等多种多样,展开的时机取决于对方 |
| 回滚 | 可以在服务器端回退 | 已经发布到的 PC 上很难轻易回退,必须保留旧版安装程序 |
| 签名 | 通常不需要(TLS 由基础设施层负责) | 对可执行文件・安装包进行代码签名实质上是必需的 |
| 构建环境 | 容易在 Linux Runner 上闭环 | 以 Windows Runner 为前提 |
| 测试 | 容易以无头(headless)方式闭环 | 单元测试相同,UI 测试需要桌面会话 |
| 「部署」的含义 | 直到反映到生产环境为止 | CI 的职责范围止于「发布物制作完成」,安装是另一道工序 |
最重要的是最后一行。对桌面应用而言,CI/CD 流水线的出口并不是「反映到生产环境」,而是「一份已签名的发布物,被放在随时可以取出的地方」。从这里往后(向客户展开、自动更新)是发布方式设计的话题,属于「发布方式判断表」所涉及的范围。反过来说,只要把出口这样明确地划定下来,桌面应用的 CI/CD 就能用和 Web 相同的工具组合来搭建。
3. 最小配置 ── 用 GitHub Actions 实现构建+测试
一开始应该引入的就只有这些。由于 GitHub 托管 Runner 会为每个作业分配一台全新的虚拟机1,每次 push 都会在一台全新的 Windows 上运行构建与测试,「只有那个人的电脑上才装了的 SDK」这类依赖会当场暴露出来。
name: build-and-test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: windows-latest # WinForms / WPF 必须使用 Windows Runner
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --configuration Release --no-build
- name: Publish
run: dotnet publish src/MyApp/MyApp.csproj -c Release -o publish
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: MyApp
path: publish
补充三个要点。
第一,runs-on: windows-latest 是基本配置。WinForms / WPF 项目的 TargetFramework 是 net8.0-windows 这类 Windows 专用 TFM,是启用了 UseWindowsForms 或 UseWPF 的 .NET 桌面 SDK 项目。3 严格来说,如果只是想编译,启用 EnableWindowsTargeting 后也能在 Linux Runner 上构建,但 dotnet test 这类涉及实际执行的步骤需要 Windows 环境,因此在这种把测试也放进同一个作业里运行的配置中,直接使用 Windows Runner 更为顺理成章。若是 .NET Framework 4.x(旧格式 csproj),就要用 MSBuild 和 NuGet CLI 而不是 dotnet build,但二者都预装在 Windows Runner 上,思路是一样的。
第二,一定要用 actions/upload-artifact 保留发布物。「这次构建的全部产物都能从 GitHub 取出来」,这正是摆脱依赖特定人电脑的实质内容。即使是急需的临时验证版本,也只需从 Actions 页面下载 zip 即可。
第三,在这个阶段还不做签名和发布。仅凭这套最小配置,就能获得「main 分支始终可构建、可测试」「任何人都能取出相同的构建产物」这两项保证,根据笔者的经验,小规模团队的大部分烦恼到这一步就已经解决了。
4. 版本号的自动编号 ── 标签驱动发布
下一个阶段是解决「这个 zip 到底是几版?」的问题。在依赖本地构建的运维方式下,忘记修改 csproj 里的 Version、导致好几代产物都停留在同一个 1.0.0 的事故是常见套路。实务上的落脚点是标签驱动发布:在想要发布的提交上打上 v1.2.3 这样的标签,以此为触发条件运行工作流,把从标签名中取出的版本号注入到构建中。
name: release
on:
push:
tags: [ 'v*' ]
jobs:
release:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
# push 标签不会触发第 3 章中的构建+测试工作流,
# 所以在生成发布产物之前,这里也要重新跑一次测试
- name: Test
run: dotnet test --configuration Release
- name: Publish with version from tag
shell: pwsh
run: |
$version = $env:GITHUB_REF_NAME.TrimStart('v') # v1.2.3 -> 1.2.3
dotnet publish src/MyApp/MyApp.csproj `
-c Release -o publish `
-p:Version=$version
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: MyApp-${{ github.ref_name }}
path: publish
以 -p:Version=1.2.3 这样的形式作为 MSBuild 属性传入后,在 .NET SDK 项目中,AssemblyVersion 和 FileVersion 会默认根据 Version 的前缀部分(去掉后缀的部分)生成,InformationalVersion 则默认直接取自 Version 本身。4 csproj 中只放开发用的占位值,发布时的正式版本号则只由标签一处持有,实现了一元化管理。
有一点需要注意。actions/upload-artifact 的产物存在仓库层面的保留期限(默认 90 天),过期就会被删除。由于桌面应用需要长期保存旧版本安装程序以便回滚,应把标签构建的产物发布到 GitHub Release 之类的永久存放位置,把 Actions 的 Artifact 当作纯粹的临时中转来看待。
好处是运维完全收拢在 Git 之内。「客户那边的 1.2.3 对应哪个提交」由标签确定,EXE 属性中显示的文件版本与 Git 标签机械地保持一致。此外,从 .NET 8 SDK 起,InformationalVersion 默认还会附加 Git 提交哈希(SourceRevisionId),4 只要把它显示在版本信息界面上,就可以直接从发布物追溯到具体提交。
5. 把代码签名接入 CI ── 这是最大的门槛
那些顺利把构建和版本号自动化的团队,几乎无一例外都会在签名这一步卡住。关于为什么 Store 之外分发的 Windows 应用实质上必须做代码签名(SmartScreen、企业安全产品、篡改检测),我们已经在「Windows 出现「Windows 已保护你的电脑」提示的原因」中整理过,这里只专注于在 CI 的哪个环节、以什么方式执行签名。
5.1 signtool 的基本形式
签名操作本身只是一条命令。signtool 包含在 Windows SDK 中,GitHub 的 Windows Runner 上也可以使用。当前版本的 SDK 要求必须指定 /fd(文件摘要)和 /td(时间戳摘要),推荐使用 SHA256。8
signtool sign /f MyCert.pfx /p $env:PFX_PASSWORD `
/fd SHA256 /tr http://timestamp.digicert.com /td SHA256 `
publish\MyApp.exe
时间戳(/tr)虽然可以省略,但一定要加上。有了时间戳,即使证书过期之后,也能验证「签名当时是有效的」,从而让已发布文件上的签名持续保持有效。86
5.2 证书类型与接入 CI 的现实情况
问题不在于命令本身,而在于私钥放在哪里。证书的获取形态不同,接入 CI 的方式也会有根本性的差异。
| 证书形态 | 私钥所在位置 | 接入 CI 的难易度 | 备注 |
|---|---|---|---|
| 云签名服务(Azure Artifact Signing = 原 Trusted Signing 等) | 云端 | 容易接入(首选方案)。设计上就以对接 GitHub Actions 等为前提 | 可用的国家/地区有限制(法人为美国、加拿大、欧盟、英国等)5 |
| OV 证书(2023 年 6 月以后新申请) | 必须使用 HSM / USB Token | 由于 Token 无法插入 Runner,原样无法使用。若 CA 提供云端 HSM 选项则可以 | 依据 CA/Browser Forum 的要求5 |
| EV 证书 | HSM / USB Token | 同上 | SmartScreen 的即时信任效果已于 2024 年废除,在签名运维层面可以与 OV 同等看待5 |
| 旧式 PFX 文件(以往发放、内部 CA、自签名) | 文件 | 以 Base64 存入 Secret 后再还原(见下文) | 面向公开发布、新申请的证书原则上已经无法再获得这种形态 |
也就是说,搜索时经常见到的「把 PFX 放进 GitHub 的 Secret、用 signtool 签名」这套配置,对内部 CA 或已有的 PFX 仍然有效,但对今后新申请公开证书的情况,前提已经不成立了。如果是从零搭建,现实的做法是围绕从一开始就支持 CI 对接的云签名服务来考虑。5 若仍在用 USB Token 方式运维,就会变成一种折衷配置:只把签名这一环节留在本地电脑,或留在插着 Token 的自建(self-hosted)Runner 上。
5.3 Secret 管理注意事项
以下是把 PFX 方式(内部 CA、已有证书)接入 CI 时的标准做法。
- 把 PFX 转成 Base64 字符串存入 GitHub 的 Secret,再在作业中还原成文件。这是 GitHub Docs 中针对如何在 Secret 中处理二进制数据所给出的做法。9
- 把密码放进另一个 Secret。Secret 的值会在日志中自动打码,9 但这种保护不会延伸到经过加工的派生值。除签名步骤之外,不要把它作为环境变量传给其他步骤。
- 来自 Fork 的 Pull Request 不会拿到 Secret(
GITHUB_TOKEN除外)。9 不过,作业本身仍然会以空 Secret 的方式执行,因此上面的还原步骤会因为对空字符串做 Base64 解码而失败。应该把签名步骤放进像第 4 章那样由标签触发的发布工作流中(它不会在 Fork PR 上触发),或者加上if: github.event_name != 'pull_request'这样的条件来明确跳过。
- name: Restore signing certificate
shell: pwsh
run: |
$bytes = [Convert]::FromBase64String($env:PFX_BASE64)
[IO.File]::WriteAllBytes("$env:RUNNER_TEMP\sign.pfx", $bytes)
env:
PFX_BASE64: ${{ secrets.SIGNING_PFX_BASE64 }}
还有一个关键点:收窄能够访问签名密钥的工作流范围。拥有仓库写权限的人可以改写工作流,因此应该把签名用的 Secret 放进带审批者(Environment)中,让只有发布工作流才能引用到它。已签名的二进制文件本身就是「这是我们公司制作的」这一事实的证明,因此对密钥的处理,应该按照与自动更新分发基础设施相同等级的信任边界来设计(这一点也可以参考「自动更新的安全设计——为什么仅靠 HTTPS 还不够」)。
6. 按发布形式划分的 CI/CD 集成判断表
走到签名这一步之后,最后剩下的就是发布物的形态。发布方式本身的选型交给「发布方式判断表」去处理,这里只从 CI/CD 的角度做比较。
| 发布形式 | 在 CI 中的易用程度 | 在 CI 中的制作手段 | 签名要求 | 自动更新 |
|---|---|---|---|---|
| xcopy(zip 发布) | 最简单 | 仅需 dotnet publish+压缩 |
对 EXE/DLL 签名(推荐) | 无(手动展开) |
| xcopy+自制 updater | 简单(仅就构建而言)。更新分发的设计另外是一项重活 | dotnet publish+生成清单 |
必须做 EXE 签名+更新文件的校验设计 | 自制(需要设计信任边界) |
| MSI | 中等 | 在 CLI 中运行 WiX 等工具 | 对 MSI 文件签名(推荐~实质必需) | 无(需要另外的发布机制) |
| MSIX | 中等 | MSBuild / MakeAppx+signtool | 必须对包签名(未签名无法安装)6 | 可通过 App Installer 等实现 |
| ClickOnce | 棱角很多 | msbuild /target:publish+发布配置文件(dotnet CLI 不支持)7 |
清单签名+EXE 签名 | 内置(该方式的主要卖点) |
补充说明如下。
- xcopy(zip):第 3、4 章的工作流几乎就是完成形态。无论最终采用哪种发布形式,先把这种形态跑通都是最近的路径。
- MSI:把安装程序定义(WiX 等)放进仓库,通过 CLI 构建。比起生成本身,「MSI 中要包含什么(服务注册、per-machine/per-user)」的设计才是重点所在。
- MSIX:由于 Windows 不允许安装未签名的 MSIX,如果不把签名的自动化一并做好,CI 化就无法真正完成。6 反过来说,只要签名基础设施到位,这是一种容易接入 CI 的形式。若通过 Microsoft Store 发布,还有另一种解法——Store 一侧会重新签名,因此不需要自备证书。5
- ClickOnce:无法通过 dotnet CLI 发布,需要使用指定了发布配置文件(.pubxml)的
msbuild /target:publish /p:PublishProfile=...。IDE 中每次发布都会自动递增的修订号(ApplicationRevision),在命令行构建中不会自动递增,7 这使得第 4 章介绍的标签驱动、显式传入版本号的设计成为必需。这里还有一个要注意的地方:ClickOnce 的更新判定所依据的不是-p:Version(程序集信息),而是部署侧的版本号(ApplicationVersion/ApplicationRevision),因此如果不额外以/p:ApplicationVersion=1.2.3.0这样的形式,把从标签生成的四段式版本号单独传入,新发布的版本就不会被识别为更新。相关机制以及适用与否,在「ClickOnce 是什么 - 从实务角度整理其原理、更新机制以及适用与不适用的场景」中有解说。
单从 CI/CD 的角度来说,「先从 zip 开始,等发布需求定下来之后再把 MSIX 或 MSI 作为一个作业加进去」是增量成本最小的推进方式。前段(构建、测试、版本号)对所有形式都是通用的,因此之后替换发布形式相关的步骤,也不会浪费之前的投入。
7. 测试自动化要做到什么程度
最后,来划定一下作为 CI 关卡(必需检查)应该要求做到什么程度的测试。
| 测试层级 | 在 CI 中的处理 | 理由 |
|---|---|---|
| 单元测试(逻辑) | 必经关卡,每次 Pull Request 都执行 | 快、稳定,在 Windows Runner 上直接就能跑 |
| 无界面集成测试(数据库、文件 I/O) | 原则上必需,如果耗时就分离到夜间执行 | 外部依赖的初始化需要一些功夫,但自动化的价值很高 |
| UI 自动化测试(冒烟) | 放在单独作业中,只跑少量用例 | 需要桌面会话,不稳定因素较多,仅限于启动~主要界面跳转这类程度 |
| UI 自动化测试(全面覆盖) | 不作为 CI 的关卡 | 维护成本容易超过收益 |
对桌面应用而言,自动化价值最高的并不是 UI,而是 UI 之下的那一层。如果业务逻辑埋在代码后置(code-behind)里,就写不了单元测试,因此把逻辑从界面中分离出来,本身就是 CI/CD 的前提投入。UI 自动化测试应限定在「启动、登录、主要界面能打开」这类冒烟测试上,放到夜间等单独的触发条件下执行,是现实可行的做法。Runner 上的 UI 测试在界面会话、分辨率、时序方面陷阱很多,这方面的内容(包括 CI、无人值守执行的坑)在「Windows 桌面应用的 UI 自动化测试 ── UI Automation 原理与用 FlaUI 打造不易损坏的测试」中有详细处理。
8. 总结
- 只要把出口定义为「已签名发布物的完成」,桌面应用的 CI/CD 就能用与 Web 相同的工具组合来搭建。
- 最小配置是
windows-latest+actions/checkout+actions/setup-dotnet+dotnet build / test+actions/upload-artifact。仅此一项就能消除「只能在某个开发者的 PC 上构建」的风险。12 - 由于 WinForms / WPF 以
net8.0-windows这类 Windows 专用 TFM 为目标,因此以 Windows Runner 为前提。3 - 版本号通过
v1.2.3标签 → 注入-p:Version的标签驱动方式实现一元化管理。4 - 签名是 CI 自动化中最大的门槛。如今 OV 证书也必须使用 HSM 保管,若想在 CI 中完整闭环,云签名服务是首选方案;PFX+Secret 方式则更适合内部 CA、已有证书的场景。59
- 发布形式接入 CI 的难易度依次为:xcopy(zip)→MSI / MSIX→ClickOnce。MSIX 必须签名,6 ClickOnce 需要留意
msbuild /target:publish以及修订号不会自动递增这两点。7 - 把单元测试作为 CI 的必经关卡,UI 测试限定为冒烟测试并放在单独的作业中执行,把逻辑从界面中分离出来是前提投入。
相关文章
- Windows 应用发布方式怎么选 - MSI / MSIX / ClickOnce / xcopy / 自定义 updater 判断指南
- Windows 出现「Windows 已保护你的电脑」提示的原因
- ClickOnce 是什么 - 从实务角度整理其原理、更新机制以及适用与不适用的场景
- 自动更新的安全设计——为什么仅靠 HTTPS 还不够
- Windows 桌面应用的 UI 自动化测试 ── UI Automation 原理与用 FlaUI 打造不易损坏的测试
相关咨询领域
合同会社小村软件(KomuraSoft)除了承接 WinForms / WPF 应用开发之外,也承接从本地构建运维向 CI/CD 迁移、用 GitHub Actions 设计构建・签名・发布流水线、以及为现有桌面应用实现可测试化(逻辑分离)等业务。
参考链接
-
GitHub Docs, GitHub-hosted runners reference。关于
windows-latest等 Runner 标签、每个作业都会分配一台全新虚拟机,以及公共仓库中标准 Runner 免费这几点的说明。 ↩ ↩2 ↩3 -
Microsoft Learn, GitHub Actions and .NET。关于通过 GitHub Actions 实现 .NET 的 CI/CD、actions/checkout 与 actions/setup-dotnet 的作用,以及在工作流中使用 dotnet restore / build / test / publish 的说明。 ↩ ↩2
-
Microsoft Learn, MSBuild reference for .NET Desktop SDK projects。关于 WinForms / WPF 项目会指定
net8.0-windows这类 Windows 专用 TFM,以及通过UseWindowsForms/UseWPF启用 .NET 桌面 SDK 的说明。 ↩ ↩2 ↩3 -
Microsoft Learn, Set assembly attributes in a project file。关于
AssemblyVersion/FileVersion(去掉后缀)与InformationalVersion默认从Version属性生成,以及 .NET 8 SDK 及以后版本会把SourceRevisionId(提交哈希)附加到InformationalVersion中的说明。 ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Code signing options for Windows app developers。关于自 2023 年 6 月起 CA/Browser Forum 要求 OV 证书的私钥必须保存在 HSM/硬件 Token 中、EV 证书的首次 SmartScreen 免信任效果已于 2024 年废除、Azure Artifact Signing(原 Trusted Signing)无需 Token 即可与 GitHub Actions 等集成但可用地区有限,以及 Store 端的 MSIX 发布会由 Microsoft 重新签名的说明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, Sign an MSIX package。关于 Windows 要求 MSIX 包必须具备有效代码签名,以及时间戳能让签名验证在证书过期后依然保持有效的说明。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Build .NET ClickOnce applications from the command line。关于 .NET 的 ClickOnce 发布需要使用指定了发布配置文件的
msbuild /target:publish,以及ApplicationRevision在命令行构建中不会自动递增的说明。 ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, SignTool。关于 SignTool 包含在 Windows SDK 中、当前版本要求指定
/fd与/td并推荐 SHA256,以及通过/tr指定 RFC 3161 时间戳的说明。 ↩ ↩2 -
GitHub Docs, Using secrets in GitHub Actions。关于 Secret 值会在日志中自动隐藏、把证书等二进制数据以 Base64 存入 Secret 并在作业中还原的步骤,以及来自 Fork 触发的工作流不会拿到 Secret 的说明。 ↩ ↩2 ↩3 ↩4
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Windows 桌面应用的 UI 自动化测试 ── UI Automation 原理与用 FlaUI 打造不易损坏的测试
本文从 Windows UI Automation 的原理(树结构、AutomationId、控件模式)出发,梳理 WinForms/WPF 应用的 UI 自动化测试。内容涵盖用 FlaUI 实现的最小示例、WinAppDriver 的现状、通过 AutomationId ...
在桌面应用中使用 .NET Generic Host 与 BackgroundService 的理由
本文整理在 Windows 工具或常驻应用中,如何使用 Generic Host 与 BackgroundService 来梳理启动、定期处理、退出处理、日志、配置与 DI。
业务应用数据库架构的版本管理 ── 防止「每个客户数据库都不一样」的迁移实践
一份为分散在各客户处的业务应用数据库架构做版本管理的实践指南。整理了 PRAGMA user_version 与前向迁移的 C# 实现、EF Core Migrations・DbUp・自行实现的判断表,以及两阶段发布策略。
当自研 Windows 应用被 Microsoft Defender 判定为病毒 ── 误报处理与性能影响应对指南
本文整理自研 Windows 应用被 Microsoft Defender 误报时的正规处理方法:现代杀毒软件的判定机制、向 Microsoft 提交误报、从隔离中恢复文件,以及正确设置排除项及其风险。
Windows 应用的任务栏托盘常驻与 Toast 通知 —— NotifyIcon 的坑与 AppNotification 的选型
本文整理了将业务 Windows 应用常驻在任务栏托盘(通知区域)并通过 Toast 通知告知用户的实现要点。内容涵盖 NotifyIcon 的正确用法与「关闭后驻留托盘」的设计、资源管理器重启后的重新注册、三种 Toast API(Windows App SDK AppN...
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
UI 线程 & 计时器
整理 WPF / WinForms UI 线程、异步流程、Dispatcher 使用、计时器判断的主题页面。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
Windows 应用程序开发
支持包含常驻处理、设备联动、运行日志与可维护结构的 Windows 桌面应用程序。
Windows 软件维护 & 现代化
支持既有 Windows 软件的阶段性升级、功能追加、64 位就绪以及可维护性的重构。
常见问题
汇总了咨询这一主题时常见的问题。
- WinForms / WPF 应用的构建应该在 GitHub Actions 的哪种 Runner 上运行?
- 应使用 windows-latest 等 Windows Runner。WinForms / WPF 项目以 net8.0-windows 这类 Windows 专用目标框架为对象,无论是验证构建产物的运行情况,还是通过 dotnet test 执行测试,都需要 Windows 环境。由于 GitHub 托管 Runner 会为每个作业分配一台全新的虚拟机,因此可以获得不依赖开发者本人 PC 的、可复现的构建。公共仓库使用标准 Runner 是免费的,私有仓库则按分钟计费。
- 代码签名能在 CI 中完全自动化吗?
- 这取决于证书的持有方式。以往那种把 PFX 文件放进 Secret、用 signtool 签名的方式,由于 2023 年 6 月以后 CA/Browser Forum 的要求规定公开 OV 证书的私钥必须保存在 HSM(硬件)中,对于新申请的证书,原则上已经无法再使用这种方式。USB Token 形态的证书无法插入云端 Runner,因此如果想在 CI 中实现完全自动化,现实的做法是经由 Azure Artifact Signing(原 Trusted Signing)这类云签名服务,或 CA 提供的云端 HSM。如果继续沿用 Token 运维方式,就只能把签名这一环节留在本地机器或自建(self-hosted)Runner 上。
- 应该从哪里开始自动化?
- 首先应该只引入构建+测试的自动化。仅仅做到每次 push 都会在 windows-latest Runner 上运行 dotnet build / dotnet test,就能消除「只能在某个开发者的 PC 上构建」「合并导致构建损坏却要到发布前夕才发现」这两个最大的风险。签名、安装程序制作、发布的自动化可以之后再逐步添加,如果一开始就想把所有环节都搭好,往往会卡在签名这一步。
- 发布形式(MSI / MSIX / ClickOnce / xcopy)不同,CI/CD 的搭建难易度也会不同吗?
- 会有很大不同。xcopy 发布(zip)只需压缩 dotnet publish 的输出,是最简单的方式。MSIX 可以通过 MSBuild 与 signtool 接入 CI,但对包进行签名是必需的。MSI 可以通过在 CI 中调用 WiX 等工具来实现自动化。ClickOnce 无法通过 dotnet CLI 发布,需要组合使用 msbuild /target:publish 与发布配置文件(publish profile),并且还要注意命令行方式下修订号(revision)不会自动递增这一点。在决定发布方式的阶段,就应该把是否容易接入 CI 也纳入判断依据。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。