我试图使用stl容器在我的SWIG绑定。一切正常工作除了stl地图处理在Perl。在C方面,我有
std::map<std::string,std::string> TryMap(const std::map<std::string,std::string> &map) { std::map<std::string,std::string> modified(map); modified["7"] = "!"; return modified; }
SWIG配置看起来像这样
%module stl %include "std_string.i" %include "std_map.i" %template(StringStringMap) std::map<std::string,std::string>; %{ #include "stl.h" %} %include "stl.h"
在我的Python脚本中,我可以这样调用TryMap
print dict(stl.TryMap({'a': '4'}))
并获得美丽的输出
{'a': '4','7': '!'}
但是在Perl我调用
print Dumper stl::TryMap({'a' => '4'});
并得到一个错误
TypeError in method 'TryMap',argument 1 of type 'std::map< std::string,std::string > const &' at perl.pl line 7.
我可以做一些事情
my $map = stl::TryMap(stl::StringStringMap->new()); print $map->get('7');
并获得’!’,但这不是一个选项,因为有很多遗留代码使用“TryMap”,期望正常的Perl哈希作为其输出。
我相信有一种方法工作,这是因为SWIG解决这个特别的问题很好地在Python和甚至在Perl如果我使用stl向量和字符串,但不是地图。
有什么办法来处理stl地图与Perl在SWIG?我使用最新的SWIG 2.0.7
UPDATE也许有一些问题perl5 / std_map.i。它太短=)
$ wc -l perl5/std_map.i python/std_map.i 74 perl5/std_map.i 305 python/std_map.i
解决方法
然后,我能够构建一个SWIG界面,做你正在寻找的。它有两个关键部分。首先,我写了一个typemap,它将允许std :: map或perl哈希作为输入到期望std :: map的C函数。在后者的情况下,它从perl哈希构建临时映射以用作参数。 (这是方便,但可能较慢)。 typemap通过检查实际传入的内容来选择正确的行为。
解决方案的第二部分是将一些C map的成员函数映射到perl用于在哈希上重载操作的特殊函数。大多数这些是简单地用%rename实现,其中C函数和perl函数是兼容的,但是FIRSTKEY和NEXTKEY不很好地映射到C的迭代器,因此这些是使用%extend和(内部)另一个std :: map实现的存储我们包装的地图的迭代状态。
这里没有实现用于返回映射的特殊类型映射,然而通过现在实现的特殊操作存在额外的行为。
SWIG界面如下所示:
%module stl %include <std_string.i> %include <exception.i> %rename(FETCH) std::map<std::string,std::string>::get; %rename(STORE) std::map<std::string,std::string>::set; %rename(EXISTS) std::map<std::string,std::string>::has_key; %rename(DELETE) std::map<std::string,std::string>::del; %rename(SCALAR) std::map<std::string,std::string>::size; %rename(CLEAR) std::map<std::string,std::string>::clear; %{ #include <map> #include <string> // For iteration support,will leak if iteration stops before the end ever. static std::map<void*,std::map<std::string,std::string>::const_iterator> iterstate; const char *current(std::map<std::string,std::string>& map) { std::map<void*,std::string>::const_iterator>::iterator it = iterstate.find(&map); if (it != iterstate.end() && map.end() == it->second) { // clean up entry in the global map iterstate.erase(it); it = iterstate.end(); } if (it == iterstate.end()) return NULL; else return it->second->first.c_str(); } %} %extend std::map<std::string,std::string> { std::map<std::string,std::string> *TIEHASH() { return $self; } const char *FIRSTKEY() { iterstate[$self] = $self->begin(); return current(*$self); } const char *NEXTKEY(const std::string&) { ++iterstate[$self]; return current(*$self); } } %include <std_map.i> %typemap(in,noblock=1) const std::map<std::string,std::string>& (void *argp=0,int res=0,$1_ltype tempmap=0) { res = SWIG_ConvertPtr($input,&argp,$descriptor,%convertptr_flags); if (!SWIG_IsOK(res)) { if (SvROK($input) && SvTYPE(SvRV($input)) == SVt_PVHV) { fprintf(stderr,"Convert HV to map\n"); tempmap = new $1_basetype; HV *hv = (HV*)SvRV($input); HE *hentry; hv_iterinit(hv); while ((hentry = hv_iternext(hv))) { std::string *val=0; // TODO: handle errors here SWIG_AsPtr_std_string SWIG_PERL_CALL_ARGS_2(HeVAL(hentry),&val); fprintf(stderr,"%s => %s\n",HeKEY(hentry),val->c_str()); (*tempmap)[HeKEY(hentry)] = *val; delete val; } argp = tempmap; } else { %argument_fail(res,"$type",$symname,$argnum); } } if (!argp) { %argument_nullref("$type",$argnum); } $1 = %reinterpret_cast(argp,$ltype); } %typemap(freearg,std::string>& { delete tempmap$argnum; } %template(StringStringMap) std::map<std::string,std::string>; %{ #include "stl.h" %} %include "stl.h"
然后我调整你的样本perl测试:
use Data::Dumper; use stl; my $v = stl::TryMap(stl::StringStringMap->new()); $v->{'a'} = '1'; print Dumper $v; print Dumper stl::TryMap({'a' => '4'}); print Dumper stl::TryMap($v); foreach my $key (keys %{$v}) { print "$key => $v->{$key}\n"; } print $v->{'7'}."\n";
我能够成功运行:
Got map: 0x22bfb80 $VAR1 = bless( { '7' => '!','a' => '1' },'stl::StringStringMap' ); Convert HV to map a => 4 Got map: 0x22af710 In C++ map: a => 4 $VAR1 = bless( { '7' => '!','a' => '4' },'stl::StringStringMap' ); Got map: 0x22bfb20 In C++ map: 7 => ! In C++ map: a => 1 $VAR1 = bless( { '7' => '!','stl::StringStringMap' ); 7 => ! a => 1 !
您还可以将此对象绑定到散列,例如:
use stl; my $v = stl::TryMap(stl::StringStringMap->new()); print "$v\n"; tie %foo,"stl::StringStringMap",$v; print $foo{'a'}."\n"; print tied(%foo)."\n";
理论上,你可以编写一个输出类型来在每次函数调用返回时自动设置这个关系,但是到目前为止,我还没有成功编写一个适用于绑定和SWIG运行时类型系统的类型。
应该注意,这不是生产就绪代码。有一个线程安全问题的内部地图和一些错误处理丢失,我知道。我还没有完全测试所有的哈希操作工作从perl一边超出你上面看到的。通过与swig_map_common宏交互,使它更通用也是很好的。最后,我不是一个perl guru的任何方式,我没有使用C API,所以在这方面的一些谨慎是顺序。