macOS 上同时运行两个微信实例的曲折之路

开篇:一个看似简单的需求

作为一个经常需要在工作和个人微信之间切换的 Mac 用户,我突发奇想:能不能在 macOS 上同时运行两个微信实例?这样就不用频繁切换账号了。

想法很美好,现实却很骨感。我简单地复制了 /Applications/WeChat.app 到桌面,改名为 WeChat2.app,并修改了 Info.plist 中的 bundle ID 为 com.eflabs.talk。然后,我满怀期待地双击了这个新应用...

砰! 系统无情地抛出了一个错误:

Launch failed. Launchd job spawn failed. (Error Code: 153)

第一个坑:签名问题

作为一个有经验的开发者,我立刻意识到这可能是签名问题。macOS 要求所有应用都必须经过代码签名才能运行,而我复制的应用还保留着原始开发者的签名。

于是我尝试重新签名:

codesign --force --deep --sign - ~/Desktop/WeChat2.app

结果,另一个错误接踵而至:

resource fork, Finder information, or similar detritus not allowed

第二个坑:Finder 的"礼物"

这个错误信息很明确:代码签名工具检测到了 Finder 留下的"垃圾数据"。当我们复制文件时,Finder 会顺手添加一些扩展属性(extended attributes),比如:

  • com.apple.FinderInfo:Finder 的元数据
  • com.apple.fileprovider.fpfs#P:文件提供者的信息
  • Resource fork:传统 macOS 文件系统的资源分叉

这些"额外信息"在代码签名时是不被允许的,因为它们可能会影响签名的完整性验证。

漫长的探索:各种尝试

尝试 1:清理扩展属性

我的第一反应是清理这些扩展属性:

xattr -rc ~/Desktop/WeChat2.app

清理完成!再次签名... 还是失败。

尝试 2:删除 resource fork

既然扩展属性清理了,那可能是 resource fork 的问题。我检查了文件,发现确实有很多文件包含 resource fork:

find ~/Desktop/WeChat2.app -type f -exec sh -c 'test -e "$1/..namedfork/rsrc" && echo "$1 has resource fork"' _ {} \;

我尝试删除这些 resource fork,但依然无济于事。

尝试 3:使用 cp -RX 重新复制

cp -RX 可以排除 resource fork 进行复制。我重新复制了整个应用:

cp -RX /Applications/WeChat.app ~/Desktop/WeChat2.app

但问题依然存在。

尝试 4:使用 ditto 重新复制

ditto 命令据说可以更好地处理文件系统元数据。我尝试了:

ditto /Applications/WeChat.app ~/Desktop/WeChat2.app

还是没有解决问题。

关键时刻:找到真正的原因

在经历了无数次的失败后,我决定使用更详细的调试信息。我使用了 codesign 的详细输出模式:

codesign --force --sign - --verbose=4 ~/Desktop/WeChat2.app/Contents/PlugIns/WeChatMacShare.appex

这次,错误信息更具体了:

file with invalid attached data: Disallowed xattr com.apple.FinderInfo found on /Users/harvey/Desktop/WeChat2.app/Contents/PlugIns/WeChatMacShare.appex

啊哈! 问题出在目录本身的扩展属性,而不仅仅是文件。我之前的清理操作可能没有完全清理目录级别的扩展属性。

解决方案:系统化的清理和签名

找到了根本原因,解决方案就清晰了。关键是要:

  1. 彻底清理所有扩展属性:包括文件和目录
  2. 逐个签名组件:先签名所有子组件(.app、.appex、.framework),再签名主应用
  3. 使用 --preserve-metadata 选项:保留必要的元数据(entitlements、requirements 等)

完整的解决脚本

# 1. 重新复制应用(排除 resource fork)
cp -RX /Applications/WeChat.app ~/Desktop/WeChat2.app

# 2. 修改 bundle ID
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.eflabs.talk" ~/Desktop/WeChat2.app/Contents/Info.plist

# 3. 彻底清理所有扩展属性(文件和目录)
xattr -rc ~/Desktop/WeChat2.app
find ~/Desktop/WeChat2.app -type d -exec xattr -c {} \; 2>/dev/null

# 4. 移除所有现有签名
codesign --remove-signature ~/Desktop/WeChat2.app 2>/dev/null
find ~/Desktop/WeChat2.app -type d \( -name "*.app" -o -name "*.appex" -o -name "*.framework" \) -exec codesign --remove-signature {} \; 2>/dev/null

# 5. 逐个签名所有组件(从最内层开始)
find ~/Desktop/WeChat2.app -type d \( -name "*.app" -o -name "*.appex" -o -name "*.framework" \) | sort -r | while read dir; do
    xattr -c "$dir" 2>/dev/null
    codesign --force --sign - --preserve-metadata=entitlements,requirements,flags,runtime "$dir" 2>&1
done

# 6. 最后签名主应用
xattr -c ~/Desktop/WeChat2.app
codesign --force --sign - --preserve-metadata=entitlements,requirements,flags,runtime ~/Desktop/WeChat2.app

关键技巧

  1. sort -r:确保从最内层的组件开始签名(依赖关系)
  2. --preserve-metadata:保留 entitlements 等信息,避免应用功能受限
  3. 清理后立即签名:防止系统在签名过程中重新添加扩展属性

成功时刻:两个微信同时运行

执行完上述步骤后,我小心翼翼地尝试启动应用:

open -n ~/Desktop/WeChat2.app

成功了! 应用正常启动,并且我看到两个微信进程同时在运行:

  • 原始微信:com.tencent.xinWeChat (来自 /Applications/WeChat.app)
  • 新微信:com.eflabs.talk (来自 ~/Desktop/WeChat2.app)

它们使用不同的 bundle ID 和独立的数据目录,完全不会互相干扰。

经验总结

  1. macOS 的代码签名非常严格:不仅仅是文件内容,连文件系统元数据也会被检查
  2. Finder 会"好心"地添加元数据:复制文件时要注意清理扩展属性
  3. 签名顺序很重要:必须从最内层的依赖组件开始签名
  4. 使用详细输出模式调试--verbose=4 可以帮助定位具体问题
  5. --preserve-metadata 选项很关键:避免丢失重要的应用配置信息

后记

这个看似简单的需求,实际上涉及了 macOS 的安全机制、文件系统特性、代码签名等多个方面。虽然过程曲折,但最终成功解决了问题,也让我对 macOS 的应用签名机制有了更深入的理解。

现在,我可以愉快地同时使用两个微信账号了!🎉


本文记录了在 macOS 15.6 上同时运行两个微信实例的完整解决方案。如果遇到类似问题,希望这篇文章能帮助你少走弯路。