c# 项目应直接调用 bsdiff/bspatch 而非自行实现二进制差分;需用绝对路径、引号包裹参数、重定向 stderr、设置超时,并注意目标端兼容性、签名、架构匹配及小文件/碎片化场景下的适用边界。

用 bsdiff 生成二进制补丁最可靠
直接说结论:C# 没有内置的、生产可用的二进制差分(binary diff)库,硬写容易出错。实际项目里,bsdiff(配合 bspatch)是事实标准,压缩率高、跨平台、被 Chromium / Unity / 很多游戏更新系统长期验证过。C# 只需调用它,别自己实现 diff 算法。
常见错误是试图用 BinaryWriter + MemoryStream 手动比对字节 —— 这只能发现“哪些位置变了”,但无法生成紧凑补丁;补丁体积可能比原文件还大,且无法处理插入/移动类变更(比如 DLL 重排了函数顺序)。
- Windows 下直接下载预编译的
bsdiff.exe和bspatch.exe(官方源或 vcpkg 构建) - Linux/macOS 用包管理器安装:
sudo apt install bsdiff4(注意不是bsdiff,是 Python 封装版,要确认底层仍是 C 实现) - C# 中用
Process.Start调用,别用FileStream.Read自己解析 ——bsdiff输出的是自定义二进制格式,没文档别碰
C# 调用 bsdiff 的安全写法
关键不是“能不能调”,而是“怎么避免路径注入、权限失败、超时卡死”。很多人用 Process.Start("bsdiff old.bin new.bin patch.bsdiff") 一跑就失败,问题全在参数和环境。
- 绝对路径优先:
bsdiff可执行文件路径必须用Path.GetFullPath解析,不能依赖PATH环境变量 - 文件名带空格或特殊字符?必须用双引号包裹每个路径参数:
""C:\tools\bsdiff.exe" "old.dll" "new.dll" "update.patch"" - 一定要设
ProcessStartInfo.RedirectStandardError = true,bsdiff出错时只往 stderr 写提示(比如 “file is too large”),stdout 是空的 - 加超时:
process.WaitForExit(30000),大文件 diff 可能卡住,别让线程无限等
示例片段:
var psi = new ProcessStartInfo
{
FileName = Path.GetFullPath("bsdiff.exe"),
Arguments = $""{oldPath}" "{newPath}" "{patchPath}"",
UseShellExecute = false,
RedirectStandardError = true
};
var p = Process.Start(psi);
p.WaitForExit(30000);
if (!p.ExitCode.Equals(0))
throw new InvalidOperationException($"bsdiff failed: {p.StandardError.ReadToEnd()}");
bspatch 在目标机器上运行的兼容性坑
生成补丁只是第一步;真正容易翻车的是应用补丁阶段——用户机器上没 bspatch、架构不匹配(x64 补丁用 x86 bspatch)、或旧文件被占用。
- 别让用户自己装
bspatch:把对应平台的bspatch静态链接版(如 musl 编译的 Linux 版)随补丁一起下发 - Windows 上如果旧文件正在运行(比如主程序 DLL),
bspatch会失败 —— 必须先结束进程,或改用“释放到临时目录 + 原子替换”策略(File.Replace) - macOS 对签名敏感:用
bspatch修改过的可执行文件会破坏code signature,后续启动可能被 Gatekeeper 拦截;必须重新签名(codesign -f -s "Your Cert" patched.app) - Android/iOS 不适用:移动端得用其他方案(如 Google’s
zucchini或自研 delta 库),bsdiff无官方支持
什么时候不该用二进制补丁
补丁不是银弹。很多团队盲目上 bsdiff,结果发现收益远低于预期,甚至引入新问题。
- 文件小于 1MB?差分后体积节省不明显,反而增加打包/校验复杂度;直接全量更新更稳
- 频繁小修改(比如配置文件、JSON 资源)?用文本 diff(
diff-match-patch)+ 应用逻辑更合适,二进制补丁反而放大噪声 - 旧版本碎片化严重(用户停留在 v1.2 / v1.5 / v1.9)?你得为每对版本生成补丁,存储和调度爆炸式增长;这时应强制升级通道 + 全量包兜底
- 补丁文件本身需要校验?别只算 MD5 ——
bsdiff输出不可预测,相同输入在不同机器上可能产出不同字节(取决于编译器优化),必须用输出内容做哈希,而非过程
真正的难点从来不是“怎么生成一个 patch 文件”,而是怎么让成千上万用户在各种环境下安静地、可回滚地完成一次二进制替换。补丁只是工具链里最显眼的一环,底下全是状态管理、原子操作、失败恢复的细节。










