首页 > 解决方案 > SWIG typemap to return an output param char*/size_t

问题描述

I have an C API that uses output params for strings. (The real signature has been changed to protect the innocent. This is a simplistic example.)

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

buf and buflen are effectively output parameters, where buflen and buf2len are the (already) allocated size of those buffers.

In the calling code, I don't want to have to pass in any parameters. Rather, I want the strings to be returned.

result1,result2 = func()

I would prefer not to pass the buffer/size to the wrapper function, but rather have it allocated by the wrapper, turned into a Python string, and deallocated before returning the Python string.

Most of the cstring.i typemaps I see related to this require me to give the wrapper function a string. The allocate typemaps all want a char**.

I'm looking for behavior similar to using OUTPUT as an outparam name, but the buffer/size pair are the (single) outparam.

I don't have power to alter the API. I just want to make it easy to use.

Is there already a typemap for this, or can you help me construct one?

Trial 1 (Python Only)

I got this to function (no test of performance or memory usage).

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

I would prefer to solve this problem without resorting to lang-specific fixes.

标签: pythonswig

解决方案


由于您特别要求提供不需要任何特定语言来支持的解决方案,因此我建议您使用它来提供具有您喜欢的语法%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 buf1andbuf2数组做更多的工作,以使其对 Python 用户更自然,但这可以使用 carrays.i 或%extend在结构上完成,以将其全部保存在 C/SWIG 中而不是 Python 特定的。


您还可以使用多参数类型映射做一些不特定于您要包装的函数的事情。为了避免特定于 Python,您可以使用%append_output从一个函数返回多个项目。虽然这不适用于 Java 或 C# 等静态类型语言,但它应该适用于大多数/所有动态类型语言。

为了进一步避免需要额外的、特定于语言的代码,我们可以使用 carrays.i,它为我们定义的每种类型生成一些额外的函数。特别是我们使用ByteArray_cast,new_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. 无需求助于特定于语言的修复程序。
  2. 我无权更改 API。我只是想让它易于使用。

考虑到这两个,没有多少可以使用。

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

我认为 SWIG 的力量不是“编写一次,随处导入”,而是帮助您抽象和模块化直观界面的语言特定部分。


推荐阅读