Search code examples
pythoncgocgo

Python module written in Golang and C


I follow this tutorial

to write this code, in C:

#define Py_LIMITED_API
#include <Python.h>

PyObject * startVM(PyObject *, PyObject *);

int PyArg_ParseTuple_S(PyObject * args, char* a) {  
    return PyArg_ParseTuple(args, "s", &a);
}

static PyMethodDef FooMethods[] = {  
    {"startVM", startVM, METH_VARARGS, "Starts."},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef foomodule = {  
   PyModuleDef_HEAD_INIT, "foo", NULL, -1, FooMethods
};

PyMODINIT_FUNC PyInit_foo(void) {
    return PyModule_Create(&foomodule);
}

and this code in GO:

package main

import "fmt"


// #cgo pkg-config: python3
// #define Py_LIMITED_API
// #include <Python.h>
// int PyArg_ParseTuple_S(PyObject *,char *);
import "C"

//export startVM
func startVM(self, args *C.PyObject) {  
    var a *C.char
    if C.PyArg_ParseTuple_S(args, a) == 0 {
        //return nil
    }
    fmt.Println(a)
    //return C.PyBytes_FromString(&a)
}

func main() {}  

I can compile the code in go, but when I call in python the module with this command: python3 -c 'import foo; foo.startVM("hello")', it prints nil and results in segmentation fault... Could someone know how to fix it? Thanks in advance.


Solution

  • Nil output

    This function:

    int PyArg_ParseTuple_S(PyObject * args, char* a) {
        return PyArg_ParseTuple(args, "s", &a);
    }
    

    will only set the local copy of a and won't return it into the calling function, because you pass the string pointer by value (by copying) so PyArg_ParseTuple only sets the copy.

    var a *C.char
    C.PyArg_ParseTuple_S(args, a)
    // Here `a` is not set, so it keeps its default value: nil.
    

    You can solve this by passing pointer to your string instead of a string itself:

    // C
    int PyArg_ParseTuple_S(PyObject * args, char** a) {
        return PyArg_ParseTuple(args, "s", a);
    }
    
    // Go
    var a *C.char
    if C.PyArg_ParseTuple_S(args, &a) == 0 {
        //return nil
    }
    

    Correct printing

    fmt.Println(a) will print the address held by a, and not the string, which it points to. Go has own type for strings and does not work with C strings.

    If you want to print the text properly, you must convert it using C.GoString:

    // C string to Go string
    func C.GoString(*C.char) string
    

    (from https://golang.org/cmd/cgo/)

    For example:

    str := C.GoString(a)
    fmt.Println(str)
    

    Segmentation fault.

    I'm not familiar with python modules development, but I can assume, that the fault happens, because python method is expected to return a valid PyObject* or NULL. But you code does none of that. The return value of startVM is not set and it doesn't default to nil, python accepts this non-nil pointer as a valid object and dereferences it, which causes segmentation error.

    Specifying the return type of startVM might help:

    //export startVM
    func startVM(self, args *C.PyObject) *C.PyObject {  
        // ...some code...
        return nil
    }