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

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

Ctypes calling Go Dll with arguments (C string)

问题

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

Go代码:

  1. package main
  2. import "C"
  3. import "fmt"
  4. //export GetInt
  5. func GetInt() int32 {
  6. return 42
  7. }
  8. //export GetString
  9. func GetString() {
  10. fmt.Println("Foo")
  11. }
  12. //export PrintHello
  13. func PrintHello(name string) {
  14. // if name == "hello" { ... }
  15. fmt.Printf("From DLL: Hello, %s!", name)
  16. }
  17. func main() {
  18. // Need a main function to make CGO compile package as C shared library
  19. }

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

Python代码:

  1. import ctypes
  2. def getString():
  3. nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
  4. dllFunc = nlp.GetString
  5. dllFunc.restype = ctypes.c_char_p
  6. return dllFunc()
  7. def getInt():
  8. nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
  9. dllFunc = nlp.GetInt
  10. dllFunc.restype = int
  11. return dllFunc()
  12. def readString():
  13. nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
  14. dllFunc = nlp.ReadString
  15. dllFunc.argtypes = [ctypes.c_char_p]
  16. dllFunc.restype = ctypes.c_char_p
  17. return dllFunc(b'Foo')
  18. print(getInt())
  19. print(getString())
  20. print(readString()) # 失败

