Golang中关于plugin.Symbol不同作用域类型的问题

Golang中关于plugin.Symbol不同作用域类型的问题 我正在尝试使用插件包在运行时动态加载函数定义。该函数操作一个结构体。

运行程序时遇到了一个奇怪的错误。

panic: interface conversion: plugin.Symbol is func(main._Ctype_struct_Vertex), not func(main._Ctype_struct_Vertex) (types from different scopes)

以下是我的程序和重现错误的步骤。 在 vertex.go 中:

package main

/*
struct Vertex {
    int x;
    int y;
};
*/
import "C"
import (
	"fmt"
)

//export Print
func Print(v C.struct_Vertex) {
	fmt.Printf("v.x = %d, v.y = %d\n", v.x, v.y)
}

func main() {}

我通过以下命令编译它: go build -buildmode=plugin -o libvertex.so vertex.go

在我的 main.go 中:

package main

/*
struct Vertex {
    int x;
    int y;
};
*/
import "C"
import (
	"fmt"
	"os"
	"plugin"
)

var fp printFunc

type printFunc func(C.struct_Vertex)

func main() {

	plug, err := plugin.Open(string("./libvertex.so"))

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	print, err := plug.Lookup("Print")

	fp = print.(func(C.struct_Vertex))
	fp(C.struct_Vertex{x: 1, y: 2})
}

然后,当我运行 go run main.go 时,得到了这个错误: panic: interface conversion: plugin.Symbol is func(main._Ctype_struct_Vertex), not func(main._Ctype_struct_Vertex) (types from different scopes)

知道为什么会出现这个错误吗?我猜这可能与两个文件中的重复定义有关,我尝试使用一个 C 头文件,但仍然不起作用。

提前感谢!


更多关于Golang中关于plugin.Symbol不同作用域类型的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中关于plugin.Symbol不同作用域类型的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个典型的Go插件类型作用域问题。问题在于两个包中的C.struct_Vertex类型虽然定义相同,但在Go的类型系统中被视为不同的类型,因为它们来自不同的编译单元。

问题分析

当编译插件时,vertex.go中的C.struct_Vertex类型被编译到插件中。当主程序加载插件时,它有自己的C.struct_Vertex类型定义。尽管它们的C定义相同,但Go将它们视为不同的类型,因为它们来自不同的包作用域。

解决方案

方案1:使用接口包装(推荐)

创建共享的接口定义文件:

shared/vertex.go:

package shared

/*
struct Vertex {
    int x;
    int y;
};
*/
import "C"

type Vertex = C.struct_Vertex

type Printer interface {
    Print(Vertex)
}

vertex.go:

package main

import (
    "fmt"
    "yourmodule/shared"
)

//export Print
func Print(v shared.Vertex) {
    fmt.Printf("v.x = %d, v.y = %d\n", v.x, v.y)
}

func main() {}

main.go:

package main

import (
    "fmt"
    "os"
    "plugin"
    "yourmodule/shared"
)

func main() {
    plug, err := plugin.Open("./libvertex.so")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    printSym, err := plug.Lookup("Print")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    // 通过类型断言到具体的函数签名
    printFunc := printSym.(func(shared.Vertex))
    printFunc(shared.Vertex{X: 1, Y: 2})
}

方案2:使用反射调用

如果不想创建共享包,可以使用反射:

main.go:

package main

/*
struct Vertex {
    int x;
    int y;
};
*/
import "C"
import (
    "fmt"
    "os"
    "plugin"
    "reflect"
)

func main() {
    plug, err := plugin.Open("./libvertex.so")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    printSym, err := plug.Lookup("Print")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    // 使用反射调用
    v := C.struct_Vertex{x: 1, y: 2}
    reflect.ValueOf(printSym).Call([]reflect.Value{
        reflect.ValueOf(v),
    })
}

方案3:使用函数指针和unsafe转换

main.go:

package main

/*
struct Vertex {
    int x;
    int y;
};
*/
import "C"
import (
    "fmt"
    "os"
    "plugin"
    "unsafe"
)

func main() {
    plug, err := plugin.Open("./libvertex.so")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    printSym, err := plug.Lookup("Print")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    // 获取函数指针
    funcPtr := *(*uintptr)(printSym.(*interface{}))
    
    // 定义本地函数类型
    type printFunc func(C.struct_Vertex)
    
    // 转换为函数
    fp := *(*printFunc)(unsafe.Pointer(&funcPtr))
    
    fp(C.struct_Vertex{x: 1, y: 2})
}

编译命令

对于方案1:

# 编译插件
go build -buildmode=plugin -o libvertex.so vertex.go

# 运行主程序
go run main.go

关键点

  1. 类型作用域:Go插件系统中的类型必须来自相同的包作用域才能进行类型断言
  2. 共享定义:最佳实践是将共享的类型定义放在独立的包中
  3. CGO限制:使用CGO类型时这个问题尤其明显,因为C类型在Go中会有特殊的包装

推荐使用方案1,因为它是最安全、最符合Go惯用法的解决方案。方案2和方案3虽然能工作,但牺牲了类型安全性。

回到顶部