Golang中如何通过指针调用动态加载的共享库函数?
Golang中如何通过指针调用动态加载的共享库函数? 我尝试从Go中调用一些共享对象函数。我不想为所有函数编写C注释代码来构建CGO接口。
我这样编写我的共享对象:
#include <stdio.h>
void greet(char* name) {
printf("Hello, %s!\n", name);
}
我使用以下命令编译它:gcc -shared -fPIC -o libgreet.so greet.c。
我的Go代码用于调用greet函数:
package main
// #cgo LDFLAGS: -ldl
// #include <dlfcn.h>
// #include <stdlib.h>
import "C"
import (
"unsafe"
"fmt"
)
func main() {
so_name := C.CString("./libgreet.so")
defer C.free(unsafe.Pointer(so_name))
function_name := C.CString("greet")
defer C.free(unsafe.Pointer(function_name))
library := C.dlopen(so_name, C.RTLD_LAZY)
defer C.dlclose(library)
function := C.dlsym(library, function_name)
greet := (*func(*C.char))(unsafe.Pointer(&function))
fmt.Println("%p %p %p %p\n", greet, function, unsafe.Pointer(function), unsafe.Pointer(&function))
(*greet)(C.CString("Bob"))
}
当我启动可执行文件时,我遇到了SIGSEGV错误。
我尝试用gdb调试它,打印出来的指针看起来是好的(与gdb中的x greet值相同)。段错误发生在指令0x47dde4 <main.main+548> call r8处,其中$r8包含0x10ec8348e5894855(可能不是一个内存地址)。
你知道我该如何修复这个错误吗?有没有一种解决方案可以在Go中调用共享对象函数,而无需像CGO语法那样编写C注释代码(我没有找到任何文档来做到这一点)?
更多关于Golang中如何通过指针调用动态加载的共享库函数?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
如果你成功让示例运行起来,如果可以的话,我想看看代码。
更多关于Golang中如何通过指针调用动态加载的共享库函数?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
谢谢!第一种方案不可行,我必须动态加载库来启动可执行文件,因为该库存在多个路径,并且它不应作为运行可执行文件的先决条件。
我会测试第二个选项。
但我猜这个解决方案并不能解决你的原始问题,即避免为你想要使用的库编写带注释的包装代码。
或者,你使用 dlopen 接口是因为你想要以插件风格使用共享库(在运行时加载和卸载)吗?
感谢您的回复!
您的答案运行良好,但并非我所需……我正在使用一个包含大量函数的共享对象,而 CGO 语法不易阅读和维护,因为它是一个没有语法高亮的注释,并且是从共享对象签名中 复制粘贴 过来的,所以每次更新共享对象时我都必须修改它。
是的,当然。我在此仓库中编写了一个概念验证:https://github.com/mauricelambert/TerminalMessages/blob/main/GoDemonstrationLinux.go
这个库目前还很小,但我希望启动一个更大的项目,在我的库中实现许多功能。
感谢您的回复!
我需要动态加载我的库,因为它就像一个可选的插件。我的项目将提供许多功能,但每个功能都将在一个DLL/SO中实现,用户可以选择他们想要的功能。因此,我的库需要动态加载,以避免成为主可执行文件运行的必要条件。
我不知道什么是 golang 插件,我会去了解更多相关信息。
我不想为我的每个功能都启动另一个进程,所以最后一个解决方案不能在我的项目中使用。
我想到了另外两个可能可行的方案?
- 直接使用
LDFLAGS: -lgreet,完全不使用动态加载(而是使用动态链接)。或者这太明显了/我是不是漏掉了什么? modernc项目可以将 C 代码转译为 Go 代码。我从未直接尝试过,只试过同一位作者转译的 sqlite 代码。https://gitlab.com/cznic/ccgo
#1: 不同的库路径(在目标系统上)实际上不是什么大问题,这其实相当常见——可以参考 ld.conf、rpath 等。
#2: 使用 dlopen() 及其相关函数时,即使是在 C 语言中,也必须格外小心。你必须确保你的函数指针确实与原型匹配。这看起来可能很简单,但很容易遇到很多陷阱,例如特定架构的调用约定——你的代码需要明确地处理这一点!
#3: 如果确实需要插件形式,你可以尝试将绑定代码放入一个 Go 插件中,该插件直接链接到那个外部库。
#4: 根据你的实际使用场景,将使用外部库的代码放入一个独立的程序/服务中可能是有意义的。
或许 go doc cmd/cgo 中的这段内容会有帮助?
目前不支持调用C函数指针,但是你可以声明持有C函数指针的Go变量,并在Go和C之间来回传递它们。C代码可以调用从Go接收到的函数指针。例如:
package main
// typedef int (*intFunc) ();
//
// int
// bridge_int_func(intFunc f)
// {
// return f();
// }
//
// int fortytwo()
// {
// return 42;
// }
import "C"
import "fmt"
func main() {
f := C.intFunc(C.fortytwo)
fmt.Println(int(C.bridge_int_func(f)))
// Output: 42
}
我正在尝试自己实现一个解决方案,但看起来我需要更新(或补充)我对C语言中函数指针的知识。
在Go中通过指针调用动态加载的共享库函数时,主要问题在于函数指针的类型转换。你的代码中(*func(*C.char))(unsafe.Pointer(&function))这种转换方式是不正确的,因为function本身已经是unsafe.Pointer类型,而&function获取的是指针变量的地址。
以下是修复后的代码示例:
package main
// #cgo LDFLAGS: -ldl
// #include <dlfcn.h>
// #include <stdlib.h>
import "C"
import (
"unsafe"
"fmt"
)
func main() {
// 加载共享库
soName := C.CString("./libgreet.so")
defer C.free(unsafe.Pointer(soName))
lib := C.dlopen(soName, C.RTLD_LAZY)
if lib == nil {
fmt.Printf("dlopen failed: %s\n", C.GoString(C.dlerror()))
return
}
defer C.dlclose(lib)
// 获取函数符号
funcName := C.CString("greet")
defer C.free(unsafe.Pointer(funcName))
fn := C.dlsym(lib, funcName)
if fn == nil {
fmt.Printf("dlsym failed: %s\n", C.GoString(C.dlerror()))
return
}
// 正确的函数指针转换方式
greet := *(*func(*C.char))(unsafe.Pointer(&fn))
// 调用函数
name := C.CString("Bob")
defer C.free(unsafe.Pointer(name))
greet(name)
}
对于更复杂的函数签名,可以使用类型定义来简化:
package main
// #cgo LDFLAGS: -ldl
// #include <dlfcn.h>
// #include <stdlib.h>
import "C"
import (
"unsafe"
"fmt"
)
// 定义函数类型
type GreetFunc func(*C.char)
func main() {
soName := C.CString("./libgreet.so")
defer C.free(unsafe.Pointer(soName))
lib := C.dlopen(soName, C.RTLD_LAZY)
if lib == nil {
fmt.Printf("dlopen failed: %s\n", C.GoString(C.dlerror()))
return
}
defer C.dlclose(lib)
funcName := C.CString("greet")
defer C.free(unsafe.Pointer(funcName))
fn := C.dlsym(lib, funcName)
if fn == nil {
fmt.Printf("dlsym failed: %s\n", C.GoString(C.dlerror()))
return
}
// 使用类型定义进行转换
var greet GreetFunc
*(*uintptr)(unsafe.Pointer(&greet)) = uintptr(fn)
name := C.CString("Bob")
defer C.free(unsafe.Pointer(name))
greet(name)
}
对于有返回值的函数,可以这样处理:
// C函数:int add(int a, int b);
type AddFunc func(C.int, C.int) C.int
func main() {
// ... 加载库和获取符号的代码
fn := C.dlsym(lib, C.CString("add"))
var add AddFunc
*(*uintptr)(unsafe.Pointer(&add)) = uintptr(fn)
result := add(10, 20)
fmt.Printf("Result: %d\n", result)
}
关键点:
C.dlsym返回的是unsafe.Pointer,直接使用uintptr(fn)获取函数地址- 通过
*(*uintptr)(unsafe.Pointer(&greet)) = uintptr(fn)将函数地址赋值给Go函数变量 - 确保函数签名与C函数完全匹配
- 使用
defer C.dlclose(lib)确保资源释放
这种方法避免了使用cgo的注释语法,直接通过动态加载的方式调用共享库函数。

