Search code examples
pythoncpython-c-api

Avoid garbage collection of C written Python library


I'm struggling with my C written Python library. The code is meant to write a register in a cp210x in order to control a Relay card. The code works, however Python clears the object somehow.

With other words, the C variable ttyPort is cleared after ending a function.

Here's the C code:

#include <Python.h>
#include <fcntl.h>
#include <stropts.h>

// C variable that holds the tty address (e.g. /dev/ttyUSB0)
const char* ttyPort;

int setRelay(int action, int relayNumber)
{
    /* more magic over here */
    printf("Port :: %s\n", ttyPort);

}
// All python wrappers below

static PyObject*setPort(PyObject* self, PyObject* args)
{
    Py_INCREF(self)
    // Copy python argument to ttyPort
    if (!PyArg_ParseTuple(args, "s", &ttyPort))
        return NULL;

    printf("RelayModule :: Port set (%s)\n", ttyPort);

    Py_RETURN_NONE;
}

static PyObject*fanOff(PyObject* self, PyObject* args)
{
    //fanMode = 0;
    printf("RelayModule :: Fan off %s\n",ttyPort);
    if (setRelay(RELAY_OFF, 0) == 1){
        // more magic
    }
    Py_RETURN_NONE;
}
/* more functions over here */
static PyMethodDef RelayMethods[] =
{
     {"setPort", setPort, METH_VARARGS, "Set tty port."},
     {"fanOff", fanOff, METH_NOARGS, "Fan off."},
     {"fanHalf", fanHalf, METH_NOARGS, "Fan half speed."},
     {"fanFull", fanFull, METH_NOARGS, "Fan full on."},
     {"pumpOn", pumpOn, METH_NOARGS, "Pump on."},
     {"pumpOff", pumpOff, METH_NOARGS, "Pump off."},
     {"isPumpOn", isPumpOn, METH_NOARGS, "Check if pump is on."},
     {"getFanMode", getFanMode, METH_NOARGS, "Get fan mode."},
     {NULL, NULL, 0, NULL}
};

static struct PyModuleDef RelayDefs = { 
    PyModuleDef_HEAD_INIT,"RelayModule","A Python module that controls the Conrad 4ch relay card.", -1, RelayMethods 
};


PyMODINIT_FUNC PyInit_Relay(void)
{
    Py_Initialize();
    return PyModule_Create(&RelayDefs);
}

The python code for example:

import Relay

def settty():
    Relay.setPort("/dev/ttyUSB0")

def gettty():
    Relay.fanOff()

def doAll():
    Relay.setPort("/dev/ttyUSB0")
    Relay.fanOff()

settty()
gettty() #Error, prints 'Port :: [garbage]'
doAll() #works!

How can I somehow declare an object? In other languages I'd do: RelayObj = new Relay()

Or how can I store a variable correctly?


Solution

  • One possible fix would be to use 'es' format specifier in PyArg_ParseTuple(args, "s", &ttyPort) instead of 's'. From documentation:

    In general, when a format sets a pointer to a buffer, the buffer is managed by the corresponding Python object, and the buffer shares the lifetime of this object

    unless you use 'es'. You will have to free memory manually, however.