NAME
XS::Framework::Manual::recipe04 - XS::Framework basics
DESCRIPTION
Let's assume that we have Timezone
object, which is available via std::shared_ptr
.
struct TimezoneRecipe04 {
const char* get_name() const { return name.c_str(); }
static std::shared_ptr<TimezoneRecipe04> create(const char* name) {
return std::make_shared<TimezoneRecipe04>(name);
}
TimezoneRecipe04(const char* name_): name{name_} { }
~TimezoneRecipe04() { std::cerr << "~TimezoneRecipe04()\n"; }
private:
std::string name;
};
using TimezoneRecipe04SP = std::shared_ptr<TimezoneRecipe04>;
// (1)
It is recommended to have an alias(1) for smart_pointes, as there will be less code below. Let's define typemap for the shared pointer of of Timezone:
namespace xs {
template <>
struct Typemap<TimezoneRecipe04SP> : TypemapObject<TimezoneRecipe04SP, TimezoneRecipe04SP, ObjectTypeSharedPtr, ObjectStorageMG, StaticCast> {
// (1) (2) (3) (4)
static std::string package () { return "MyTest::Cookbook::TimezoneRecipe04"; }
};
}
As usual, we have to do full specialization (1) for the smart pointer; we are using TypemapObject
helper and as there is no types hierarchy base class (2) and final class (3) point to the same type (timezone smart pointer). The shipped ObjectTypeSharedPtr
life time policy should be used, as it is aware of std::shared_ptr
and knows how to increase and decrease it's reference counter.
The xs-adapter will be like:
MODULE = MyTest PACKAGE = MyTest::Cookbook::TimezoneRecipe04
PROTOTYPES: DISABLE
const char* get_name(TimezoneRecipe04SP tz) { RETVAL = tz->get_name(); }
// (5)
TimezoneRecipe04SP create(const char* name) { RETVAL = TimezoneRecipe04::create(name); }
Pay attention to the XS-function definition (5). It would be error to write it as const char* TimezoneRecipe04::get_name
, because XS::Framework::ParseXS
will try to map THIS
variable to TimezoneRecipe04*
, i.e. timezone pointer; but there is no typemap exist for TimezoneRecipe04*
, there is only typemap for TimezoneRecipe04SP
, and C++ compiler will emit error.
For the sake of completeness there is another mapped C++ class, which uses TimezoneRecipe04SP
. There is nothing new for a reader familiar with the previous recipes.
// C++ class
struct DateRecipe04 {
DateRecipe04() { update() ; }
void update() { epoch = std::time(nullptr); }
int get_epoch() const { return epoch; }
void set_timezone(TimezoneRecipe04SP tz_) { tz = tz_; }
TimezoneRecipe04SP get_timezone() { return tz; }
private:
std::time_t epoch;
TimezoneRecipe04SP tz;
};
// typemap
namespace xs {
template <>
struct Typemap<DateRecipe04*> : TypemapObject<DateRecipe04*, DateRecipe04*, ObjectTypePtr, ObjectStorageMG, StaticCast> {
static std::string package () { return "MyTest::Cookbook::DateRecipe04"; }
};
}
// XS-adapter
MODULE = MyTest PACKAGE = MyTest::Cookbook::DateRecipe04
PROTOTYPES: DISABLE
DateRecipe04* DateRecipe04::new() { RETVAL = new DateRecipe04(); }
void DateRecipe04::update()
std::time_t DateRecipe04::get_epoch()
TimezoneRecipe04SP DateRecipe04::get_timezone()
void DateRecipe04::set_timezone(TimezoneRecipe04SP tz)
If there will be test code like:
my $tz = MyTest::Cookbook::TimezoneRecipe04::create('Europe/Minsk');
my $date = MyTest::Cookbook::DateRecipe04->new;
$date->set_timezone($tz);
is $date->get_timezone->get_name, 'Europe/Minsk'; # (6)
is $date->get_timezone, $tz; # (7)
the check code on line (6) will pass, and on the line (7) will fail. This is because every time new wrapper (SV*) is created by typemap::out
method. To handle that you can either a) overload eq
method to let it extracts actual pointers from shared_ptr
and does pointer comparison, or use typemaps with backrefs
support, which are covered in future recipes.