使用ctypes调用带有参数(C字符串)的Go DLL。

huangapple go评论78阅读模式
英文:

Ctypes calling Go Dll with arguments (C string)

问题

如何将Python中的string作为参数传递给Go Dll(使用ctypes):

Go代码:

package main

import "C"
import "fmt"

//export GetInt
func GetInt() int32 {
    return 42
}

//export GetString
func GetString() {
    fmt.Println("Foo")
}

//export PrintHello
func PrintHello(name string) {
    // if name == "hello" { ... }
    fmt.Printf("From DLL: Hello, %s!", name)
}

func main() {
    // Need a main function to make CGO compile package as C shared library
}

在MacOS上编译:GOOS=windows GOARCH=386 CGO_ENABLED=1 CC=i686-w64-mingw32-gcc go build -buildmode=c-shared -o ./dist/perf_nlp.dll

Python代码:

import ctypes

def getString():
    nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
    dllFunc = nlp.GetString
    dllFunc.restype = ctypes.c_char_p
    return dllFunc()

def getInt():
    nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
    dllFunc = nlp.GetInt
    dllFunc.restype = int
    return dllFunc()

def readString():
    nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
    dllFunc = nlp.ReadString
    dllFunc.argtypes = [ctypes.c_char_p]
    dllFunc.restype = ctypes.c_char_p
    return dllFunc(b'Foo')

print(getInt())
print(getString())
print(readString())  # 失败

输出:

42
Foo
None
unexpected fault address 0x871000
fatal error: fault
[signal 0xc0000005 code=0x0 addr=0x871000 pc=0x623e501f]

