2

I'm trying to wrap some functions defined in a DLL using Cython, the difficulty is that lots of this functions use pointers to void*, here is an example of functions prototype:

---------------"header.h"-----------------------

typedef void* HANDLE

int open(HANDLE* a_handle_pointer , int open_mode)

int use(HANDLE a_handle, int usage_mode )

the usage example in C is:

---------------"main.c" -----------------

#include"header.h"

HANDLE my_handle ;
int results ;
if(open(&my_handle ,1) == 0) /* open a handle with mode 1 */
{
    printf ("failed to open \n);
    return 0;
}

else printf("open success \n");

use(handle , 2); /* use handle (opened with open) in mode 2 */

As you can remark, the "Use" function can't do something unless the Handle had already been opened using the "open" function, which makes it confusing in Python/cython

Here is how I define my function "open" in Cython (one of several trials)

from libc.stdint cimport uintptr_t
cdef extern from "header.h":
     ctypedef void*     HANDLE 
     int open(HANDLE* a_handle_pointer , int open_mode)


def Open( uintptr_t a_handle_pointer , int open_mode)

    return open(<HANDLE*> a_handle_pointer , open_mode)

I have tried to cast void * pointer to uintptr_t as some people advise, but still get the error:

" TypeError: an integer is required " when calling the function.

>>>from my_module import open
>>>open (handle , 1)

What can I do to solve this problem?

I am wondering how would I call a function from Python with an argument of type void* or void**?

double-beep
  • 5,031
  • 17
  • 33
  • 41
werber bang
  • 173
  • 3
  • 11

2 Answers2

1

Writing the module/binding in Python itself is a bad idea, specially if pointers are involved. You should rather do it in C with something like this... Warning: This is specific to CPython 3+. CPython 2 extensions are coded differently! BTW: Renamed your open function as load because it conflicts with POSIX's open(3).

// my_module.c: My Python extension!

/* Get us the CPython headers.
 */
#include "Python.h"

/* And your function's headers, of course.
 */
#include "header.h"

/* Actual structures used to store
 * a 'my_module.Handle' internally.
 */
typedef struct
{
    PyObject_HEAD /* The base of all PyObjects. */
    HANDLE handle; /* Our handle, great! */
} my_module_HandleObject;

/* The type 'my_module.Handle'. This variable contains
 * a lot of strange, zero, and NULLified fields. Their
 * purpose and story is too obscure for SO, so better
 * off look at the docs for more details.
 */
static PyTypeObject my_module_HandleType =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "my_module.Handle", /* Of course, this is the type's name. */
    sizeof(my_module_HandleObject), /* An object's size. */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ... Don't ask. */
    Py_TPFLAGS_DEFAULT, /* The type's flags. There's nothing special about ours, so use the defaults. */
    NULL /* No docstrings for you! */
};

/* The "wrapper" function. It takes a tuple of
 * CPython PyObject's and returns a PyObject.
 */
static PyObject *my_module_load(PyObject *self, PyObject *args)
{
    int load_mode;
    if(!PyArg_ParseTuple(args, "i", &load_mode) != 0) { /* Parse the argument list. It should have one single integer ("i") parameter. */
        return NULL;
    }

    /* Create a Handle object, so as to put
     * in itthe handle we're about to get.
     */
    my_module_HandleObject *the_object = PyObject_New(my_module_HandleObject, &my_module_HandleType);
    if(the_object == NULL) {
        return NULL;
    }

    /* Finally, do our stuff.
     */
    if(load(&the_object->handle, load_mode) == -1) {
        Py_DECREF(the_object);
        PyErr_SetFromErrno(NULL);
        return NULL;
    }

    return (PyObject*)the_object;
}

/* The method table. It is a list of structures, each
 * describing a method of our module.
 */
static struct PyMethodDef my_module_functions[] =
{
    {
        "load", /* The method's name, as seen from Python code. */
        (PyCFunction)my_module_load, /* The method itself. */
        METH_VARARGS, /* This means the method takes arguments. */
        NULL, /* We don't have documentation for this, do we? */
    }, { NULL, NULL, 0, NULL } /* End of the list. */
};

