如何为双指针结构参数应用 SWIG 类型映射

Posted

技术标签:

【中文标题】如何为双指针结构参数应用 SWIG 类型映射【英文标题】:How to apply a SWIG typemap for a double pointer struct argument 【发布时间】:2016-03-23 17:03:08 【问题描述】:

我有一个 API,我正在尝试使用 SWIG 进行包装,以便我可以从 python 调用底层 C 库。

我被一个特定的 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);

这是我无法处理的指向track_t 数据结构的双指针。

所有单指针都可以正常工作。

这是唯一一个具有指向 track_t 的双指针的 API fn

所有其他的只有一个指针,例如

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

我很确定我需要在我的 SWIG 接口文件 (interface.i) 中制作一个类型映射,但我发现 SWIG 文档难以理解。

我认为我需要做的是创建一个类型映射,每当它看到 track_t** 类型时,它就会接受 track_t* 并将其转换为其地址,类似于:

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

但我只是在运行时遇到分段错误:

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

在 python 方面。

我觉得我几乎解决了这个问题,但不能完全正确地获得类型映射规范,同时努力理解相关文档。

柔印 - 如果你在外面 - 也许你可以对此有所了解,你似乎是该领域的 SO 专家..

更新 - m7ython(太棒了!另一位关于 SO 的 SWIG 专家)

在 C 中的使用非常简单

声明并初始化一个指向 NULL 的 track_t 指针:

track_t *hash_tracks = NULL;

然后:

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

所以指向track_t 的指针的地址作为arg 传递给update_tracks()update_tracks() fn 负责为放入 hash_tracks 的数据处理所有必要的 malloc,即 track_t 结构的哈希表

所有其他参数都是单指针,我可以在 python 端创建和填充它们而没有问题。

track_t 是一个包含一堆整数、浮点数、char* 等的结构体。例如

typedef struct

/* make struct hashable */
UT_hash_handle hh;

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

track_t 参数是 track_t** 而不仅仅是 track_t* 的原因是因为 hash_tracks 是指向哈希表的指针(使用 UTHash 库)。 hash_tracks 指向哈希表中的第一个 track_t。在 update_tracks() 的主体中,可以从哈希表中添加/删除 track_t 结构,这样指向第一个 track_t 的指针可能会改变,即 hash_tracks 可能会在调用 update_tracks() 后指向其他内容,因此将指针传递给指针的原因。

换句话说,track_t** arg,phash_tracks 被用作输入和输出类型 arg,因此是指向指针的指针。所有其他 args 只是输入,它们不会更改,因此可以作为单个指针传入。

我使用以下 C fn 尝试了“helper fn”路线:

track_t** make_phash_tracks(void)

    track_t **phash_tracks;

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

    return phash_tracks;

使用 calloc 应确保 *phash_tracks 为 NULL

这个编译和包装没有错误,但是当我从 python 端使用它时,它出现了段错误,例如

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

在调用update_tracks 之前检查phash_tracks 变量:

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

【问题讨论】:

我不了解 SWIG,但看看函数调用,你确定你正确传递了双指针吗?看来您传递的不是tracks 的地址,而是值本身。 TBH 如果您不了解 SWIG,您很可能无法获得太多启示。类型映射正在尝试将轨道指针转换为双指针。我相信它会被合并到自动生成的包装器代码中 你能用 C 语言举例说明你将如何使用update_tracks 吗?目前,您的类型映射将track_t** 传递给未初始化的track_t *,并且在函数完成后不使用track_t**。如果您可以使用 C 库获得 track_t**,那么 SWIG 应该将其包装起来,您可以简单地将其传递给 update_tracks 函数(没有任何类型映射)。也许你可以用 C 写一个辅助函数:track_t** make_phash_tracks(track_t* tracks) return &amp;tracks; 我一直在尝试构建相同的接口,但使用的是 java 而不是 python - 这应该可行吗?我收到很多编译时错误,诸如 SWIG_ConvertPtr、SWIG_POINTER_OWN、SWIG_POINTER_DISOWN 之类的东西未声明 我还注意到,当 java 是目标时,%append_output 在输出 c 包装器代码中显示为“未替换” - 这不可能是正确的.. 【参考方案1】:

编辑:好的,我想我现在明白 update_tracks 做了什么。看来您可以通过两种方式使用该功能。要么更新现有的tracks,要么创建tracks(如果您将指针传递给NULL 指针)。我不确定在 SWIG 中处理这两种情况的最优雅方式(或者这甚至是一个问题),但这里有一些选项。

1。 phash_tracks 是一个输出参数

首先,您必须*phash_tracks作为返回值传回Python,并以某种形式使用函数,例如

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

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

这是通过以下“argout”类型映射完成的:

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

也许您不希望 Python 拥有 track_t* 的所有权,然后将 SWIG_POINTER_OWN 替换为 0

2。传递一个空的phash_tracks

如果您只想使用update_tracks 函数创建 tracks,您基本上可以做您已经在做的事情。使用以下“in”类型映射,并使用上面第二个示例中的函数(不带tracks 参数)。

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

3。 phash_tracks 作为输入(和输出)参数

如果你想使用update_tracks 来更新existing tracks,你应该可以使用我之前建议的“in”类型映射,并像第一个一样使用 Python 中的函数示例(包括tracks 参数)。

%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;

请注意,Python 不承认其tracks_t* 很重要。

4。启用上述 (2) 和 (3)

你基本上也可以使用版本 (3) 来创建 tracks,如果你可以痛饮传递一个包装好的NULL tracks_t*。我不确定 SWIG 是否允许这样做——但也许确实允许。尝试使用辅助函数:

tracks_t* empty_tracks()  return NULL; 

或者,您可以按照以下几行修改“in”类型映射,尝试将提供的参数转换为 track_t* 并传递其地址,或者传递 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;

然后,从 Python 中,只需传递其他内容即可创建 tracks

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

【讨论】:

谢谢,我已经用一些额外的细节更新了 OP 如果我们试图将 track_t* 轨道 arg 转换为 track_t**,在 'in' 类型映射中是否应该有一个 '&'?即我们正在尝试获取曲目的地址.. 是的,可能需要临时的track_t* tracks。我添加了它,并用更多解释更新了我的答案。此外,您可以查看生成的包装器代码以了解究竟发生了什么(如果您了解类型映射的基本作用,您将能够阅读代码)。 除了“in”类型映射外,在所有情况下都需要“argout”类型映射。你是这样用的吗?否则该函数只返回一个int,这是不可迭代的。 正确地进行内存管理也是一个问题,但从长远来看很难做到,因为这实际上取决于您如何使用函数以及内存在何处分配和释放。可能,您永远不希望 Python 拥有代表哈希表的 tracks_t*,因此最好在 argout 类型映射中将 SWIG_POINTER_OWN 替换为 0。对于您的辅助函数,如果您确实希望 Python 获得所有权,请使用 %newobject 指令。此外,您可以使用.thisown.own().disown() 从 Python 中检查和设置所有权。很高兴这些提示有助于让它为你工作:-)

以上是关于如何为双指针结构参数应用 SWIG 类型映射的主要内容,如果未能解决你的问题,请参考以下文章

SWIG Python - 包装一个需要指向结构的双指针的函数

SWIG:无法使用双指针访问构造函数

c中结构指针内的双数组

如何为类类型创建 OUTPUT 类型映射?

指针数组 (*A)[] 和双指针 **A 之间的区别

指针 && 双指针