Go与C间传递sql.DB指针的实现问题求助
问题解答
直接通过C共享库在两个Go程序间传递*sql.DB指针不可行,核心原因有两点:
- Go的GC会自动管理内存,跨runtime传递的指针无法被目标Go进程的GC识别,原进程的GC可能回收该对象导致野指针。
- 不同Go runtime的内存空间相互隔离,指针仅在所属runtime内有效,跨runtime解引用会直接触发内存访问错误。
你提到的对象引用映射方案是正确的思路:在提供DB连接的Go模块(编译为C共享库的test.go)内部维护一个*sql.DB的全局映射表,用唯一ID代替指针传递给调用方,所有数据库操作都通过ID委托给共享库内的函数执行。
修正后的实现代码
test.go(编译为C共享库)
package main /* #cgo CFLAGS: -I. #include <stdlib.h> */ import "C" import ( "database/sql" "fmt" "sync" _ "github.com/denisenkom/go-mssqldb" "unsafe" ) // ObjectId 用于标识共享库内的DB连接实例 type ObjectId C.int32 var refs struct { sync.Mutex objs map[ObjectId]*sql.DB next ObjectId } func init() { refs.Lock() defer refs.Unlock() refs.objs = make(map[ObjectId]*sql.DB) refs.next = 1000 } //export OpenDB func OpenDB(engine *C.char, connStr *C.char) ObjectId { connStrGo := C.GoString(connStr) engineStrGo := C.GoString(engine) // 解密连接字符串逻辑 // decryptedConnStr := decrypt(connStrGo) db, err := sql.Open(engineStrGo, connStrGo) if err != nil { fmt.Println("Failed to open database:", err) return 0 } // 提前Ping验证连接 if err := db.Ping(); err != nil { fmt.Println("Failed to ping database:", err) db.Close() return 0 } refs.Lock() defer refs.Unlock() id := refs.next refs.next++ refs.objs[id] = db return id } //export PingDB func PingDB(id ObjectId) C.int { refs.Lock() db, exists := refs.objs[id] refs.Unlock() if !exists { return 1 } if err := db.Ping(); err != nil { fmt.Println("Ping failed:", err) return 1 } return 0 } //export CloseDB func CloseDB(id ObjectId) { refs.Lock() db, exists := refs.objs[id] delete(refs.objs, id) refs.Unlock() if exists { db.Close() } } func main() {}
demo.go(调用C共享库的Go程序)
package main /* #cgo LDFLAGS: -L. -ltest #include <stdlib.h> // 声明共享库导出函数 typedef int32_t ObjectId; ObjectId OpenDB(const char* engine, const char* connStr); int PingDB(ObjectId id); void CloseDB(ObjectId id); */ import "C" import ( "fmt" "unsafe" ) func main() { connStr := "server=;user id=;password=;database=" engine := "sqlserver" // 转换Go字符串为C字符串 connStrC := C.CString(connStr) engineC := C.CString(engine) defer C.free(unsafe.Pointer(connStrC)) defer C.free(unsafe.Pointer(engineC)) // 打开DB连接,获取ID dbID := C.OpenDB(engineC, connStrC) if dbID == 0 { fmt.Println("Failed to connect to database") return } defer C.CloseDB(dbID) // 调用共享库内的Ping函数 if res := C.PingDB(dbID); res != 0 { fmt.Println("Failed to ping database") return } fmt.Println("Database connection is alive") }
编译与运行命令
# 编译test.go为C共享库 go build -buildmode=c-shared -o test.dll test.go # 编译demo.go为可执行文件 go build -o demo.exe demo.go # 运行demo.exe ./demo.exe
关键注意事项
- 所有数据库操作(Query、Exec等)都需要在test.go中导出对应的函数,通过ObjectId委托执行,不能直接在demo.go中操作DB对象。
- 必须在demo.go中调用
CloseDB释放连接,避免共享库内的DB连接泄漏。 - 共享库内的全局映射表需要用互斥锁保护,确保并发安全。
内容的提问来源于stack exchange,提问作者richardkiwilee
相关产品推荐
相关产品推荐

