2012-06-22 89 views
31

這是我的question on SWIG mailing list的重複。使用SWIG的Perl中的STL映射

我想在我的SWIG綁定中使用stl容器。除了在Perl中執行stl map處理之外,一切都很完美。在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; 
} 

痛飲配置這個樣子的

%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散列作爲其輸出。

我相信有一種方法可以解決這個問題,因爲如果我使用stl向量和字符串而不是地圖,SWIG可以很好地解決Python中甚至Perl中的這個特殊問題。

有什麼辦法可以在SWIG中用Perl處理stl map?我正在使用最新的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 
+1

您的Perl代碼正在將引用傳遞給散列。我根本不瞭解SWIG,但我認爲這很重要。如果您將C++端改爲期望指針會發生什麼? –

+0

我有TryVector是類似於TryMap(期望參考),我使用它像'打印Dumper stl :: TryVector([1,2]);''和一切工作正常 – alexanderkuk

+2

我只注意到一個錯字:**'stl :: TryMap({'a'=>'4});'**你的原始代碼是否有這樣的? –

回答

-1

你試過:

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

爲什麼是-1?好的,我不熟悉SWIG界面,但是我提供了一個語法建議。 – dwright

1

我把你的C++函數爲頭文件作爲測試一個內聯函數。

我然後能夠構建一個SWIG接口,它可以完成您正在尋找的任務。它有兩個關鍵部分。首先,我寫了一個類型映射,它允許將std::map,作爲perl散列作爲輸入給期望std::map的C++函數。在後者的情況下,它會根據perl散列構建一個臨時映射來用作參數。 (這很方便但可能很慢)。 typemap通過檢查實際傳入的內容來選擇正確的行爲。

該解決方案的第二部分是將一些C++映射的成員函數映射到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"; 

從理論上講,你可以編寫一個out typemap來從每個函數調用返回時自動設置這個關係,但到目前爲止,我還沒有成功編寫一個兼容捆綁和SWIG運行時類型系統的類型映射。

應該指出,這不是生產就緒代碼。內部映射有一個線程安全問題,我也知道一些錯誤處理也不存在。我還沒有完全測試所有來自Perl方面的散列操作,超出了上面所看到的。通過與宏進行交互也可以使它更通用。最後,我不是一個perl大師,我也沒有使用過C API,所以在這個領域的一些小心會是有序的。

+0

我甚至不相信有一種解決方法[在Perl中,即使使用C API,我怎樣才能從子例程返回一個並列哈希?](http://stackoverflow.com/q/4047940)。 – Flexo