goroutine 17 [running, locked to thread]:
runtime.throw(0x6245b592, 0x5)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/panic.go:1116 +0x64 fp=0x1242fda4 sp=0x1242fd90 pc=0x623be404
runtime.sigpanic()
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/signal_windows.go:249 +0x1ed fp=0x1242fdb8 sp=0x1242fda4 pc=0x623ceb8d
runtime.memmove(0x12500011, 0x800588, 0x32efe4)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/memmove_386.s:89 +0x7f fp=0x1242fdbc sp=0x1242fdb8 pc=0x623e501f
fmt.(*buffer).writeString(...)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:82
fmt.(*fmt).padString(0x124a60b0, 0x800588, 0x32efe4)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:110 +0x6c fp=0x1242fdfc sp=0x1242fdbc pc=0x6241576c
fmt.(*fmt).fmtS(0x124a60b0, 0x800588, 0x32efe4)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:359 +0x4d fp=0x1242fe14 sp=0x1242fdfc pc=0x6241664d
fmt.(*pp).fmtString(0x124a6090, 0x800588, 0x32efe4, 0x73)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:450 +0x188 fp=0x1242fe38 sp=0x1242fe14 pc=0x62418f58
fmt.(*pp).printArg(0x124a6090, 0x62447c80, 0x1248c110, 0x73)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:698 +0x776 fp=0x1242fe80 sp=0x1242fe38 pc=0x6241ad56
fmt.(*pp).doPrintf(0x124a6090, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:1030 +0x12b fp=0x1242fef0 sp=0x1242fe80 pc=0x6241d81b
fmt.Fprintf(0x62476550, 0x1248c0d8, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1, 0x623e2fe7, 0x0, 0x12488030)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:204 +0x52 fp=0x1242ff20 sp=0x1242fef0 pc=0x62417bd2
fmt.Printf(...)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:213
main.ReadString(...)
        /Users/foobar/__projects__/heine/db_dll/perf_nlp.go:19
main._cgoexpwrap_c3579cea1e16_ReadString(0x800588, 0x32efe4)
        _cgo_gotypes.go:71 +0x8d fp=0x1242ff54 sp=0x1242ff20 pc=0x6241e9bd
runtime.call16(0x0, 0x32ef1c, 0x32ef68, 0x8, 0x0)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:565 +0x30 fp=0x1242ff68 sp=0x1242ff54 pc=0x623e3020
runtime.cgocallbackg1(0x0)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:332 +0x149 fp=0x1242ffb0 sp=0x1242ff68 pc=0x62393f59
runtime.cgocallbackg(0x0)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:207 +0xb5 fp=0x1242ffe0 sp=0x1242ffb0 pc=0x62393d85
runtime.cgocallback_gofunc(0x0, 0x0, 0x0, 0x0)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:806 +0x7e fp=0x1242fff0 sp=0x1242ffe0 pc=0x623e419e
runtime.goexit()
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:1337 +0x1 fp=0x1242fff4 sp=0x1242fff0 pc=0x623e4651

**解决方案:**

```go
//export ReadString
func ReadString(name *C.char) *C.char {
    res := "";
    goName := C.GoString(name);

    if goName == "Foo" {
        res = "From DLL: Hello, Foo"
    } else {
        res = "From DLL: Hello!"
    }
    return C.CString(res)
}

Python:

def readString():
    nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
    dllFunc = nlp.ReadString
    dllFunc.argtypes = [ctypes.c_char_p]
    dllFunc.restype = ctypes.c_char_p
    return dllFunc(b'cFoo')
英文:

How to pass a string as argument from Python to a Go Dll using ctypes:

Go-code:

package main

import "C"
import "fmt"

//export GetInt
func GetInt() int32 {
	return 42
}

//export GetString
func GetString() {
	fmt.Println("Foo")
}  

//export PrintHello
func PrintHello(name string) {
    // if name == "hello" { ... }
    fmt.Printf("From DLL: Hello, %s!", name)
}


func main() {
	// Need a main function to make CGO compile package as C shared library
}

Compiled on MacOs using: GOOS=windows GOARCH=386 CGO_ENABLED=1 CC=i686-w64-mingw32-gcc go build -buildmode=c-shared -o ./dist/perf_nlp.dll

Python code:

import ctypes


def getString():
    nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
    dllFunc = nlp.GetString
    dllFunc.restype = ctypes.c_char_p
    return dllFunc()

def getInt():
    nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
    dllFunc = nlp.GetInt
    dllFunc.restype = int
    return dllFunc()

def readString():
    nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
    dllFunc = nlp.ReadString
    dllFunc.argtypes = [ctypes.c_char_p]
    dllFunc.restype = ctypes.c_char_p
    return dllFunc(b'Foo')


print(getInt())
print(getString())
print(readString()). # Fails

Out:

42
Foo
None
unexpected fault address 0x871000
fatal error: fault
[signal 0xc0000005 code=0x0 addr=0x871000 pc=0x623e501f]

goroutine 17 [running, locked to thread]:
runtime.throw(0x6245b592, 0x5)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/panic.go:1116 +0x64 fp=0x1242fda4 sp=0x1242fd90 pc=0x623be404
runtime.sigpanic()
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/signal_windows.go:249 +0x1ed fp=0x1242fdb8 sp=0x1242fda4 pc=0x623ceb8d
runtime.memmove(0x12500011, 0x800588, 0x32efe4)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/memmove_386.s:89 +0x7f fp=0x1242fdbc sp=0x1242fdb8 pc=0x623e501f
fmt.(*buffer).writeString(...)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:82
fmt.(*fmt).padString(0x124a60b0, 0x800588, 0x32efe4)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:110 +0x6c fp=0x1242fdfc sp=0x1242fdbc pc=0x6241576c
fmt.(*fmt).fmtS(0x124a60b0, 0x800588, 0x32efe4)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:359 +0x4d fp=0x1242fe14 sp=0x1242fdfc pc=0x6241664d
fmt.(*pp).fmtString(0x124a6090, 0x800588, 0x32efe4, 0x73)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:450 +0x188 fp=0x1242fe38 sp=0x1242fe14 pc=0x62418f58
fmt.(*pp).printArg(0x124a6090, 0x62447c80, 0x1248c110, 0x73)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:698 +0x776 fp=0x1242fe80 sp=0x1242fe38 pc=0x6241ad56
fmt.(*pp).doPrintf(0x124a6090, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:1030 +0x12b fp=0x1242fef0 sp=0x1242fe80 pc=0x6241d81b
fmt.Fprintf(0x62476550, 0x1248c0d8, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1, 0x623e2fe7, 0x0, 0x12488030)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:204 +0x52 fp=0x1242ff20 sp=0x1242fef0 pc=0x62417bd2
fmt.Printf(...)
        /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:213
main.ReadString(...)
        /Users/foobar/__projects__/heine/db_dll/perf_nlp.go:19
main._cgoexpwrap_c3579cea1e16_ReadString(0x800588, 0x32efe4)
        _cgo_gotypes.go:71 +0x8d fp=0x1242ff54 sp=0x1242ff20 pc=0x6241e9bd
runtime.call16(0x0, 0x32ef1c, 0x32ef68, 0x8, 0x0)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:565 +0x30 fp=0x1242ff68 sp=0x1242ff54 pc=0x623e3020
runtime.cgocallbackg1(0x0)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:332 +0x149 fp=0x1242ffb0 sp=0x1242ff68 pc=0x62393f59
runtime.cgocallbackg(0x0)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:207 +0xb5 fp=0x1242ffe0 sp=0x1242ffb0 pc=0x62393d85
runtime.cgocallback_gofunc(0x0, 0x0, 0x0, 0x0)
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:806 +0x7e fp=0x1242fff0 sp=0x1242ffe0 pc=0x623e419e
runtime.goexit()
        /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:1337 +0x1 fp=0x1242fff4 sp=0x1242fff0 pc=0x623e4651

Working solution:

//export ReadString
func ReadString(name *C.char) *C.char {
	res := "";
	goName := C.GoString(name);

	if goName == "Foo" {
		res = "From DLL: Hello, Foo"
	}else{
		res = "From DLL: Hello!"
	}
	return C.CString(res)
}

Python:

def readString():
    nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
    dllFunc = nlp.ReadString
    dllFunc.argtypes = [ctypes.c_char_p]
    dllFunc.restype = ctypes.c_char_p
    return dllFunc(b'cFoo')

答案1

得分: 2

一个Go字符串和一个C字符串完全没有关系(除了它们都被称为字符串,但至少其中一个是错误的)。

在这里,Python发送了一个C字符串,因为你告诉它这样做,但是Go期望一个Go字符串,它们的布局完全不同,所以会出错。如果它没有在调用点出错,当垃圾回收器尝试处理该字符串时,它可能会出错,因为它不是一个Go字符串。

你需要看一下神奇的"C"伪包:你需要接收一个*C.char,并使用C.GoString将其复制到一个Go string中,然后才能将其传递给任何期望Go字符串的地方。或者类似这样的操作,我对cgo的经验(特别是调用它)有限,只是避免将其作为一个坏主意。

无论如何,你可能至少要完整阅读cgo文档,FFI在大多数情况下都很棘手,而在两个托管语言之间的FFI更加如此。

英文:

A Go string and a C string are entirely unrelated (except in that both are called strings, which is a lie for at least one of them).

Here Python is sending a C string because you've told it to, but Go expects a Go string, which has a completely diffrent layout so it blows up. And if it didn't blow up at the callsite it'd probably blow up when the GC tries to handle the string, which it can't, because it's not a Go string.

You want to look at the magical "C" pseudo-package: you need to take in a *C.char and copy that to a Go string using C.GoString before you can pass it to anything expecting a go String. Or something along those lines, my experience with cgo (especially calling into it) is limited to avoiding this as a bad idea.

Regardless you probably want to at the very least read the cgo documentation in full, FFI is tricky at the best of time, and FFI between two managed languages much more so.

huangapple
  • 本文由 发表于 2021年12月14日 20:58:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/70349271.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定