SWIG类型图以返回输出参数char * / size_t

时间:2019-04-24 04:06:42

标签: python swig

我有一个C API,它对字符串使用输出参数。 (真实的签名已更改为保护无辜者。这是一个简单的示例。)

void func( char* buf, size_t buflen, char* buf2, size_t buf2len );

bufbuflen是有效的输出参数,其中buflenbuf2len是这些缓冲区(已经)分配的大小。

在调用代码中,我不需要传递任何参数。相反,我希望返回字符串。

result1,result2 = func()

我不希望将缓冲区/大小传递给包装器函数,而是由包装器分配缓冲区/大小,将其转换为Python字符串,然后在返回Python字符串之前取消分配。

我看到的与此相关的大多数cstring.i类型映射都要求我给包装函数提供一个字符串。所有的分配类型图都需要一个char**

我正在寻找与使用OUTPUT作为outparam名称类似的行为,但是缓冲区/大小对是(单个)outparam。

我无权更改API。我只是想使其易于使用。

已经有用于此的类型映射,或者您可以帮助我构造一个吗?

试验1(仅适用于Python)

我让它起作用(不测试性能或内存使用情况)。

%typemap(in,numinputs=0)(char* mutable_buffer, size_t mutable_buffer_size) {
    $1 = malloc(sizeof(char)*4096);
    $1[0] = 0x0;
    $2 = 4096;
}
%typemap(argout)(char* mutable_buffer, size_t mutable_buffer_size) {
#ifdef SWIGPYTHON
    PyObject *o;
    $1[4095] = 0x0; // null-terminate it, just in case
    o = PyUnicode_FromString($1);
    resultobj = SWIG_Python_AppendOutput(resultobj,o);
#endif
}
%typemap(freearg)(char* mutable_buffer, size_t mutable_buffer_size) {
    free($1);
}

我宁愿不使用特定于lang的修补程序来解决此问题。

1 个答案:

答案 0 :(得分:1)

由于您特别要求的解决方案不需要特定的语言支持,因此建议您使用%inline提供另一种形式的func,该语法具有您喜欢的语法。这样的事情应该做到:

%module test

%rename(func) func_adjusted; 
%newobject func_adjusted();

%inline %{
  struct func1_out {
    // can make tuple syntax work here if you would rather, but likely having names is clearer anyway
    char buf1[4096];
    size_t buf1len;
    char buf2[4096];
    size_t buf2len;
  };

  // with the rename we pretend this is the C function, but really it is specific to our wrapper module.
  struct func1_out *func_adjusted() {
    struct func1_out *ret = malloc(sizeof *ret);
    // could also dynamically allocate buf1 and buf2 instead of fixed max size.
    func(buf1, &buf1len, buf2, &buf2len);
    return ret;
  }
%}

%ignore func;
%include "blahblah.h"

您可能想对结构中的char buf1buf2数组做更多的工作,以使其对Python用户更加自然,但这可以使用carrays完成。在结构上使用i或%extend,以将其全部保留在C / SWIG中,而不是仅在Python中保留。


您还可以使用多参数类型映射来执行与您要包装的函数无关的操作。为了避免特定于Python,您可以使用%append_output从一个函数返回多个项目。不过,这不适用于Java或C#等静态类型的语言,但应适用于大多数/所有动态类型的语言。

为了进一步避免需要额外的语言特定代码,我们可以使用carrays.i,它会为我们定义的每种类型生成一些额外的功能。特别是,我们使用ByteArray_castnew_ByteArraydelete_ByteArray处理可能遇到的大量情况。这适用于C和C ++情况。

%module test

%include <carrays.i>
%array_class(char, ByteArray);

%typemap(in,numinputs=0) (char* mutable_buffer, size_t mutable_buffer_size) (ByteArray *tmp=NULL) {
    // N.B. using new_ByteArray() here makes this work well with both C and C++ code
    tmp = new_ByteArray(4096);
    $1 = ByteArray_cast(tmp);
    $1[0] = 0x0;
    $2 = 4096;
}

%typemap(freearg) (char* mutable_buffer, size_t mutable_buffer_size) {
    // conditional is needed here as in some cases delete_ByteArray would dereference a null pointer
    if (tmp$argnum) delete_ByteArray(tmp$argnum);
}

%typemap(argout) (char* mutable_buffer, size_t mutable_buffer_size) {
   // Take ownership from in typemap
   %append_output(SWIG_NewPointerObj(SWIG_as_voidptr(tmp$argnum), $descriptor(ByteArray*), SWIG_POINTER_NEW));
   tmp$argnum = NULL;
}

%apply (char *mutable_buffer, size_t mutable_buffer_size) { (char *buf, size_t buflen), (char *buf2, size_t buf2len) };

%inline %{
void func(char* buf, size_t buflen, char* buf2, size_t buf2len) {
  strncpy(buf, "this is buffer1", buflen);
  strncpy(buf2, "this is buffer2", buf2len);
}
%}

这在我的测试中可以正常使用Python 3.7。请注意,您需要使用-builtin参数运行swig以获得您在此处查找的确切行为,或者需要其他用户代码来解决:

a,b = test.func()
# needed for the non builtin case, I can't see a way to avoid that without Python specific code
aa=test.ByteArray_frompointer(a)
# for the -builtin case it just works though

这里整洁的界面没有太多选择,因为您的要求非常严格:

  1. 不求助于特定于lang的修复程序。
  2. 我无权更改API。我只想使其易于使用。

考虑到这两个因素,可以使用的东西并不多。

就我个人而言,尽管我倾向于编写一些特定于Python的C,即使它会使Python用户看到的界面更加自然,即使这意味着同一界面不能以另一种语言进行100%复制。对于一些额外的特定于语言的工作,这里有很多更整洁的解决方案,通常会奏效。

我认为SWIG的功能不是“写一次就可以导入到任何地方”,而是可以帮助您抽象化和模块化直观界面的特定于语言的部分。