我有一个C API,它使用字符串的输出参数。 (真正的签名已经改变,以保护无辜。这是一个简单的例子。)
void func( char* buf, size_t buflen, char* buf2, size_t buf2len );
buf
和buflen
是有效的输出参数,其中buflen
和buf2len
是(已经)分配的那些缓冲区的大小。
在调用代码中,我不想传递任何参数。相反,我希望返回字符串。
result1,result2 = func()
我宁愿不将缓冲区/大小传递给包装器函数,而是将它由包装器分配,转换为Python字符串,并在返回Python字符串之前取消分配。
我看到的与此相关的大多数cstring.i类型图要求我给包装函数一个字符串。分配类型图都需要char**
。
我正在寻找类似于使用OUTPUT
作为outparam名称的行为,但缓冲区/大小对是(单个)outparam。
我没有权力改变API。我只是想让它易于使用。
是否已经有了这个类型图,或者你可以帮我构建一个吗?
我得到了它的功能(没有性能或内存使用测试)。
%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特定的修复。
由于您特别要求提供不需要任何语言支持的解决方案,我建议使用%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 buf1
和buf2
数组进行更多的工作,以使Python用户更自然,但可以使用结构上的carrays.i或%extend
将其全部保存在C / SWIG而不是Python特定的。
您还可以使用多参数类型映射执行与您正在包装的函数不相关的操作。为避免特定于Python,您可以使用%append_output
从一个函数返回多个项目。这对于Java或C#这样的静态类型语言不起作用,但它应该适用于大多数/所有动态类型的语言。
为了进一步避免需要额外的语言特定代码,我们可以使用carrays.i,它为我们定义的每种类型生成一些额外的函数。特别是我们使用ByteArray_cast
,new_ByteArray
和delete_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
这里的整洁界面没有很多选择,因为您的要求非常严格:
考虑到这两个因素并没有多少可供使用。
就个人而言,我倾向于编写一些Python特定的C,如果它使Python用户看到的界面更自然,即使它意味着相同的界面不能100%重复另一种语言。这里有一些更简洁的解决方案,可用于额外的语言特定工作,通常可以获得回报。
我认为SWIG的强大功能不是“一次编写,随处导入”,而是帮助您抽象和模块化直观界面的语言特定部分。