需求很简单:电脑上同时登两三个微信号。但实现路径上,macOS 和 Windows 完全是两个故事。本文讲讲这两个平台各自的破解思路,以及为什么这个工具「不修改微信本体」。
背景:微信为什么只能开一个
微信客户端通过两种机制强制单实例:
- macOS :微信 4.x 的单实例限制其实是沙盒容器内的一个文件锁------
app_data/lock/lock.ini,而沙盒容器路径由应用的 Bundle ID 决定 - Windows :启动时创建命名互斥体(Mutex),第二个进程发现互斥体已存在就退出
搞清楚机制,就能找到不碰微信本体的解法。
macOS 方案:改 Bundle ID + APFS 秒级克隆
既然锁在沙盒容器里、容器路径由 Bundle ID 决定,那么只要让每个实例用不同的 Bundle ID,各副本自然落入不同容器 → 不同锁 → 互不干扰。
创建副本分三步:
- APFS clonefile 克隆 整个
.app------这是写时复制(CoW),几乎不占额外磁盘空间,全流程 10 秒左右 - 修改
Info.plist的CFBundleIdentifier,同时改显示名(自定义名字会同步到 Finder / 启动台 / Dock) - ad-hoc 重签名 :改动 bundle 后签名失效,调用
/usr/bin/codesign重新签
rust
fn codesign_adhoc(app: &Path) -> PlatformResult<()> {
let out = Command::new("/usr/bin/codesign")
.args(["--force", "--deep", "--sign", "-"])
.arg(app)
.output()
.map_err(|e| format!("执行 codesign 失败:{e}"))?;
// ...
}
这个方案的意外收益是数据隔离 :每个副本独立沙盒容器,聊天记录天然分账户存放。微信升级后副本版本落后,工具检测到后可以批量重建副本------因为数据在容器里而不在 .app 里,重建不影响登录状态。
实测验证于微信 4.1.15。
Windows 方案:挂起启动 + 关互斥体
Windows 不能复制文件了事(会产生副本残留,微信一更新就全套失效),思路是让微信自己「以为」自己是第一个启动的:
CreateProcessW时挂起主线程(CREATE_SUSPENDED)- 用
NtQuerySystemInformation枚举该进程的全部句柄,逐个NtQueryObject查对象名 - 定位到互斥体名(微信 3.x 和 4.x 的名字不同,工具分别适配),关闭这个句柄
ResumeThread恢复执行------微信检查互斥体时发现不存在,认为自己第一个启动,正常跑起来
rust
// 仅对安全类型做名称查询(部分对象类型的 NtQueryObject 可能卡死)
let status = NtQuerySystemInformation(
SystemHandleInformation,
// ... 枚举句柄表
);
两个踩过的坑:
NtQueryObject对部分对象类型调用会卡死,必须先判断对象类型,只对安全类型查名字- 进程启动有瞬时抖动,句柄可能还没就绪,需要轮询重试,不能一次定成败
全程不写入、不替换任何微信文件,微信自动更新照常。
为什么选 Tauri 而不是 Electron
这种工具 95% 的代码在系统调用层(克隆、签名、句柄枚举),UI 只是薄薄一层管理面板。Tauri 2 + Rust 正好匹配:系统能力全在 Rust 侧异步 command(spawn_blocking),安装包 2MB 左右------Electron 同样功能得 80MB+。
架构很直白:
bash
src-tauri/src/platform/
├── macos.rs # APFS 克隆 / Bundle ID / codesign 重签名
├── windows.rs # 挂起启动 / NtQuerySystemInformation 句柄枚举 / 互斥体关闭
└── mod.rs # 平台抽象,按编译目标分发
边界声明
刻意不做:自动化操作、群控、批量营销、防撤回、消息修改。只做启动层处理。
- 源码:github.com/xiaoRui278/... (MIT,欢迎 Star / Issue)
- 官网与下载:vxmo.cting.vip

