You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go与C间传递sql.DB指针的实现问题求助

问题解答

直接通过C共享库在两个Go程序间传递*sql.DB指针不可行,核心原因有两点:

  1. Go的GC会自动管理内存,跨runtime传递的指针无法被目标Go进程的GC识别,原进程的GC可能回收该对象导致野指针。
  2. 不同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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 15:30:22