从 Python ctypes 调用 Go 字符串函数导致段错误
Calling a Go string function from Python ctypes results in segfault
我有一个名为 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
中但无济于事。
我在这里做错了什么?具体来说,如何与接受字符串参数的 Go 函数交互 Python?
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)
}
注意这里的一些注意事项:
- 这两个助手都复制了他们参数的内存,所以上面那个愚蠢的例子可以正常工作,但它会首先复制
a
和 b
指向的内存块,然后吃增加两倍的内存来生成连接的字符串,然后再次复制结果字符串的内存以生成返回的指针。
IOW,如果你试图将一些大块的 Go 代码导出到 C 中,那么这种方法很好,这样这些分配与该块所做的任何事情都相形见绌。
- 使用
*C.char
等同于C语言中的*char
,所以字符串应该以NUL结尾;如果不是,请使用 C.GoStringN
.
- 每个由
C.CString
分配的内存块都必须通过调用 C.free
来释放。这里有一个转折:C.free
基本上是一个从 libc
中的链接调用 free()
的薄垫片,所以如果你能保证完整的产品(代码完全加载到内存中并且(inter )使用动态链接器链接)只有一个链接的 libc
副本,您可以从调用 C.Cstring
中产生的内存块上的非 Go 代码调用 free()
去代码。
另外几个随机指针:
- 我不精通 Python 的
ctypes
但我推测使用 ctypes.c_wchar_p
是不正确的:在 C(和 C++,FWIW)中 wchar_t
是一种表示单个 fixed-sized “宽字符”的类型,通常是 一个 UCS-2/UTF-16 code point, and Go's strings are not composed of these—they may contain arbitrary bytes, and when they are used to contain Unicode text, they are encoded using UTF-8 这是一个多字节编码(单个 Unicode 代码点可能由字符串中的 1 到 4 个字节表示)。
在任何一种情况下,wchar_t
都不能用于 UTF-8(实际上是 many seasoned devs beleive it's an abomination)。
- 在开始这个项目之前,请阅读 the docs on
cmd/cgo
。真的,请做!
我有一个名为 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
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
中但无济于事。
我在这里做错了什么?具体来说,如何与接受字符串参数的 Go 函数交互 Python?
Go 的 string
类型实际上类似于
type string {
ptr *byte
size int
}
所以这就是 Test{Hello|Concat}
实际期望的——不是一对指针,而是一对 struct
类型的值。
换句话说,cgo
对从 Go 到 C 和返回的网关调用执行了足够的魔法,但它不执行值的自动转换。
你有两个选择:
如果可能,请从您的
ctypes
绑定中明确使用它。
编译你的包时,cgo
生成一个头文件,其中包含表示 Go 字符串的结构的 C 定义;你可以马上使用它。使导出到 C 的函数与 C 的“类型系统”兼容。
为此,cgo
提供 helper functionsC.CString
andC.GoString
.
基本上,您可以这样定义 API:func TestHello(a, b *C.char) *C.char { testArg1, testArg2 := C.GoString(a), C.GoString(b) return C.CString(testArg + TestArg2) }
注意这里的一些注意事项:
- 这两个助手都复制了他们参数的内存,所以上面那个愚蠢的例子可以正常工作,但它会首先复制
a
和b
指向的内存块,然后吃增加两倍的内存来生成连接的字符串,然后再次复制结果字符串的内存以生成返回的指针。
IOW,如果你试图将一些大块的 Go 代码导出到 C 中,那么这种方法很好,这样这些分配与该块所做的任何事情都相形见绌。 - 使用
*C.char
等同于C语言中的*char
,所以字符串应该以NUL结尾;如果不是,请使用C.GoStringN
. - 每个由
C.CString
分配的内存块都必须通过调用C.free
来释放。这里有一个转折:C.free
基本上是一个从libc
中的链接调用free()
的薄垫片,所以如果你能保证完整的产品(代码完全加载到内存中并且(inter )使用动态链接器链接)只有一个链接的libc
副本,您可以从调用C.Cstring
中产生的内存块上的非 Go 代码调用free()
去代码。
- 这两个助手都复制了他们参数的内存,所以上面那个愚蠢的例子可以正常工作,但它会首先复制
另外几个随机指针:
- 我不精通 Python 的
ctypes
但我推测使用ctypes.c_wchar_p
是不正确的:在 C(和 C++,FWIW)中wchar_t
是一种表示单个 fixed-sized “宽字符”的类型,通常是 一个 UCS-2/UTF-16 code point, and Go's strings are not composed of these—they may contain arbitrary bytes, and when they are used to contain Unicode text, they are encoded using UTF-8 这是一个多字节编码(单个 Unicode 代码点可能由字符串中的 1 到 4 个字节表示)。
在任何一种情况下,wchar_t
都不能用于 UTF-8(实际上是 many seasoned devs beleive it's an abomination)。 - 在开始这个项目之前,请阅读 the docs on
cmd/cgo
。真的,请做!