go 1.15+ 不支持生成可被非c程序直接dlopen的通用动态库,仅支持buildmode=c-shared生成带c abi的封装库,依赖go运行时且必须由c程序调用。

Go 1.15+ 不支持直接编译传统 .so 动态库
Go 官方从 Go 1.15 开始移除了 buildmode=c-shared 对非 C 语言主程序的动态链接支持,且 Go 本身不生成 ELF/Dylib 风格的、可被其他语言(如 Python、C)直接 dlopen 加载的通用动态库。所谓“Go 动态库”,实际仅指 buildmode=c-shared 生成的带 C ABI 的封装层 —— 它依赖 Go 运行时,必须由 C 程序调用,不能脱离 Go 生态独立加载。
用 buildmode=c-shared 生成 C 兼容接口
这是唯一受官方支持的“动态库”路径,产出 .so(Linux)或 .dylib(macOS),但本质是 C 接口桥接 + 内嵌 Go 运行时。需严格满足:
- 主包必须是
package main,且只含export函数(用//export FuncName注释标记) - 必须导入
"C"包(即使没显式使用) - 导出函数参数/返回值只能是 C 兼容类型:
C.int、*C.char、C.size_t等,不能是 Go 的string、slice、struct - 需手动管理内存:Go 分配的内存(如
C.CString)必须在 C 侧调用C.free释放
示例:
// hello.go
package main
import "C"
import "unsafe"
//export Add
func Add(a, b int) int {
return a + b
}
//export Hello
func Hello(s *C.char) *C.char {
goStr := C.GoString(s)
ret := C.CString("Hello, " + goStr)
return ret
}
func main() {} // required, but not executed
编译命令:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
go build -buildmode=c-shared -o libhello.so hello.go
调用时必须链接 libgo 和 libc,且不能静态链接 Go 运行时
生成的 .so 不是纯代码,它依赖 Go 运行时符号(如 runtime.mallocgc)。C 程序调用时:
- 必须用
gcc(不是clang默认配置)链接,且顺序为:your_app.c libhello.so -lgo -lpthread - 不能加
-static,否则链接失败 —— Go 运行时不支持全静态链接到外部程序 - 运行时需确保
LD_LIBRARY_PATH包含libhello.so路径,否则dlopen找不到依赖 - 首次调用任意导出函数会触发 Go 运行时初始化,后续调用才真正执行逻辑
替代方案:CGO + 纯 C 库更可控
如果目标是让 Python/Java/Rust 调用 Go 逻辑,c-shared 很容易因运行时冲突、GC 干预、线程模型不匹配出问题。更稳的做法是:
- 用 Go 实现核心逻辑,通过
c-shared暴露极简 C 接口(只做数据转换,不启动 goroutine、不调用net/http等) - 把 Go 编译成静态链接的
.a库,再用纯 C 封装一层,对外提供标准 POSIX 动态库接口 - 或改用
gopy(go get github.com/go-python/gopy)生成 Python 绑定,绕过 C ABI 层
真正麻烦的从来不是编译命令,而是 Go 运行时与宿主环境的生命周期耦合 —— 比如 Python 的 GIL、Java 的 JNI Attach/Detach、C 程序的 fork 处理,都可能让 Go 的调度器卡死或泄露 goroutine。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










