h2xs -A -n SimpleTest
####
mkdir SimpleTest/SimpleLib
##
##
#include
#include
##
#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