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.