h2xs -A -n SimpleTest #### mkdir SimpleTest/SimpleLib #### #include #include #include using namespace std; typedef map StringMap; typedef StringMap::iterator StringMapIt; typedef vector StringVector; typedef StringVector::iterator StringVectorIt; class Simple { public: Simple(int myArg); int add(int myArg); std::string get_string(std::string str); StringMap getMap(StringMap myMap); StringVector getVec(StringVector myVec); protected: int myInt; }; #### #include "Simple.H" #include Simple::Simple(int myArg) { myInt = myArg; return; } int Simple::add( int myArg ) { return myInt + myArg; } std::string Simple::get_string( std::string str ) { std::string foo = "|" + str + "|"; return foo; } StringMap Simple::getMap(StringMap myMap) { myMap["Hello"] = "world"; return myMap; } StringVector Simple::getVec(StringVector myVec) { myVec.push_back("abc"); myVec.push_back("123"); return myVec; } #### use ExtUtils::MakeMaker; ( $CC, $Verbose ) = ( 'g++', 1 ); # define g++ as your CPP compiler WriteMakefile( NAME => 'Simple::SimpleLib', # the name of the library we're building SKIP => [qw(all static static_lib dynamic dynamic_lib)], clean => {'FILES' => 'libSimpleLib$(LIB_EXT) unitTests'}, # when you run make clean, it'll delete the shared library and the binary too. CC => $CC, # Our CPP compiler LD => '$(CC)', CCFLAGS => '-fPIC', # this instructs the CPP compiler to use "Position independence", basically a shared library, more details here # http://www.fpx.de/fp/Software/tcl-c++/tcl-c++.html ); # Ok, this "MY::top_targets" command isn't even mentioned in the pod for ExtUtils::MakeMaker. # Just understand that it is a method that allows you to modify the targets that the makefile will build. # (when you type just "make", you're really invoking "make all"). # You can see that "all" points to static, which points to libSimpleLib.a, which actually does the real work. # O_FILES refers to the object files that are created when the ".c" files are compiled. # You can see what is going on here when you actually run "make" # g++ -c -fPIC -O2 -march=nocona -mmmx -msse -msse2 -msse3 -DVERSION=\"\" -DXS_VERSION=\"\" Simple.C # g++ -c -fPIC -O2 -march=nocona -mmmx -msse -msse2 -msse3 -DVERSION=\"\" -DXS_VERSION=\"\" unitTests.C # ar cr libSimpleLib.a Simple.o unitTests.o # : libSimpleLib.a # "ar" is an archiving program that takes object files and packages them into archives. It reminds me a lot of tar, except minus the raw device related stuff. # When you run "make bin" it takes the object files created when we ran "make" and links them to create the binary unitTests (since unitTests has a "main" function defined). # g++ Simple.o unitTests.o -o unitTests sub MY::top_targets { ' all :: static pure_all :: static static :: libSimpleLib$(LIB_EXT) # These must be indented with tabs!!! libSimpleLib$(LIB_EXT): $(O_FILES) $(AR) cr libSimpleLib$(LIB_EXT) $(O_FILES) $(RANLIB) libSimpleLib$(LIB_EXT) # Tab indent these lines bin: $(O_FILES) $(LD) $(O_FILES) -o unitTests '; } #### #include "Simple.H" #include #include int main( int argc, char * argv[] ) { Simple s = Simple(5); std::cout << s.add(9) << std::endl; std::string moo = "abc"; std::string foo = s.get_string(moo); std::cout << foo << std::endl; StringMap m; m["qqq"] = "uuu"; StringMap n = s.getMap(m); std::cout << "n[qqq] " << n["qqq"] << std::endl; StringVector v; v.push_back("999"); StringVector w = s.getVec(v); std::cout << "w[0] " << w[0] << std::endl; std::cout << "w[1] " << w[1] << std::endl; std::cout << "w[2] " << w[2] << std::endl; return 0; } #### perl Makefile.PL make make bin #### ./unitTests #### 14 |abc| #### use 5.008003; use ExtUtils::MakeMaker; $CC = 'g++'; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( NAME => 'Simple', VERSION_FROM => 'lib/Simple.pm', # finds $VERSION PREREQ_PM => {}, # e.g., Module::Name => 1.1 CC => $CC, LD => '$(CC)', ($] >= 5.005 ? ## Add these new keywords supported since 5.005 (ABSTRACT_FROM => 'lib/Simple.pm', # retrieve abstract from module AUTHOR => 'dextius ') : ()), LIBS => [''], # e.g., '-lm' DEFINE => '', # e.g., '-DHAVE_SOMETHING' INC => '-I.', # e.g., '-I. -I/usr/include/other' # OBJECT => '$(O_FILES)', # link all the C files too # Un-comment this if you add C files to link with later: # target the shared library we just created "MYEXTLIB" tells make to use this shared library. 'MYEXTLIB' => 'SimpleLib/libSimpleLib$(LIB_EXT)', ); # (THE INDENTION on "cd SimpleLib..." MUST BE A TAB) sub MY::postamble { ' $(MYEXTLIB): SimpleLib/Makefile cd SimpleLib && $(MAKE) $(PASSTHRU) '; } #### # Ahh now we are getting to the meat and potatoes of this monster. # Most of this mess is generated by h2xs, but let's see what else we got. #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" # This line being ABOVE the "MODULE =" line is critical, as everything above that line is treated as pure C++ # Anything below it is in XS macro land. #include "SimpleLib/Simple.h" using namespace std; MODULE = Simple PACKAGE = Simple # The constructor. Kind of looks like C++ here. I pass in an "int". Simple * Simple::new( y ) int y # I have no idea how "SimplePtr" became a valid keyword, but somehow the XS engine realizes that it is the return type of the constructor. # It's magic to me, for now. MODULE = Simple PACKAGE = SimplePtr # Here is some basic XS, the documentation on this is covered pretty well in "Extending and Embedding Perl", specifically chapters 2 and 6. int Simple::add(x) int x OUTPUT: RETVAL string Simple::get_string(z) string z OUTPUT: RETVAL StringMap Simple::getMap(myMap) StringMap myMap OUTPUT: RETVAL StringVector Simple::getVec(myVec) StringVector myVec OUTPUT: RETVAL #### TYPEMAP Simple * T_PTROBJ string T_STRING StringMap T_STRING_MAP StringVector T_STRING_VECTOR # Ok, let's step through this one fragment at a time. First we're going to declare the "input" type # (ie: an argument passed in from perl to C++) of T_STRING, which maps to the C++ STL type of std::string. # # SvTYPE is a function that looks at an SV's type to determine if it's an ordinary SV. # It's basically the XS version of "ref". SVt_PV is a string. But you should use SvROK (the other half of "ref") # first, not sure why it's not. If the string typemap receives something that isn't one, it'll warn and return undef. # SvCUR is a function that lets you interrogate the length of a scalar. # SvCUR has a friend named SvLEN that tells you how long it could be. # If it's length is 0, warn and return undef (not always desired, but for now it's fine. # The last line generates a string from the argument given. # "string" is the actual C++ class constructor. # SvPV_nolen lets you extract the value of a scalar and coerce it as a char *, # of unlimited length, because the C++ std::string constructor doesn't care, I think. INPUT T_STRING { if (SvTYPE($arg) != SVt_PV) { warn(\"${Package}::$func_name() -- $var is invalid svtype\"); XSRETURN_UNDEF; } if (SvCUR($arg) == 0) { warn(\"${Package}::$func_name() -- $var is empty\"); XSRETURN_UNDEF; } $var = string(SvPV_nolen($arg)); } # Ok, this next section covers strings returned from C++ functions or methods (hence the OUTPUT), # that need to be transformed back into scalars for perl to deal with. The "sv_setpvn" macro sets # the value of a scalar. OUTPUT T_STRING sv_setpvn($arg, $var.c_str(), $var.size()); # Now things get complicated, let's handle an "array reference" and turn it into a std::vector of std::string's # and pass it into a C++ function or method, yee-hah.. # # AV *av is a perl array. # I32 len is a 32 bit int. (why not just use int?) # SvROK "is this a reference" sort of like ref, except it doesn't tell you which kind. (SvRV dereferences the reference) # SVt_PVAV is Perl's C structure for an Array. # So, the entire line says, "if the argument is a perl array reference:" # Dereference the arg, then downcast it to a pointer of an array type (since we know it is one), assign that to our array type (av). # Assign the length of the arraa, using "av_len". # We return undef if it's length is 0 (not sure why we couldn't return an empty list here, but whatever) # The "else" handles the case when it's not handed an array reference as an argument # # Now for the guts. Iterate over every element (the for loop)..Call the vector's method "push_back". # Create a new string, calling the scalar extraction SvPV_nolen, against the dereferenced value of what av_fetch returned. # It passes the array, the position, and a third argument detailing if Perl needs to auto extend the list # (because you're about to write to that index position). av_fetch returns a pointer to a pointer to an SV, hence the deref'ing "*av_fetch". # lastly, we need to assign the "$var" (what goes to C++ to t_sv, our C++ StringVector, the header shows this is a std::vector INPUT T_STRING_VECTOR { AV *av; I32 len; StringVector t_sv; if(SvROK($arg) && SvTYPE(SvRV($arg)) == SVt_PVAV){ av = (AV *)SvRV($arg); len = av_len(av) + 1; if(len == 0){ warn(\"${Package}::$func_name() -- $var is empty array reference\"); XSRETURN_UNDEF; } } else { warn(\"${Package}::$func_name() -- $var is not a array reference\"); XSRETURN_UNDEF; } for (I32 i = 0; i < len; i++) { t_sv.push_back(string(SvPV_nolen(*av_fetch(av, i, 0)))); } $var = t_sv; } # Now for String vectors returned back to Perl # If it the C++ vector is empty, it returns undef back to perl # Why is it when I see "sv_2mortal" I think about the intro to the Sega game, "Altered Beast"... "Rise from your grave!" # So many weirdly named keywords in Perl. I love the language, maybe even because of it's quirks. # # Anyway, we create a new Array. (the rest is covered similarly on page 124 of Extending and Embedding Perl). # # SvSetSV will COPY the data from one variable to another, unless they're pointing to the same thing. # newRV_noinc: This is the C/C++ equivalent of the "\" or reference operator. We have a list, we want to return it as an array reference. # We don't increment because it needs to go out of scope when this block finishes (we're copying to "arg" anyway, so the data lives on). # Reading from inside out. Take an array, cast it as a pointer to a scalar (what newRV_noinc requires). Pass that to newRV_noinc to # create a reference. "arg" and this new reference value get sent to SvSetSV to copy the contents of your new array ref to the outbound # "arg" structure. Phew! OUTPUT T_STRING_VECTOR { if($var.empty()){ warn(\"${Package}::$func_name() -- vector is empty\"); XSRETURN_UNDEF; } AV *av = (AV *)sv_2mortal((SV *)newAV()); for(StringVectorIt it = $var.begin(); it != $var.end(); it++) { av_push(av, newSVpvn(it->c_str(), it->size())); } SvSetSV($arg, newRV_noinc((SV *)av)); } # Now we need to look at hash referenecs passed as arguments to C++ functions. # They'll transform into std::map. # HV is a hash value # HE is a hash entry (a key) # Ok, we've seen most of this already. So let's move quick. # If it's not a hash ref, the return undef (SVt_PVHV is a hash) # Next is the C side perl loop over the keys in a hash # The key and value from the hash get forced into scalar values via HeSVKEY_force and HeVAL. # Those scalars are passed into the insert function of our StringMap (std::map) # Assign the ingoing $var to the StringMap we've been inserting into! INPUT T_STRING_MAP { HV *hv; HE *he; StringMap t_sm; if(SvROK($arg) && SvTYPE(SvRV($arg)) == SVt_PVHV) { hv = (HV *)SvRV($arg); if(hv_iterinit(hv) == 0) { warn(\"${Package}::$func_name() -- $var is empty hash reference\"); XSRETURN_UNDEF; } } else { warn(\"${Package}::$func_name() -- $var is not a hash reference\"); XSRETURN_UNDEF; } while((he = hv_iternext(hv)) != NULL) { SV *svkey = HeSVKEY_force(he); SV *svval = HeVAL(he); t_sm.insert(StringMap::value_type(string(SvPV_nolen(svkey)), string(SvPV_nolen(svval)))); } $var = t_sm; } # Lastly, deal with functions / methods that return StringMap's back to Perl. # If it's empty, return undef.. # That crazy 'rise from the dead' thing again creating a Hash value that will go out of scope and be freed after this block ends. # Now it's basic C++ map looping. hv_store only takes 5 arguments. ::blink:: # The hash that you want insert into # A const char* of the key # An I32 integer of the length of that char* key we just mentioned # A scalar that is to be the value of the key. # The hashing value (passing 0 will tell Perl to compute it for you) # it->first is the key of the current element in std::map # it->second is the value of the current element in std::map # The value needs to be "wrapped" by the creation of a new scalar value, same as what we did when adding elements to an array OUTPUT T_STRING_MAP { if($var.empty()){ warn(\"${Package}::$func_name() -- map is empty\"); XSRETURN_UNDEF; } HV *hv = (HV *)sv_2mortal((SV *)newHV()); for(StringMapIt it = $var.begin(); it != $var.end(); it++) { hv_store(hv, (it->first).c_str(), (it->first).size(), newSVpvn((it->second).c_str(), (it->second).size()), 0); } SvSetSV($arg, newRV_noinc((SV *)hv)); } #### #undef init_tm #undef do_open #undef do_close #ifdef ENTER #undef ENTER #endif