/* Used to describe the module itself. */
static struct PyModuleDef my_module =
{
    PyModuleDef_HEAD_INIT,
    "my_module", /* The module's name. */
    NULL, /* No docstring. */
    -1,
    my_module_functions,
    NULL, NULL, NULL, NULL
};

/* This function _must_ be named this way
 * in order for the module to be named as
 * 'my_module'. This function is sort of
 * the initialization routine for the module.
 */
PyMODINIT_FUNC PyInit_my_module()
{
    my_module_HandleType.tp_new = PyType_GenericNew; /* AFAIK, this is the type's constructor. Use the default. */
    if(PyType_Ready(&my_module_HandleType) < 0) { // Uh, oh. Something went wrong!
        return NULL;
    }

    PyObject *this_module = PyModule_Create(&my_module); /* Export the whole module. */
    if(this_module == NULL) {
        return NULL;
    }

    Py_INCREF(&my_module_HandleType);
    PyModule_AddObject(this_module, "Handle", (PyObject*)&my_module_HandleType);

    return this_module;
}

In order to build and install the extension, see the docs on distutils.

3442
  • 8,248
  • 2
  • 19
  • 41
  • Thanks for your answer ,I've been studying it for a while now ,so your Idea is to abandon Cython and focus on creating an extension module , so you defined the `Handle` type inside a struct , and the `load` function which will use access to the address of `the_object->handle`, and return `the_object` to be used in the future by the `use` function , It's a great idea I will work on it , the typical usage will be now: `handle = load(2) ### use(handle, 1) ` , I have one question still : can the speed of this extension matche the speed of C (as it is written in C ) ?. – werber bang Mar 17 '16 at 10:09
  • @werberbang: It *matches* the speed, performance, etc... of C, I mean, it's C after all. The only overhead is that of handling Python wrapper stuff. However, the cost of that is negligible. I wouldn't go with Cython, mainly because CPython extensions are more widely supported, and because Cython can be pretty limiting when working with pointers. – 3442 Mar 17 '16 at 22:11
  • @werberbang: BTW, it's worth nothing that Cython is just a wrapper language around C extensions. – 3442 Mar 17 '16 at 22:12
  • 1
    Why to do this and not define a class using Cython? They can contain HANDLE like pointers. – J.J. Hakala Mar 18 '16 at 17:44
  • @J.J.Hakala: Of course that's possible (note: I have a negative bias towards Cython). However, this solution is (a bit) more flexible, and has the benefits of C-land performance, which the OP seems to be seeking for (see his comment above). [See this](http://stackoverflow.com/a/5729449/5249858) for details. – 3442 Mar 18 '16 at 18:39
  • @J.J.Hakala is there an online tutorial for making that , the difficulty that i'm facing here is the 1: "cannot convert python object to HANDLE or HANDLE*" when I compile the pyx file . and 2: I dont know how would I pass an argument of type`void* or void** `from within python to my function or get one from it : ex `int open(HANDLE* a_handle_pointer , int open_mode) `. – werber bang Mar 21 '16 at 09:22
1

You need to wrap your pointer in a Cython/Python object and then pass an instance of that object to the Python versions of your open and use functions. Please look at the question Wrapping custom type C++ pointer in Cython for how to wrap and pass around pointers in Cython. The answer I provided there should also work for void-pointers.

Unless you really want to map the C-API as unmodified as possible to Python you should also consider using a different, possibly object-oriented design for the Python interface. For example you could put the call to open() in the constructor of your Python class that wraps the HANDLE pointer, raise an exception on errors and make use() an instance method of of that class. That way you can prevent errors from using unitialized pointers and integer return values for indicating errors which might be unexpected for a Python programmer.

Community
  • 1
  • 1
michitux
  • 403
  • 2
  • 10