Подтвердить что ты не робот

Карта STL в Perl с использованием SWIG

Это дубликат моего вопроса в списке рассылки SWIG.

Я пытаюсь использовать stl-контейнеры в своих привязках SWIG. Все работает отлично, за исключением обработки stl-карт в Perl. На стороне С++ у меня есть

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 script я могу вызвать 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
4b9b3361

Ответ 1

Я помещаю вашу С++-функцию в заголовочный файл как встроенную функцию для тестирования.

Тогда я смог построить интерфейс SWIG, который делает то, что вы ищете. Он имеет две ключевые части. Во-первых, я написал типовую карту, которая позволит указать либо std::map, либо хеш perl в качестве входных данных для функций С++, которые ожидают std::map. В случае последнего он создает временную карту из хеша perl для использования в качестве аргумента. (Что удобно, но потенциально медленно). Карта типов выбирает правильное поведение, проверяя, на что она была фактически передана.

Вторая часть решения состоит в том, чтобы сопоставить некоторые функции членов карты С++ с специальными функциями, которые perl использует для перегрузки операций при хэшах. Большинство из них реализованы просто с помощью %rename, где функции С++ и perl-функции совместимы, однако FIRSTKEY и NEXTKEY плохо отображаются на итераторах С++, поэтому они были реализованы с использованием %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-гуру любыми средствами, и я не использовал API C, поэтому некоторая осторожность в этой области была бы в порядке.

Ответ 2

Вы пробовали:

print Dumper stl::TryMap(('a' => '4'));