cluaswiglua-tabletypemaps

Creating typemaps for SWIG so C function can return a Lua table


I'm trying to wrap a C function which creates float array and then return this array as Lua table so it can be used in Lua.

Here's the C function that returns float array with 4 elements.

static void getArray(int size, float values[4]) {

    for (int i=0; i<size; ++i)
        values[i] = (float)i;
}

And this is the typemaps part in .i file.

// using typemaps
%include <typemaps.i>
%apply (float OUTPUT[ANY]) {(float values[4])}; 

And in Lua, I can use the function as the following,

arr = my.getArray(4); //table "arr" is now {0,1,2,3}

While this works fine, I wonder if it is possible to create a C function that can return a mutable float array.

So I think the function will look like this.

static void getArray(int size, float **values) {

    //create a float array and then return this as a table in Lua. 
}

However, I don't know how to bind this function with SWIG interface(.i).

I tried everything I can do but couldn't make it to work so far.

Can anyone please guide me how to wrap this function using SWIG so I can return a mutable float array as a table in Lua?

P.S: Here's the link to the Lua SWIG documentation. http://www.swig.org/Doc1.3/Lua.html

------------------------ADDED BELOW------------------------

@Flexo Based on your updated solution, I could successfully bind getArray function which looks like the following.

static void getArray(const string &name, int *size, t_word **values) {

        t_garray *a;
        int vecsize;
        t_word *vec;

        if (getArrayData(name, &a, &vecsize, &vec)) {

            *values = vec;
            *size = vecsize;
        }
    }

And this is my SWIG interface.

%typemap(in,numinputs=0) (int *size, t_word **values) (t_word *tmp=NULL, int tsize=0) %{
  $2 = &tmp; // Use the temporary we setup
  $1 = &tsize;
%}

%typemap(argout) (int *size, t_word **values) {

  int i;
  lua_newtable(L);
  for (i = 0; i < *$1; i++){
    lua_pushnumber(L,(lua_Number)(*$2)[i].w_float);
    lua_rawseti(L,-2,i+1);/* -1 is the number, -2 is the table*/
  }
  SWIG_arg++;
}

t_word is a struct used for array which has a float data w_float.

I didn't need to free anything as I didn't allocate new memory and it seems to work like a charm.

Thank you so much for your help. I learned a lot from your code.


Solution

  • You can do this with a multi-argument typemap. I based mine on the ones for OUTPUT[ANY] in typemaps.i and put together a quick test to show how it might work:

    %module test
    
    %typemap(in,numinputs=1) (int size, float **values) (float *tmp=NULL) %{
      $2 = &tmp; // Use the temporary we setup
      // Either of the next two lines are equivalent 
      //$1 = (int)lua_tonumber(L,$input);
      $typemap(in,int);
    %}
    
    %typemap(freearg) (int size, float **values) %{
      free(tmp$argnum); // Assuming this is the right semantics here
    %}
    
    %typemap(argout) (int size, float **values) {
      // Adapted from OUTPUT[ANY] argout in typemaps.i 
      int i;
      lua_newtable(L);
      for (i = 0; i < $1; i++){
        lua_pushnumber(L,(lua_Number)(*$2)[i]);
        lua_rawseti(L,-2,i+1);/* -1 is the number, -2 is the table*/ \
      }
      SWIG_arg++;
    }
    
    %inline %{
    static void getArray(int size, float **values) {
        //create a float array and then return this as a table in Lua.
        float *arr = malloc(sizeof *arr * size);
        for (int i = 0; i < size; ++i) {
            arr[i] = i;
        } 
        *values = arr;
    }
    

    From Lua we can use this function like this:

    Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
    > l=require('test')
    > l
    table: 0xcb87de8
    > l.getArray()
    stdin:1: Error in getArray expected 1..1 args, got 0
    stack traceback:
            [C]: in function 'test.getArray'
            stdin:1: in main chunk
            [C]: in ?
    > l.getArray(1)
    table: 0xcb86d48
    > l.getArray(1)
    table: 0xcb88b50
    > l.getArray(1)[0]
    nil
    > l.getArray(1)[1]
    0.0
    > l.getArray(1)[2]
    nil
    > l.getArray(3)[2]
    1.0
    > l.getArray(3)[3]
    2.0
    > l.getArray(3)[4]
    nil
    > 
    

    Most of the work here is in the argout typemap, which creates a new table and populates it with the values that our getArray() function placed into the float ** output parameter.

    If the real version of getArray() doesn't actually let you specify or find out the length of the output then you're better off not wrapping that at all and focusing on wrapping getArrayData() itself. (You can use %rename to make the interface feel more similar for users already familiar with the C++ API if desired though). To wrap getArrayData() I'd change this to be something like:

    %module test
    %include <std_string.i>
    %typemap(in,numinputs=0) (int *size, float **values) (float *tmp=NULL,int tsize=0) %{
      $2 = &tmp;
      $1 = &tsize;
    %}
    
    %typemap(argout) (int *size, float **values) {
      // Adapted from OUTPUT[ANY] argout in typemaps.i 
      int i;
      lua_newtable(L);
      for (i = 0; i < *$1; i++){
        lua_pushnumber(L,(lua_Number)(*$2)[i]);
        lua_rawseti(L,-2,i+1);/* -1 is the number, -2 is the table*/ \
      }
      SWIG_arg++;
    }
    
    %inline %{
    static void getArrayData(const std::string& name, void *something_else, int *size, float **values) {
        // pretend like someone already owns this memory
        static float arr[100]; 
        *size = sizeof arr / sizeof *arr;
        for (int i = 0; i < *size; ++i) {
            arr[i] = i;
        } 
        *values = arr;
    }
    %}
    

    Key changes: