【问题标题】:SWIG typemap to return an output param char*/size_tSWIG 类型映射以返回输出参数 char*/size_t
【发布时间】:2019-04-24 04:06:41
【问题描述】:

我有一个使用字符串输出参数的 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 作为外参数名称的行为,但缓冲区/大小对是(单个)外参数。

我无权更改 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);
}

我更愿意在不求助于特定语言的修复的情况下解决这个问题。

【问题讨论】:

  • 如果你想要一个输出,它必须是char**,否则你不能修改指针。
  • @JensMunk 如果你传入一个先前分配的char* 和一个size_t 表明它有多大,函数可以在不分配它的情况下向它写入数据。
  • 你是对的,但这也是一个输入 - 一种处理输出的非常烦人的方式。如果以这种方式处理输出,则提供一个返回整数的函数是有意义的,这是用户在调用 func 之前需要分配的大小
  • 在这种情况下,作为包装维护者,我可以知道一些合理的界限来放置在字符串上。从客户端的角度来看(尤其是在脚本语言中),他们不想欺骗字符串大小等。如果我知道所有内容总是适合 4096 字节,那么客户端不需要告诉我如何可能很大,我可以返回一个字符串。
  • 在这种情况下,有很多选择。您可以包含 %include "carrays.i"%array_class(char, charArray).. 使用 carr = lib.charArray(4096) 分配一个 char 数组,然后简单地将其作为参数传递。

标签: python swig


【解决方案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 或 @987654326 完成@ 将其全部保存在 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. 无需求助于特定于语言的修复程序。
  2. 我无权更改 API。我只是想让它易于使用。

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

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

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

【讨论】:

  • 这样做的缺点是我必须为我要处理的每个 API 函数编写一个单独的函数。我正在寻找适用于所有类似(就超参数而言)API 函数的通用解决方案。根据 arg 类型和名称,类型映射似乎可以做到这一点,但没有任何预制的可以做我想要的。
  • 我给出的一般建议是,您只能从这 3 件事中选择 2 件事:直观的界面、语言可移植性、最少的重复样板代码。那就是说我会看看,看看是否还有其他可行的方法。在这种情况下,避免特定语言修复的目标非常困难,因为并非所有语言都可以动态返回元组对象。
  • @mojo 我添加了另一个更类似于您的类型图的示例。虽然与做 Python 特定的事情相比,我仍然不太喜欢它,而且它永远无法在没有额外支持的情况下适用于动态类型语言,但它可能是你能得到的最好的。
  • 如果可以的话,我会给你+2。这个答案非常有帮助(我的理解也是如此)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-06
  • 1970-01-01
  • 2019-02-06
  • 1970-01-01
  • 2016-11-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多