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')
CodePudding user response:
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.