在macOS上交叉编译Windows Rust程序 | AI生成和翻译

Home 2026.04

在macOS上交叉编译仅限Windows的Rust项目:来自WeFriends的经验

最近我发现了WeFriends——一个用Rust编写的开源微信好友检测工具。它使用egui构建GUI,并通过DLL注入Windows版微信桌面客户端来检测哪些好友已删除或拉黑你。这个项目很有趣——但它是专门为Windows构建的。我想看看能否至少让它在macOS上完成编译。

首次构建尝试

运行cargo build立即遇到了障碍:

error[E0433]: failed to resolve: could not find `windows` in `os`
 --> src/wechat_manager.rs:1:14
  |
1 | use std::os::windows::process::CommandExt;
  |              ^^^^^^^ could not find `windows` in `os`

这很合理——在macOS上根本不存在std::os::windows这个模块。该模块由rustc根据条件编译,仅在针对Windows目标时可用。

根本问题

代码库中的Windows专用代码分散在多个层面:

  1. 无条件Windows导入——std::os::windows::process::CommandExtstd::process::Command(仅在Windows代码块中使用)、用于DLL加载的libloading类型
  2. 构建脚本——build.rs无条件使用了extern crate winres,这在非Windows系统上无法解析
  3. Cargo.toml——winreswinapi被列为普通的[build-dependencies],导致在所有平台都被下载和编译

有趣的是,原作者确实在函数体内使用了#[cfg(target_os = "windows")](例如kill_wechatstart_wechat),但忘记在文件顶部对导入语句类型别名进行条件控制。

解决方案

1. 使用#[cfg]控制Windows专用导入

// 修改前
use std::os::windows::process::CommandExt;
use std::process::{Command, Stdio};
use libloading::{Library, Symbol};
use rand::Rng;

// 修改后
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
#[cfg(target_os = "windows")]
use std::process::{Command, Stdio};
#[cfg(target_os = "windows")]
use libloading::{Library, Symbol};
#[cfg(target_os = "windows")]
use rand::Rng;

关键点:仅在#[cfg(target_os = "windows")]代码块内使用的导入语句本身也必须受条件控制。Rust的未使用导入警告通常能发现这个问题,但由于代码从未在非Windows平台编译,所以无人察觉。

std::time::Durationtokio::time这样的导入被跨平台代码使用(install_wechat使用了tokio::time::sleep),因此保持无条件导入。

2. 为公开函数添加非Windows存根实现

login_wechatunhook_wechat等函数在main.rs中被无条件调用。与其在每个调用点包装#[cfg],不如添加存根实现:

#[cfg(not(target_os = "windows"))]
pub async fn login_wechat() -> Result<u16> {
    Err(anyhow::anyhow!("仅支持Windows系统"))
}

#[cfg(target_os = "windows")]
pub async fn login_wechat() -> Result<u16> {
    // ... 实际实现
}

这保持了所有平台上公开API的一致性。GUI可以在任何地方编译运行;只是尝试执行Windows特定功能时会返回错误。

3. 使构建依赖平台特定化

# 修改前
[build-dependencies]
winres = "0.1"
winapi = { version = "0.3", features = ["winnt"] }

# 修改后
[target.'cfg(target_os = "windows")'.build-dependencies]
winres = "0.1"
winapi = { version = "0.3", features = ["winnt"] }

并在build.rs中包装整个主体:

fn main() {
    #[cfg(target_os = "windows")]
    {
        let mut res = winres::WindowsResource::new();
        res.set_manifest_file("app.manifest");
        // ...
        res.compile().unwrap();
    }
}

结果

经过这些修改,cargo build在macOS上成功运行,仅有一些警告(已弃用的rand方法、注释代码块中未使用的变量)。

它真的能在macOS上运行吗?

不能——至少在功能上不行。GUI可以启动,但每个核心功能都依赖于:

这个应用本质上是一个Windows工具。让它跨平台编译对开发很有用(IDE支持、CI检查、库兼容性测试),但运行时行为从设计上就是仅限Windows的。

经验总结

  1. 控制导入语句,而不仅仅是函数体。 如果函数内的代码受#[cfg]控制,那么它使用的类型很可能需要在导入层面进行同样的条件控制。

  2. 在Cargo.toml中使用平台特定的依赖表。 [target.'cfg(target_os = "windows")'.dependencies]可以防止在其他平台拉取和编译Windows专用的crate。

  3. 为公开API提供存根实现。 如果你的库公开了仅在一个平台可用的函数,返回Err("unsupported")的存根实现比缺失符号更好。这让下游代码可以在任何地方编译,同时在运行时优雅地失败。

  4. #[cfg]具有传染性。 一旦某个元素被条件控制,所有依赖它的元素也需要被控制——或者你需要提供替代路径。在模块层面规划cfg边界,而不是逐行添加。


Back Donate