输出:

  1. 42
  2. Foo
  3. None
  4. unexpected fault address 0x871000
  5. fatal error: fault
  6. [signal 0xc0000005 code=0x0 addr=0x871000 pc=0x623e501f]
  7. goroutine 17 [running, locked to thread]:
  8. runtime.throw(0x6245b592, 0x5)
  9. /Users/foobar/.goenv/versions/1.14.15/src/runtime/panic.go:1116 +0x64 fp=0x1242fda4 sp=0x1242fd90 pc=0x623be404
  10. runtime.sigpanic()
  11. /Users/foobar/.goenv/versions/1.14.15/src/runtime/signal_windows.go:249 +0x1ed fp=0x1242fdb8 sp=0x1242fda4 pc=0x623ceb8d
  12. runtime.memmove(0x12500011, 0x800588, 0x32efe4)
  13. /Users/foobar/.goenv/versions/1.14.15/src/runtime/memmove_386.s:89 +0x7f fp=0x1242fdbc sp=0x1242fdb8 pc=0x623e501f
  14. fmt.(*buffer).writeString(...)
  15. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:82
  16. fmt.(*fmt).padString(0x124a60b0, 0x800588, 0x32efe4)
  17. /Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:110 +0x6c fp=0x1242fdfc sp=0x1242fdbc pc=0x6241576c
  18. fmt.(*fmt).fmtS(0x124a60b0, 0x800588, 0x32efe4)
  19. /Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:359 +0x4d fp=0x1242fe14 sp=0x1242fdfc pc=0x6241664d
  20. fmt.(*pp).fmtString(0x124a6090, 0x800588, 0x32efe4, 0x73)
  21. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:450 +0x188 fp=0x1242fe38 sp=0x1242fe14 pc=0x62418f58
  22. fmt.(*pp).printArg(0x124a6090, 0x62447c80, 0x1248c110, 0x73)
  23. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:698 +0x776 fp=0x1242fe80 sp=0x1242fe38 pc=0x6241ad56
  24. fmt.(*pp).doPrintf(0x124a6090, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1)
  25. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:1030 +0x12b fp=0x1242fef0 sp=0x1242fe80 pc=0x6241d81b
  26. fmt.Fprintf(0x62476550, 0x1248c0d8, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1, 0x623e2fe7, 0x0, 0x12488030)
  27. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:204 +0x52 fp=0x1242ff20 sp=0x1242fef0 pc=0x62417bd2
  28. fmt.Printf(...)
  29. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:213
  30. main.ReadString(...)
  31. /Users/foobar/__projects__/heine/db_dll/perf_nlp.go:19
  32. main._cgoexpwrap_c3579cea1e16_ReadString(0x800588, 0x32efe4)
  33. _cgo_gotypes.go:71 +0x8d fp=0x1242ff54 sp=0x1242ff20 pc=0x6241e9bd
  34. runtime.call16(0x0, 0x32ef1c, 0x32ef68, 0x8, 0x0)
  35. /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:565 +0x30 fp=0x1242ff68 sp=0x1242ff54 pc=0x623e3020
  36. runtime.cgocallbackg1(0x0)
  37. /Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:332 +0x149 fp=0x1242ffb0 sp=0x1242ff68 pc=0x62393f59
  38. runtime.cgocallbackg(0x0)
  39. /Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:207 +0xb5 fp=0x1242ffe0 sp=0x1242ffb0 pc=0x62393d85
  40. runtime.cgocallback_gofunc(0x0, 0x0, 0x0, 0x0)
  41. /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:806 +0x7e fp=0x1242fff0 sp=0x1242ffe0 pc=0x623e419e
  42. runtime.goexit()
  43. /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:1337 +0x1 fp=0x1242fff4 sp=0x1242fff0 pc=0x623e4651
  44. **解决方案:**
  45. ```go
  46. //export ReadString
  47. func ReadString(name *C.char) *C.char {
  48. res := "";
  49. goName := C.GoString(name);
  50. if goName == "Foo" {
  51. res = "From DLL: Hello, Foo"
  52. } else {
  53. res = "From DLL: Hello!"
  54. }
  55. return C.CString(res)
  56. }

Python:

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

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

Go-code:

  1. package main
  2. import "C"
  3. import "fmt"
  4. //export GetInt
  5. func GetInt() int32 {
  6. return 42
  7. }
  8. //export GetString
  9. func GetString() {
  10. fmt.Println("Foo")
  11. }
  12. //export PrintHello
  13. func PrintHello(name string) {
  14. // if name == "hello" { ... }
  15. fmt.Printf("From DLL: Hello, %s!", name)
  16. }
  17. func main() {
  18. // Need a main function to make CGO compile package as C shared library
  19. }

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:

  1. import ctypes
  2. def getString():
  3. nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
  4. dllFunc = nlp.GetString
  5. dllFunc.restype = ctypes.c_char_p
  6. return dllFunc()
  7. def getInt():
  8. nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
  9. dllFunc = nlp.GetInt
  10. dllFunc.restype = int
  11. return dllFunc()
  12. def readString():
  13. nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
  14. dllFunc = nlp.ReadString
  15. dllFunc.argtypes = [ctypes.c_char_p]
  16. dllFunc.restype = ctypes.c_char_p
  17. return dllFunc(b'Foo')
  18. print(getInt())
  19. print(getString())
  20. print(readString()). # Fails

Out:

  1. 42
  2. Foo
  3. None
  4. unexpected fault address 0x871000
  5. fatal error: fault
  6. [signal 0xc0000005 code=0x0 addr=0x871000 pc=0x623e501f]
  7. goroutine 17 [running, locked to thread]:
  8. runtime.throw(0x6245b592, 0x5)
  9. /Users/foobar/.goenv/versions/1.14.15/src/runtime/panic.go:1116 +0x64 fp=0x1242fda4 sp=0x1242fd90 pc=0x623be404
  10. runtime.sigpanic()
  11. /Users/foobar/.goenv/versions/1.14.15/src/runtime/signal_windows.go:249 +0x1ed fp=0x1242fdb8 sp=0x1242fda4 pc=0x623ceb8d
  12. runtime.memmove(0x12500011, 0x800588, 0x32efe4)
  13. /Users/foobar/.goenv/versions/1.14.15/src/runtime/memmove_386.s:89 +0x7f fp=0x1242fdbc sp=0x1242fdb8 pc=0x623e501f
  14. fmt.(*buffer).writeString(...)
  15. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:82
  16. fmt.(*fmt).padString(0x124a60b0, 0x800588, 0x32efe4)
  17. /Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:110 +0x6c fp=0x1242fdfc sp=0x1242fdbc pc=0x6241576c
  18. fmt.(*fmt).fmtS(0x124a60b0, 0x800588, 0x32efe4)
  19. /Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:359 +0x4d fp=0x1242fe14 sp=0x1242fdfc pc=0x6241664d
  20. fmt.(*pp).fmtString(0x124a6090, 0x800588, 0x32efe4, 0x73)
  21. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:450 +0x188 fp=0x1242fe38 sp=0x1242fe14 pc=0x62418f58
  22. fmt.(*pp).printArg(0x124a6090, 0x62447c80, 0x1248c110, 0x73)
  23. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:698 +0x776 fp=0x1242fe80 sp=0x1242fe38 pc=0x6241ad56
  24. fmt.(*pp).doPrintf(0x124a6090, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1)
  25. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:1030 +0x12b fp=0x1242fef0 sp=0x1242fe80 pc=0x6241d81b
  26. fmt.Fprintf(0x62476550, 0x1248c0d8, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1, 0x623e2fe7, 0x0, 0x12488030)
  27. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:204 +0x52 fp=0x1242ff20 sp=0x1242fef0 pc=0x62417bd2
  28. fmt.Printf(...)
  29. /Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:213
  30. main.ReadString(...)
  31. /Users/foobar/__projects__/heine/db_dll/perf_nlp.go:19
  32. main._cgoexpwrap_c3579cea1e16_ReadString(0x800588, 0x32efe4)
  33. _cgo_gotypes.go:71 +0x8d fp=0x1242ff54 sp=0x1242ff20 pc=0x6241e9bd
  34. runtime.call16(0x0, 0x32ef1c, 0x32ef68, 0x8, 0x0)
  35. /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:565 +0x30 fp=0x1242ff68 sp=0x1242ff54 pc=0x623e3020
  36. runtime.cgocallbackg1(0x0)
  37. /Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:332 +0x149 fp=0x1242ffb0 sp=0x1242ff68 pc=0x62393f59
  38. runtime.cgocallbackg(0x0)
  39. /Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:207 +0xb5 fp=0x1242ffe0 sp=0x1242ffb0 pc=0x62393d85
  40. runtime.cgocallback_gofunc(0x0, 0x0, 0x0, 0x0)
  41. /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:806 +0x7e fp=0x1242fff0 sp=0x1242ffe0 pc=0x623e419e
  42. runtime.goexit()
  43. /Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:1337 +0x1 fp=0x1242fff4 sp=0x1242fff0 pc=0x623e4651

Working solution:

  1. //export ReadString
  2. func ReadString(name *C.char) *C.char {
  3. res := "";
  4. goName := C.GoString(name);
  5. if goName == "Foo" {
  6. res = "From DLL: Hello, Foo"
  7. }else{
  8. res = "From DLL: Hello!"
  9. }
  10. return C.CString(res)
  11. }

Python:

  1. def readString():
  2. nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
  3. dllFunc = nlp.ReadString
  4. dllFunc.argtypes = [ctypes.c_char_p]
  5. dllFunc.restype = ctypes.c_char_p
  6. 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:

确定