Search code examples
c++perlvectordoubleswig

Wrapper generator SWIG (C++/Perl): How to access "blessed" objects in a 1d vector<double>in Perl?


I have written a C++ library to extract simulation data (= simple vectors with (x,y) or (x,y,z) components) from electronic design automation (EDA) tools. More concrete, this data represents electrical signals for different points in time.

The C++ library offers several methods. Two important ones are:

std::vector<std::string> getSignalNames() // Returns all signal names in the file
std::vector<std::vector<double>> getSignals() // Returns the actual data as M x N matrix (with M rows and N columns)

Using the library in C++ works perfectly and yields the expected results, e.g.:

getSignalNames():
  Signal1
  Signal2
getSignals():
  1 1 1 2
  2 1 2 3

Perl programmers asked me to also offer the library to them and I decided to use the wrapper generator SWIG to create bindings. I worked through the tutorial and I was able to successfully set up a minimal working example.

Based on the example, I wrote a complete SWIG interface file for the C++ library. The wrapper generation and build process works smoothly and I can also use getSignalNames() without any problems:

// Perl snippet to read out signal names
my $parserPointer = new waveformparser::ScopeParser("input.file");
$signalNames = $parserPointer->getSignalNames();

foreach my $signalName ( @$signalNames ) {
    print "$signalName\n";
}
// Output:
Signal1
Signal2

But, I ran into trouble when using the return value from getSignals():

// Perl snippet to read out the actual signal data
my $parserPointer = new waveformparser::ScopeParser("input.file");
$signalData = $parserPointer->getSignals();

foreach my $rowAsHashRef ( @$signalData ) {
    print "reftype: " . reftype($rowAsHashRef) . "\n";
    print "keys: " . keys(%$rowAsHashRef) . "\n"
}
// Output:
reftype: HASH
keys: 0
reftype: HASH
keys: 0

As you see, each row is represented as hash in Perl, but there are no keys in the Hash. Nevertheless, when using Perl's Data::Dumper, I can see the correct data type for each row:

my $parserPointer = new waveformparser::ScopeParser("input.file");
$signalData = $parserPointer->getSignals();
print Dumper $signalData;
// Output:
$VAR1 = [
          bless( {}, 'waveformparser::vector_1d_double' ),
          bless( {}, 'waveformparser::vector_1d_double' )
];

I.e., according to the data dumper, each row consists of several columns (i.e., 'waveformparser::vector_1d_double') which are defined in the SWIG interface file as following:

...
%include "std_vector.i"
%template(vector_1d_double) std::vector<double>;
%template(vector_2d_double) std::vector<std::vector<double>>;
...

My question is now: How can I access elements of this "blessed" (wrapped) vector_1d_double objects in Perl?

I thought, SWIG would provide convenient access methods for such objects. I.e., the underlying C++ data type is just a simple 1d vector of doubles (std::vector<double>).


Solution

  • You need to write an output typemap for std::vector<std::vector<double>> to convert to a proper Perl array of arrays. Here is an example:

    VecVec.i:

    %module VecVec
    %typemap(out) std::vector<std::vector<double>> {
        AV *array = (AV *) newAV();
        auto vec_ptr = &$1;
        std::vector<std::vector<double>>& vec = *vec_ptr;
        for (const auto &item : vec) {
            AV *subarray = (AV *) newAV();
            for (const auto &subitem : item) {
                av_push(subarray, newSVnv((NV) subitem));
            }
            av_push(array, (SV*) newRV_noinc((SV*)subarray));
        }
        $result = newRV_noinc((SV*) array);
        sv_2mortal($result);
        argvi++;
    }
    %{
        #include "VecVec.h"
    
    %}
    %include "VecVec.h"
    

    VecVec.h:

    #ifndef VEVEC_H_
    #define VECVEC_H_
    #include <vector>
    
    class VecVec
    {
    public:
        VecVec() {}
        ~VecVec() {}
        std::vector<std::vector<double>> getSignals();
    private:
    };
    #endif
    

    VecVec.cpp:

    #include <string>
    #include <vector>
    #include <iostream>
    #include "VecVec.h"
    
    std::vector<std::vector<double>> VecVec::getSignals()
    {
        std::vector<std::vector<double>> vec {
            {1, 2, 3},
            {4, 5, 6}
        };
        return vec;
    }
    

    Then compile with:

    perl_include_dir=$(perl -MConfig -e'print $Config{archlib}')"/CORE"
    swig -perl5 -c++ -I/usr/include VecVec.i
    g++ -fPIC -c VecVec.cpp
    g++ -I${perl_include_dir} -c -fPIC -g -o VecVec_wrap.o VecVec_wrap.cxx
    g++ -shared -L. VecVec.o VecVec_wrap.o -o VecVec.so
    

    and test the module with test.pl:

    use strict;
    use warnings;
    use Data::Dumper qw(Dumper);
    use lib '.';
    use VecVec;
    
    my $p = VecVec::VecVec->new();
    my $sig = $p->getSignals();
    print Dumper($sig);
    

    Output:

    $VAR1 = [
              [
                '1',
                '2',
                '3'
              ],
              [
                '4',
                '5',
                '6'
              ]
            ];