32 votes

Carte STL en Perl avec SWIG

C'est une copie de mon question sur la liste de diffusion SWIG .

J'essaie d'utiliser des conteneurs stl dans mes liaisons SWIG. Tout fonctionne parfaitement, sauf la gestion des cartes stl en Perl. Du côté C++, j'ai

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;
}

La configuration de SWIG ressemble à ceci

%module stl

%include "std_string.i"

%include "std_map.i"
%template(StringStringMap) std::map<std::string, std::string>;

%{
  #include "stl.h"
%}

%include "stl.h"

Dans mon script Python, je peux appeler TryMap de la façon suivante

print dict(stl.TryMap({'a': '4'}))

et obtenir une belle sortie

{'a': '4', '7': '!'}

mais en Perl, j'appelle

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

et obtenir une erreur

TypeError in method 'TryMap', argument 1 of type 'std::map< std::string,std::string > const &' at perl.pl line 7.

Je peux en fait faire quelque chose comme

my $map = stl::TryMap(stl::StringStringMap->new());
print $map->get('7');

et obtenir '!', mais ce n'est pas une option car il y a beaucoup d'anciens codes utilisant "TryMap" qui s'attendent à un hash Perl normal en sortie.

Je pense qu'il existe un moyen de résoudre ce problème, car SWIG résout ce problème particulier de manière satisfaisante en Python et même en Perl si j'utilise des vecteurs stl et des chaînes, mais pas des cartes.

Existe-t-il un moyen de gérer les cartes stl avec Perl dans SWIG ? J'utilise la dernière version de SWIG 2.0.7.

UPDATE Peut-être qu'il y a un problème avec perl5/std_map.i . C'est trop court =)

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

2voto

Flexo Points 39273

J'ai mis votre fonction C++ dans le fichier d'en-tête comme une fonction en ligne pour le test.

J'ai ensuite pu construire une interface SWIG qui fait ce que vous recherchez. Elle comporte deux parties essentielles. Tout d'abord, j'ai écrit une carte de type qui permet à l'un ou l'autre des éléments suivants d'entrer dans l'application std::map , o un hachage perl à donner en entrée aux fonctions C++ qui attendent un std::map . Dans le cas de ce dernier, il construit une carte temporaire à partir du hachage perl pour l'utiliser comme argument. (Ce qui est pratique mais potentiellement lent). La carte de type choisit le comportement correct en vérifiant ce qui a été réellement passé.

La deuxième partie de la solution consiste à faire correspondre certaines des fonctions membres de map C++ aux fonctions spéciales que perl utilise pour surcharger les opérations sur les hachages. La plupart de ces fonctions sont implémentées simplement avec %rename où la fonction C++ et les fonctions perl sont cependant compatibles FIRSTKEY y NEXTKEY ne s'adaptent pas bien aux itérateurs du C++, c'est pourquoi ils ont été implémentés en utilisant %extend et (en interne) un autre std::map pour stocker l'état d'itération des cartes que nous enveloppons.

Il n'y a pas de cartes types spéciales mises en œuvre ici pour renvoyer les cartes, mais il y a un comportement supplémentaire via les opérations spéciales qui sont maintenant mises en œuvre.

L'interface de SWIG se présente comme suit :

%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"

J'ai ensuite adapté votre exemple perl pour le tester :

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";

Que j'ai pu exécuter avec succès :

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
!

Vous pouvez également lier cet objet à un hash, par exemple :

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";

En théorie, vous pouvez écrire une carte de type out pour établir ce lien automatiquement au retour de chaque appel de fonction, mais jusqu'à présent, je n'ai pas réussi à écrire une carte de type qui fonctionne à la fois avec le lien et le système de type du runtime SWIG.

Il convient de noter que ce code n'est pas prêt pour la production. Il y a un problème de thread safety pour la carte interne et une gestion d'erreur manquante aussi à ma connaissance. Je n'ai pas non plus entièrement testé toutes les opérations de hachage du côté perl au-delà de ce que vous voyez ci-dessus. Il serait également intéressant de rendre le code plus générique, en interagissant avec la fonction swig_map_common macro. Enfin, je ne suis en aucun cas un gourou de Perl et je n'ai pas beaucoup utilisé l'API C. Une certaine prudence dans ce domaine serait donc de mise.

-1voto

dwright Points 312

Avez-vous essayé :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X