pythoncstructswigtypemaps

Use struct in SWIG argout typemap


I want to call a C function from Python, which should create and initialize a struct. I want this struct converted to a Python object as the return value in Python.


Solution

  • Here are example files (based on Accessing C struct array to Python with SWIG) which achieve what I want, i.e. create_struct() creates and initializes a struct, which can be used in Python. Thanks to John Bollinger for helping to fix bugs.

    example.h

    #include <stdint.h>
    
    struct Foo
    {
      uint8_t a[4];
    };
    
    void create_struct(struct Foo** new_struct);
    

    example.c

    #include <string.h>
    #include "example.h"
    
    void create_struct(struct Foo** new_struct){
        struct Foo* foo = (struct Foo*) malloc(sizeof(struct Foo));
        uint8_t tmp[4] = {0,1,2,3};
        memcpy(foo->a, tmp, 4);
        *new_struct = foo;
    }
    

    example.i

    %module example
    %{
    #define SWIG_FILE_WITH_INIT
    #include "example.h"
    %}
    
    // Define input and output typemaps for a
    
    %typemap(in) uint8_t a[4] {
      if (!PyBytes_Check($input)) {
        PyErr_SetString(PyExc_TypeError, "Expecting a bytes parameter");
        SWIG_fail;
      }
    
      if (PyObject_Length($input) != 4) {
        PyErr_SetString(PyExc_ValueError, "Expecting a bytes parameter with 4 elements");
        SWIG_fail;
      }
    
      uint8_t res[4];
      char* bytes = PyBytes_AsString($input);
      int i;
      for (i=0; i<4; i++) {
        res[i] = (uint8_t) bytes[i];
      }
    
      $1 = res;
    }
    
    %typemap(out) uint8_t a[4] {
        $result = PyBytes_FromStringAndSize((char*) $1, 4);
    }
    
    /*
     * This fails with "Warning 453: Can't apply (struct Foo *OUTPUT). No typemaps are defined.":
     *   %apply struct Foo* OUTPUT {struct  Foo* new_struct };
     * So I'm trying to define typemaps for struct Foo*
     */
    
    // This typemap suppresses requiring the parameter as an input.
    %typemap(in,numinputs=0) struct Foo** new_struct (struct Foo* temp) {
      $1 = &temp;
    }
    
    %typemap(argout) struct Foo** new_struct {
        $result = SWIG_NewPointerObj(*$1, $descriptor(struct Foo*), SWIG_POINTER_OWN);
    }
    
    %include "example.h"
    
    extern void create_struct(struct Foo** new_struct);
    

    setup.py

    #!/usr/bin/env python3
    
    from distutils.core import setup, Extension
    
    module1 = Extension('example', sources=['example.c', 'example.i'])
    setup(name='Example', version='0.1', ext_modules=[module1])
    

    test.py

    #!/usr/bin/env python3
    
    import example
    foo = example.create_struct()
    print("foo.a: %r" % foo.a)
    

    Build and execute:

    python3 setup.py build_ext --inplace && mv example.*.so _example.so && python3 test.py
    

    The test code should print foo.a: b'\x00\x01\x02\x03'.