macOS下Rust通过libloading动态加载的.dylib无法访问主进程运行时设置的环境变量(Windows正常)
macOS下Rust通过libloading动态加载的.dylib无法访问主进程运行时设置的环境变量(Windows正常)
嗨,我之前也踩过这个macOS动态库环境变量的坑,太懂这种跨平台不一致的糟心了!先帮你理清楚问题所在,再给你几个实用的解决办法。
先确认下你的问题场景和代码
首先还原下你的情况:
- Windows上一切正常:Rust主进程运行时设的环境变量,动态加载的.dll能妥妥读到
- 到了macOS就拉胯:同样的逻辑,用libloading加载Go编译的.dylib,不管是库初始化的
init函数,还是调用sayHello的时候,都读不到Rust刚设的TEST_ENV值,输出全是空的
先把你提供的关键代码贴出来(方便大家对照排查):
Go侧生成.dylib的代码
package main import "C" import ( "fmt" "os" ) //export sayHello func sayHello() { testEnv := os.Getenv("TEST_ENV") fmt.Println("sayHello testEnv:", testEnv) } func init() { testEnv := os.Getenv("TEST_ENV") fmt.Println("init testEnv:", testEnv) } func main() { }
编译命令
GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -buildmode=c-shared -o skd.dylib
Rust侧调用代码
use libloading::Library; fn main() { unsafe { std::env::set_var("TEST_ENV", "test123") }; let lib = unsafe { Library::new("skd.dylib").unwrap() }; let say_hello: libloading::Symbol<unsafe extern "C" fn()> = unsafe { lib.get(b"sayHello").unwrap() }; unsafe { say_hello() }; }
实际运行结果
init testEnv: sayHello testEnv:
为啥macOS会这样?
这其实是macOS的dyld(动态链接器)和Windows的动态链接器在逻辑上的核心差异:
- Windows那边,动态库加载时会直接用主进程当前的环境变量表,哪怕是加载前一秒刚设的变量都能读到
- 但macOS的
dyld在进程启动时就会拍一个环境变量的快照,之后不管主进程怎么改环境变量,动态库读取的都是这个冻结的快照。更坑的是,Go编译的c-shared库在加载瞬间就会跑init函数,这时候快照早就固定死了,后面Rust设的变量根本同步不进去,自然读不到。
给你几个解决办法
根据场景选就行:
1. 最稳妥:绕开环境变量,直接传参数(跨平台通用)
既然环境变量在macOS有这种坑,不如直接不用它,改成通过函数参数传值,Windows和macOS都能正常跑,还没兼容性问题。
- 改Go侧的
sayHello,让它接受参数:
//export sayHello func sayHello(env *C.char) { testEnv := C.GoString(env) fmt.Println("sayHello testEnv:", testEnv) }
- Rust侧调用时直接把值传进去:
use libloading::Library; fn main() { let test_env = "test123"; let lib = unsafe { Library::new("skd.dylib").unwrap() }; // 注意调整函数签名,匹配带参数的版本 let say_hello: libloading::Symbol<unsafe extern "C" fn(*const i8)> = unsafe { lib.get(b"sayHello").unwrap() }; unsafe { say_hello(test_env.as_ptr()) }; }
2. 凑合用:先设环境变量再启动进程
哦不对,Rust主进程里set_var之后再加载库还是没用,因为快照是进程启动时就固定的。如果一定要用环境变量,得在Rust进程启动前就把变量设好,或者用std::process::Command起一个子进程,在子进程里先设变量再加载库。但这个方案灵活性很低,适合环境变量固定不变的场景。
3. 不推荐:用私有API改快照(hack性质)
如果你死磕一定要用环境变量的方式,可以调用macOS的底层私有API去更新dyld的环境变量快照,但这个做法依赖系统内部实现,macOS一更新可能就崩了,而且全是unsafe代码,生产环境千万别用。
补充一句
你看到init函数也读不到值,是因为Go的c-shared库在加载的瞬间就会自动执行所有init函数,这时候dyld的快照早就冻结了,所以哪怕你在Rust里先设变量再加载库,init还是拿不到值,这和你看到的结果完全一致。
内容来源于stack exchange
相关产品推荐
相关产品推荐

