从Python ctypes调用Go string函数会导致segfault

2024-09-30 16:41:10 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个名为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函数交互


Tags: 模块字符串testimportgostringmainlib
1条回答
网友
1楼 · 发布于 2024-09-30 16:41:10

Go的string类型实际上类似于

type string {
    ptr *byte
    size int
}

这就是Test{Hello|Concat}实际上期望的不是一对指针,而是一对struct类型的值。
换言之,cgo执行的魔力刚好足以让网关调用从Go到C再返回,但它不执行值的自动转换

您有两个选择:

  • 如果可能的话,请从ctypes绑定显式地处理此问题。
    编译包时,cgo生成一个头文件,其中包含表示Go字符串的结构的C定义;你可以马上用

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

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

    请注意以下几点注意事项:

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

还有几个随机指针:

    <> Li >我不精通Python的{{CD5}},但我推测使用{{c+24}}是不正确的:在C(和C++,fWIW)^ {CD25}}是一种表示单个< EM >固定大小的“宽字符”,这是通常 aUCS-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 ^{}。真的,请吧

相关问题 更多 >