使用SWIG在Perl中实现STL map

32

这是我在SWIG邮件列表中的重复问题

我试图在我的SWIG绑定中使用STL容器。除了Perl中的STL映射处理外,一切都运行完美。在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向量和字符串,但不支持哈希表。

是否有办法在SWIG中处理Perl的stl map?我正在使用最新的SWIG 2.0.7。

更新 可能perl5/std_map.i有问题。它太短了 =)

$ wc -l perl5/std_map.i python/std_map.i 
   74 perl5/std_map.i
  305 python/std_map.i

1
你的Perl代码传递了一个哈希引用。我完全不了解SWIG,但我认为这很重要。如果你将C++端改为期望一个指针,会发生什么? - Sinan Ünür
我有一个类似于TryMap(期望引用)的TryVector,我像这样使用它:'print Dumper stl :: TryVector([1,2]);',一切正常。 - alexanderkuk
2
我刚刚注意到一个拼写错误:**stl::TryMap({'a' => '4});** 你的原始代码里有这个吗? - Sinan Ünür
1
你更喜欢高效还是直观易懂?虽然可以编写一些能够实现你想要的功能的代码,但这意味着每次调用都需要创建、填充和返回一个新的哈希表,而使用 ->{'key'} 语法仍然可以接受哈希表作为输入,而不需要大量复制。如果你特别关心 Data::Dumper 的工作方式,我认为可以自定义它,但我以前没有这样做过。 - Flexo
你尝试过在C++中将函数参数的const关键字移除吗? - Alex Cohn
1个回答

2

我将你的C++函数作为内联函数放入头文件中进行测试。

然后,我成功构建了一个SWIG接口,完成了你所需要的功能。它有两个关键部分。首先,我编写了一个类型映射,允许将std::map或Perl哈希表作为输入提供给期望std::map的C++函数。在后者的情况下,它会从Perl哈希表中构建临时映射,并将其用作参数。(这很方便,但可能很慢)。类型映射通过检查实际传入的内容来选择正确的行为。

解决方案的第二部分是将一些C++ map的成员函数映射到Perl用于重载哈希操作的特殊函数上。其中大多数都是使用%rename简单实现的,其中C++函数和Perl函数是兼容的,但是FIRSTKEYNEXTKEY不易映射到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::map<std::string, 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", $symname, $argnum); }
  $1 = %reinterpret_cast(argp, $ltype);
}

%typemap(freearg,noblock=1) const std::map<std::string, 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' => '!',
                 'a' => '1'
               }, '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大师,并且我也没有经常使用C API,因此在这方面需要谨慎。

我甚至不确定即使使用C API也没有绕过在Perl中,如何从子例程返回一个绑定哈希?的方法。 - Flexo

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