使用SWIG将二进制数据移动到/从Perl的方法

4

我希望能够轻松地在Perl和我的C++库之间移动二进制数据。

我创建了一个C++结构来处理binary_data:

struct binary_data {
    unsigned long length;
    unsigned char *data;
};

在我的SWIG接口文件中,我有以下内容:
%typemap(in) binary_data * (binary_data temp) {
    STRLEN len;
    unsigned char *outPtr;
    if(!SvPOK($input))
        croak("argument must be a scalar string");
    outPtr = (unsigned char*) SvPV($input, len);
        printf("set binary_data '%s' [%d] (0x%X)\n", outPtr, len, $input);
    temp.data = outPtr;
    temp.length = len;
    $1 = &temp;
}
%typemap(out) binary_data * {
  SV *obj = sv_newmortal();
  if ($1 != 0 && $1->data != 0 && $1->length > 0) {     
    sv_setpvn(obj, (const char*) $1->data, $1->length);
    printf("get binary_data '%s' [%d] (0x%X)\n", $1->data, $1->length, obj);
  } else {
    sv_setsv(obj, &PL_sv_undef);
    printf("get binary_data [set to undef]\n");
  }
  if( !SvPOK(obj) )
    croak("The result is not a scalar string"); 
  $result = obj;
}

我使用"ExtUtils::MakeMaker"来构建我的Perl模块,效果不错。
然后我运行下面的Perl测试脚本以确保二进制数据能够正确地从Perl字符串中设置/获取。
my $fr = ObjectThatContainsBinaryData->new();
my $data = "1234567890"; 
print ">>>PERL:swig_data_set\n"; 
$fr->swig_data_set($data);
print "<<<PERL:swig_data_set\n";
print ">>>PERL:swig_data_get\n"; 
my $rdata = $fr->swig_data_get();
print "<<<PERL: swig_data_get\n";
print "sent    :" . \$data . " len=" . length($data). " '$data'\n"
     ."recieved:". \$rdata.  " len=" . length($rdata). " '$rdata'\n";

现在结合使用C++和Perl的printf标准输出是:
>>>PERL:swig_data_set
set binary_data '1234567890' [10] (0x12B204D0)
<<<PERL:swig_data_set
>>>PERL:swig_data_get
get binary_data '1234567890' [10] (0x1298E4E0)
<<<PERL: swig_data_get
sent    :SCALAR(0x12b204d0) len=10 '1234567890'
recieved:SCALAR(0x12bc71c0) len=0 ''

那么为什么perl调用< strong>sv_setpvn< /strong>似乎失败或无法工作呢?我不知道为什么当我在perl中打印返回的二进制数据时,它显示为空标量,但是在SWIG C++嵌入式类型映射中看起来很好。 我正在使用:Perl v5.8.8 built for x86_64-linux-thread-multi、SWIG 2.0.1和gcc version 4.1.1 20070105 (Red Hat 4.1.1-52)。
4个回答

2
如果你要替换%typemap(out)中的以下行:
$result = obj;

带有

$result = obj; argvi++;  //This is a hack to get the hidden stack pointer to increment before the return

SWIG生成的代码现在将如下所示:
...
  ST(argvi) = obj; argvi++;
}
XSRETURN(argvi);
}

你的测试脚本会按预期返回Perl字符串。

SV = PV(0x1eae7d40) at 0x1eac64d0
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,POK,pPOK)
  PV = 0x1eb25870 "1234567890"\0
  CUR = 10
  LEN = 16
<<<PERL: swig_data_get
sent    :SCALAR(0x1ea64530) len=10 '1234567890'
recieved:SCALAR(0x1eac64d0) len=10 '1234567890'

你应该更仔细地阅读SWIG 2.0中关于在Perl中使用typemaps的文档:

"30.8.2 返回值

返回值被放置在每个包装器函数的参数堆栈上。参数堆栈指针的当前值包含在变量argvi中。每当添加新的输出值时,关键是要递增此值。对于多个输出值,argvi的最终值应该是输出值的总数。"


1
如果你不把它变成临时变量会怎样呢?我正在使用Inline::C进行测试(因为我从未使用过SWIG),将SV设置为临时变量会导致问题,因为Inline::C已经为我做了这个操作。也许SWIG使用了类似的设计?
两者都可以。
SV* obj = newSV(0);
sv_setpvn(obj, "abc", 3);

并且

SV* obj = newSVpvn("abc", 3);

曾经使用过Inline::C。


谢谢。我尝试使用这两种不同的构造方法来替换new_mortal调用,但仍然得到相同的结果。 - user297500

1

Swig提供了一个名为cdata.i的模块。 你应该把它包含在接口定义文件中。

一旦你包含了这个模块,它就会提供两个函数:cdata()memmove()。给定一个void *和二进制数据的长度,cdata()将其转换为目标语言的字符串类型。

memmove()则是相反的操作。给定一个字符串类型,它将把字符串内容(包括嵌入的空字节)复制到C void*类型中。

使用这个模块可以使处理二进制数据变得更加简单。

希望这正是你需要的。


0

在Perl方面,你能加一下吗?

use Devel::Peek;
Dump($fr->swig_data_get());

并提供输出结果吗?谢谢。


获取二进制数据 '1234567890' [10] (0x186BC4E0) <newline> 用法:Devel::Peek::Dump(sv, lim=4) 在 ./Test_swig_binary.t 的第20行。 - user297500
@user297500,好像根本没有返回任何东西。这怎么可能?!如果你把tarball上传到某个地方,我会试一下的。 - ikegami
1
$result = obj;//HACK: SWIG YOU ARE SUPPOST TO DO THIS, OR AT LEAST DOCUMENT IT // This is needed for the swig generated perl to function correctly. argvi++; //现在它可以工作了,感谢您的帮助。在“$result = obj”之后添加“argvi ++;”语句纠正了问题。我注意到所有使用SWIG创建的其他getter方法都将此语句放入包装器cxx代码中。 - user297500

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接