How to apply a SWIG typemap for a double pointer struct argument

空扰寡人 提交于 2019-12-07 22:41:26

问题


I have an API that I am trying to wrap using SWIG such that I can call the underlying C library from python.

I have got stuck with a particular API fn:

int update_tracks(track_t **phash_tracks,
                  const pdws_t *pdw_frame,
                  const rdws_t *rdw_frame,
                  lib_t *lib,
                  lib_meta_t *lib_meta,
                  const cfg_t *cfg);

Its the double pointer to track_t data structure that I can't handle.

All the single pointers work fine.

This is the only API fn that has a double pointer to track_t

All the others only have a single pointer, e.g.

void print_hash_tracks(const track_t *hash_tracks, const cfg_t *cfg,
                       enum TRKTYPE trktype);

I'm pretty sure I need to make a typemap in my SWIG interface file (interface.i) but I am finding the SWIG docs impenetrable.

What I think I need to do is create a typemap that whenever it sees the track_t** type, it takes a track_t* and converts it to its address, something like:

/* provide typemap to handle instances of track_t** parameters */
%typemap(in) track_t** (track_t *tracks) {
    $1 = &tracks;
}

but I'm just getting segmentation faults when I run:

tracks = g3.track_t()
g3.update_tracks(tracks, pdw_frame, rdw_frame, lib, lib_meta, cfg)

on the python side.

I feel like I've almost solved this but can't quite get the typemap specification right and at the same time struggling to understand the relevant documentation.

flexo - if you're out there - maybe you can shed some light on this, you seem to be the SO expert in this area..

UPDATE - m7ython (brilliant! another SWIG expert on SO)

Usage in C is pretty straigthforward

declare and initialise a track_t pointer to NULL:

track_t *hash_tracks = NULL;

then:

update_tracks(&hash_tracks, &pdw_frame, &rdw_frame,
              &lib, &lib_meta, &cfg);

So the address of the pointer to track_t is passed as an arg to update_tracks(). The update_tracks() fn takes care of all the necessary mallocs for the data that gets put into hash_tracks, i.e. the hash table of track_t structs

All the other args are single pointers and I can create and populate them with no issues on the python side.

track_t is a struct containing a bunch of ints, floats, char* etc. e.g.

typedef struct
{
/* make struct hashable */
UT_hash_handle hh;

int id;
...
char name[MAX_BUF];
...
} track_t;

The reason that the track_t arg is a track_t** and not just a track_t* is because hash_tracks is a pointer to a hash table (using the UTHash library). hash_tracks points to the 1st track_t in the hash table. In the body of the update_tracks() fn track_t structs can be added/removed from the hash table, such that the pointer to the 1st track_t may change, i.e. hash_tracks may point to something else after the call to update_tracks(), hence the reason for passing a pointer to the pointer.

In other words, the track_t** arg, phash_tracks is being used both as an input and output type arg, hence the pointer to a pointer. All the other args are simply inputs, they don't change so they can be passed in as single pointers.

I attempted the 'helper fn' route with the following C fn:

track_t** make_phash_tracks(void)
{
    track_t **phash_tracks;

    phash_tracks = calloc(1, sizeof(track_t*));

    return phash_tracks;
}

the use of calloc should ensure that *phash_tracks is NULL

this compiled and wrapped with no errors, but when I used it from the python side it segfaulted, e.g.

phash_tracks = g3.make_phash_tracks()
g3.update_tracks(phash_tracks, pdw_frame, rdw_frame, lib, lib_meta, cfg)

checking the phash_tracks var just prior to calling update_tracks gave:

(Pdb) p phash_tracks
<Swig Object of type 'track_t **' at 0x7fb9e37c9030>

回答1:


EDIT: Ok, I think I now understand what update_tracksdoes. It seems you can use the function in two ways. Either to update existing tracks, or to create tracks if you pass a pointer to a NULL pointer. I am not sure about the most elegant way to handle both cases in SWIG (or if this is even a problem), but here are some options.

1. phash_tracks is an output argument

First, you must pass *phash_tracks back to Python as a return value, and use the function in some form like

>>> int_res, tracks = g3.update_tracks(tracks, pdw_frame, rdw_frame, lib, lib_meta, cfg)

or

>>> int_res, tracks = g3.update_tracks(pdw_frame, rdw_frame, lib, lib_meta, cfg)

This is accomplished by the following "argout" typemap:

%typemap(argout) track_t **phash_tracks {
  %append_output(SWIG_NewPointerObj(%as_voidptr(*$1), $*1_descriptor, SWIG_POINTER_OWN));
}

Maybe you don't want Python to take ownership of the track_t*, then replace SWIG_POINTER_OWN by 0.

2. Passing an empty phash_tracks

If you only want to use the update_tracks function to create tracks, you can do essentially what you are already doing. Use the following "in" typemap, and use the function as in the second example above (without the tracks parameter).

%typemap(in, numinputs=0) track_t **phash_tracks (track_t *tracks) {
  tracks = NULL;
  $1 = &tracks;
}

3. phash_tracks as an input (and output) argument

If you want to use update_tracks to update existing tracks, you should be able to use the "in" typemap I suggested before, and use the function from Python as in the first example (including the tracks parameter).

%typemap(in) track_t **phash_tracks (track_t *tracks) {
  if ((SWIG_ConvertPtr($input, (void **) &tracks, $*1_descriptor, SWIG_POINTER_EXCEPTION | SWIG_POINTER_DISOWN)) == -1)
    return NULL;
  $1 = &tracks;
}

Note that it is important that Python disowns its tracks_t*.

4. Enabling both (2) and (3) above

You could basically use version (3) also to create tracks, if you could get swig to pass a wrapped NULL tracks_t*. I am not sure if SWIG allows this -- but maybe it does. Try using a helper function:

tracks_t* empty_tracks() { return NULL; }

Alternatively, you can modify the "in" typemap along the following lines, attempting to convert the provided argument to a track_t* and passing its address, or alternatively passing the address of a NULL track_t*.

%typemap(in) track_t **phash_tracks (track_t *tracks) {
  // Alternatively, check if $input is a 0 integer `PyObject`...
  if ((SWIG_ConvertPtr($input, (void **) &tracks, $*1_descriptor, SWIG_POINTER_DISOWN)) == -1)
    tracks = NULL;
  $1 = &tracks;
}

Then, from Python, just pass something else to create tracks:

>>> int_res, tracks = g3.update_tracks(0, pdw_frame, rdw_frame, lib, lib_meta, cfg)


来源:https://stackoverflow.com/questions/36184402/how-to-apply-a-swig-typemap-for-a-double-pointer-struct-argument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!