Menu

#1315 numinputs=0 doesn't work with argout of type "pointer to struct" in python

None
closed-invalid
nobody
None
5
2022-03-21
2013-04-12
No

When trying to wrap a C function that has an output argument of type "pointer to struct", the (in,numinputs=0) typemap prevents the (argout) typemap from working.

Example:

The C function to access from python is:

ucis_GetScopeSourceInfo(
  ucisT               db,
  ucisScopeT          scope,
  ucisSourceInfoT*    sourceinfo);

The input typemap used to prevent python scripts from having to pass in a dummy 3rd argument is:

%typemap(in, numinputs=0) ucisSourceInfoT *sourceinfo (ucisSourceInfoT *temp) {
  $1 = &temp;
}

The typemap specifying that the 3rd argument is an output, and how to handle its pointer is:

%typemap(argout) ucisSourceInfoT *sourceinfo {
  PyObject *o = SWIG_NewPointerObj($1, $1_descriptor, 0);
  $result = SWIG_Python_AppendOutput($result, o);
}

This should be callable from a python script like this:

(status,sourceinfo) = ucis_GetScopeSourceInfo(cb.db, cb.obj)

This will run, but the fields of sourceinfo contain garbage when printed.

To prove that the (numinputs=0) typemap is the source of the problem, it was removed while keeping the (argout) typemap the same, and the python script was modified as follows:

dummy = malloc_ucisSourceInfoT()
(status,sourceinfo) = ucis_GetScopeSourceInfo(cb.db, cb.obj, dummy)

This works perfectly, except that it requires scripts to add dummy code, and makes things more cumbersome/less readable.

Discussion

  • Olly Betts

    Olly Betts - 2022-03-21
    • status: open --> closed-invalid
    • Group: -->
     
  • Olly Betts

    Olly Betts - 2022-03-21

    The supplied in typemap doesn't compile as $1 is a ucisSourceInfoT* but &temp is a ucisSourceInfoT**.

    If I change it to:

    %typemap(in, numinputs=0) ucisSourceInfoT *sourceinfo (ucisSourceInfoT *temp) {
      $1 = &temp;
    }
    

    then the problem is that we wrap and return a pointer to a ucisSourceInfoT which is a local variable to the wrapper function, so isn't valid after returning and you get garbage results (in fact, C undefined behaviour).

    (If instead the assignment is changed to $1 = temp then we don't have an object at all!)

    I think numinputs=0 is not to blame here - the "dummy" example works because an object is allocated which lives on after the wrapper function returns. Make the typemap version do the same and it works (I also pass SWIG_POINTER_OWN so SWIG knows it is responsible for calling free() on the pointer so we don't leak memory:

    $ cat test.i
    %module test
    
    %typemap(in, numinputs=0) ucisSourceInfoT *sourceinfo {
      $1 = calloc(1, sizeof(ucisSourceInfoT));
    }
    
    %typemap(argout) ucisSourceInfoT *sourceinfo {
      PyObject *o = SWIG_NewPointerObj($1, $1_descriptor, SWIG_POINTER_OWN);
      $result = SWIG_Python_AppendOutput($result, o);
    }
    
    %inline %{
    typedef int ucisT;
    typedef int ucisScopeT;
    typedef struct { int x, y; } ucisSourceInfoT;
    
    int ucis_GetScopeSourceInfo(
      ucisT               db,
      ucisScopeT          scope,
      ucisSourceInfoT*    sourceinfo) {
          sourceinfo->x = 7;
          sourceinfo->y = 42;
          return 0;
      }
    %}
    $ swig -python test.i
    $ gcc `python3-config --cflags` -fPIC -shared test_wrap.c -o _test.so
    $ python3
    Python 3.9.10 (main, Feb 22 2022, 13:54:07) 
    [GCC 11.2.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from test import *
    >>> (status,sourceinfo) = ucis_GetScopeSourceInfo(1, 2)
    >>> status
    0
    >>> sourceinfo.x
    7
    >>> sourceinfo.y
    42
    

    The problem was in the example so closing.

     

    Last edit: Olly Betts 2022-03-21

Log in to post a comment.