【问题标题】:Calling a Go string function from Python ctypes results in segfault从 Python ctypes 调用 Go 字符串函数会导致段错误
【发布时间】:2021-02-26 08:03:57
【问题描述】:

我有一个名为 test.go 的模块,其中包含两个接受字符串类型的简单 Go 函数:

package main

import (
  "fmt"
  "C"
)

//export TestConcat
func TestConcat(testArg string, testArg2 string) (string) {
  retval := testArg + testArg2
  return retval
}

//export TestHello
func TestHello(testArg string) {
  fmt.Println("%v\n", testArg)
}


func main(){}

我将它编译为go build -o test.so -buildmode=c-shared test.go的共享库

然后我有一个名为test.py的Python模块

import ctypes

from ctypes import cdll


test_strings = [
    "teststring1",
    "teststring2"
]

if __name__ == '__main__':
    lib = cdll.LoadLibrary("./test.so")
    lib.TestConcat.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p]
    lib.TestHello.argtypes = [ctypes.c_wchar_p]
    for test_string in test_strings:
        print(
            lib.TestConcat("hello", test_string)
        )
        lib.TestHello(test_string)

然后我运行 test.py 并得到一个讨厌的段错误

runtime: out of memory: cannot allocate 279362762964992-byte block (66781184 in use)
fatal error: out of memory

我尝试将参数包装在 ctypes.c_wchar_p 中,但无济于事。

我在这里做错了什么?具体来说,如何与 Python 中接受字符串参数的 Go 函数进行交互?

【问题讨论】:

    标签: python go ctypes ffi


    【解决方案1】:

    Go 的 string 类型实际上类似于

    type string {
        ptr *byte
        size int
    }
    

    这就是Test{Hello|Concat} 实际期望的——不是一对指针,而是一对struct 类型的值。
    换句话说,cgo 对从 Go 到 C 和返回的网关调用执行了足够的魔法,但它不执行值的自动转换。

    你有两个选择:

    • 如果可能,请从您的 ctypes 绑定中明确使用它。
      在编译你的包时,cgo 会生成一个头文件,其中包含表示 Go 字符串的结构的 C 定义;您可以立即使用它。

    • 使导出到 C 的函数与 C 的“类型系统”兼容。
      为此,cgo 提供helper functions C.CString and C.GoString
      基本上,您可以像这样定义您的 API:

      func TestHello(a, b *C.char) *C.char {
          testArg1, testArg2 := C.GoString(a), C.GoString(b)
          return C.CString(testArg + TestArg2)
      }
      

      请注意这里的一些警告:

      • 这两个助手都复制了他们参数的内存,所以上面这个愚蠢的例子可以正常工作,但它会首先复制ab指向的内存块,然后消耗两倍的内存来生成连接的字符串,然后再次复制结果字符串的内存以生成返回的指针。
        IOW,如果您尝试将一些大的 Go 代码块导出到 C 中,那么这种方法很好,这样无论该块做什么,这些分配都相形见绌。
      • 使用*C.char与C中的*char相同,所以字符串应该是NUL终止的;如果不是,请使用C.GoStringN
      • C.CString 分配的每个内存块都必须通过调用C.free 来释放。这里有一个转折点:C.free 基本上是从libc 中的链接调用free() 的薄垫片,所以如果你能保证完整的产品(代码完全加载到内存中并使用动态链接器(相互)链接) 只有一个链接的libc 副本,您可以从非Go 代码中调用free(),该内存块由Go 代码中的C.Cstring 调用产生。

    更多随机指针:

    • 我并不精通 Python 的 ctypes,但我推测使用 ctypes.c_wchar_p 是不正确的:在 C(和 C++,FWIW)中,wchar_t 是一种表示单个 fixed 的类型-sized “宽字符”,通常是一个UCS-2/UTF-16 代码点,Go 的字符串不是由这些组成的——它们可能包含任意字节,当它们用于包含Unicode 文本,它们使用UTF-8 编码,这是一种多字节编码(单个 Unicode 代码点可以用字符串中的 1 到 4 个字节表示)。
      无论哪种情况,wchar_t 都不能用于 UTF-8(实际上是 many seasoned devs beleive it's an abomination)。
    • 在开始这个项目之前,请阅读the docs on cmd/cgo完全。真的,请做!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-21
      • 1970-01-01
      • 2017-08-02
      • 2013-07-23
      • 2017-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多