NirIzr
NirIzr

Reputation: 3410

OUT argument internally allocated to return an array of structs

I'm new to swig and I have the following function which i cant fix:

int get_list(IN const char * string, OUT struct entry ** results);

where struct entry is defined:

struct flux_entry
{
    char * addr_str;
    char cc[2];
};

the entry struct is properly converted to a python class. I googled but couldn't find any explanation i could use.
I want to make it return a tuple of: (original get_list int return value, python list of entry python objects, based on the results buffer), but don't know how to convert the C entry to a python object in the argout code snippet. I've managed to get thus far:

%typemap(argout) struct entry **
{
    PyObject *o = PyList_New(0);
    int i;
    for(i=0; $1[i] ; i++)
    {
        PyList_Append(o, SWIG_HOW_TO_CONVERT_TO_PYOBJECT($1[i]));
    }
    $result = o;
}

what should i replace SWIG_HOW_TO_CONVERT_TO_PYOBJECT with? passed results is supposed to be a pointer to a (struct entry *) type, set to NULL before calling get_list and should be set to an allocated array of struct entry * pointers. maybe a small wrapper function could make that easier?

the struct entry array is allocated within the C function using malloc, after calculating (inside get_list) how many elements are needed, and ends with a NULL pointer to indicate the end of the array.

i'd also like to make sure it's freed somewhere :)

thanks!

Upvotes: 0

Views: 527

Answers (1)

Mark Tolonen
Mark Tolonen

Reputation: 177901

This should at least give you a starting point that works. I still wasn't sure how the data was returned, since to return an array of pointers so that the final one was NULL I'd think you'd need a struct entry ***, so I just set addr_str = NULL on the last one as a sentinel, and just put some dummy data partially based on the input string into the fields. Modify as needed to suit your needs:

%module example

// Insert the structure definition and function to wrap into the wrapper code.
%{
  struct entry {
    char* addr_str;
    char cc[2];
  };

  int get_list(const char* string, struct entry** results)
  {
    *results = malloc(3 * sizeof(struct entry));
    (*results)[0].addr_str = malloc(10);
    strcpy((*results)[0].addr_str,"hello");
    (*results)[0].cc[0] = string[0];
    (*results)[0].cc[1] = string[1];
    (*results)[1].addr_str = malloc(10);
    strcpy((*results)[1].addr_str,"there");
    (*results)[1].cc[0] = string[2];
    (*results)[1].cc[1] = string[3];
    (*results)[2].addr_str = NULL;
    return 0;
  }
%}

#include <typemaps.i>

// Define the structure for SWIG
struct entry {
  char* addr_str;
  char cc[2];
};

// Define a set of typemaps to be used for an output parameter.

// This typemap suppresses requiring the parameter as an input.
// A temp variable is created and passed instead.
%typemap(in,numinputs=0) struct entry **OUTPUT (struct entry* temp) %{
  $1 = &temp;
%}

// Build a list of tuples containing the two entries from the struct.
// Append the new Python list object to the existing "int" result.
%typemap(argout) struct entry **OUTPUT {
  int i = 0;
  PyObject* out = PyList_New(0);
  while((*$1)[i].addr_str != NULL)
  {
    //PyObject* t = PyTuple_New(2);
    //PyTuple_SET_ITEM(t,0,PyBytes_FromString((*$1)[i].addr_str));
    //PyTuple_SET_ITEM(t,1,PyBytes_FromStringAndSize((*$1)[i].cc,2));
    //PyList_Append(out,t);
    //Py_DECREF(t);
    PyObject* s = SWIG_NewPointerObj(*$1+i,$descriptor(struct entry*),0);
    PyList_Append(out,s);
    Py_DECREF(s);
    ++i;
  }
  $result = SWIG_AppendOutput($result,out);
}

// Since a Python object was created and the data copied for each entry struct,
// free the memory returned in the structure.
//%typemap(freearg) struct entry **OUTPUT {
//  int i=0;
//  while((*$1)[i].addr_str != NULL) {
//    free((*$1)[i].addr_str);
//    ++i;
//  }
//  free(*$1);
//}

// Apply the OUTPUT typemap set to the "results" parameter.
%apply struct entry **OUTPUT {struct entry** results};

// Finally, define the function for SWIG
int get_list(const char* string, struct entry** results);

Demo (Python 3.3):

>>> import example
>>> example.get_list('abcd')
[0, [(b'hello', b'ab'), (b'there', b'cd')]]

Hope that helps.

Edit:

I commented out the tuple creation and just save the entry* proxy instead. This doesn't leak Python objects, but the memory malloced for use by an entry* is not freed. I'm not sure where to put that, although I'm experimenting with %extend.

Upvotes: 1

Related Questions