Home > Software engineering >  Ctypes calling Go Dll with arguments (C string)
Ctypes calling Go Dll with arguments (C string)

Time:12-15

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.

  • Related