NAME
XS::Framework::Manual::recipe06 - XS::Framework advanced topic
DESCRIPTION
Let's assume that there is external C++ library (out of our control), which offers the following API:
struct DateRecipe10;
struct TimezoneRecipe10 {
const char* get_name() const { return name.c_str(); }
private:
TimezoneRecipe10(const char* name_): name{name_} { }
std::string name;
friend class DateRecipe10;
};
struct DateRecipe10 {
DateRecipe10(const char* tz_ = "Europe/Minsk"): tz(tz_) { update(); }
~DateRecipe10() { std::cout << "~DateRecipe10()\n"; }
void update() { epoch = std::time(nullptr); }
int get_epoch() const { return epoch; }
TimezoneRecipe10& get_timezone() { return tz; }
private:
std::time_t epoch;
TimezoneRecipe10 tz;
};
It allows to create Date
object, and offres the Timezone
object by reference. In other words, the lifetime of Timezone object is limited by lifetme of Date object. Let's imagine to possible use-cases of the library from Perl perspecitve:
my $date = MyTest::Cookbook::DateRecipe10->new;
my $tz = $date->get_timezone;
print "now is ", $date->epoch, " at ", $tz->get_name, "\n";
undef $date;
print "mytimezone is ", $tz->get_name, "\n"; # SEGFAULT (1)
The common Perl programmer expectations are: if there is a valid reference to an objcet ($tz
), the object is valid, and all it's allowed to invoke it's methods etc. May be some error might occur, but Perl interpreter core dump is not expected: it is hard to debug, it should be not allowed to have that code in production system. Let's reformulate: here there is a conflict between C++ API objects lifetimes and possible perl script lifetimes.
Let's check what are the available options:
1) In Date
xs-adapter do not expose the Timezone
object, but instead just return by value timezone name as string. That good solution, but it is rather limited for simple objects, which can be reduced to string names and do not expose any other methods.
2) Let Date
xs-adapter return "detached" copy of Timezone
object. As the new clone has independent from Date
lifetime the problem would be solved. Hovewer, this is not always possible: the c++ object might do not have clone method or copy-constructor, or the cloning operation might be a bit heavy-weight.
3) Let the Timezone
xs-adapter somehow "prolongs" lifetime of it's owner Date
as long as needed.
+-----------------+ +---------------------+
+---->|Date(C++ pointer)|---------->|TimeZone(C++ pointer)|<------------+
| +-----------------+ +---------------------+ |
| |
| |
| |
| +-------------------+ +----------------------------+ |
| |Date (xs-adapter) | |TimeZone(xs-adapter) | |
+----|* C++ date pointer |<--- | * C++ TimeZone pointer |-------+
+-------------------+ \----| * XS adapter Date pointer |
+----------------------------+
in other words, Timezone
xs-adapter will hold C++ Timezone
pointer and Date xs-adapter, which holds C++ Date
pointer. So, invoking methods on TimeZone
C++ object is guaranteed to be safe and match Perl developer expectations, i.e. s/he might store/enclose $timezone
object as long as needed and methods invocations will be safe.
Let's show how this can be achived using magic payload, provided by SV-api of XS::Framework:
namespace xs {
template <>
struct Typemap<DateRecipe10*> : TypemapObject<DateRecipe10*, DateRecipe10*, ObjectTypePtr, ObjectStorageMG> {
static std::string package () { return "MyTest::Cookbook::DateRecipe10"; }
};
template <>
struct Typemap<TimezoneRecipe10*> : TypemapObject<TimezoneRecipe10*, TimezoneRecipe10*, ObjectTypeForeignPtr, ObjectStorageMG> {
// (2)
static std::string package () { return "MyTest::Cookbook::TimezoneRecipe10"; }
};
};
static xs::Sv::payload_marker_t payload_marker_10{};
MODULE = MyTest PACKAGE = MyTest::Cookbook::TimezoneRecipe10
PROTOTYPES: DISABLE
const char* TimezoneRecipe10::get_name()
MODULE = MyTest PACKAGE = MyTest::Cookbook::DateRecipe10
PROTOTYPES: DISABLE
DateRecipe10* DateRecipe10::new(const char* name)
void DateRecipe05::update()
std::time_t DateRecipe10::get_epoch()
Sv DateRecipe10::get_timezone() {
Object self {ST(0)};
Object tz = xs::out<>(&THIS->get_timezone()); // (3)
auto self_ref = Ref::create(self); // (4)
tz.payload_attach(self_ref, &payload_marker_10); // (5)
RETVAL = tz.ref(); // (6)
}
As the lifetime of Timezone
C++ object isn't managed by Perl the ObjectTypeForeignPtr
(2) should be specified as lifetime policy, i.e. the delete
operation will never be invoked on C++ object.
When a user asks Date
xs-wrapper for Timezone
object, xs-adapter for it is lazily created at (3). Than, reference to the original Date
object is taken (4) and stored in Timezone
Perl SV* wrapper (5) as payload.
It is possible to achive the same without XS::Framework
, i.e. via inheritance, storing SV* wrapper to Date
in the Derived class; and doing inc
on the pointer in Constructor, and dec
in the destructor. Hovewer, using XS::Framework magic payload seems a bit more easier and convenient in the case.
As usual, we should return reference to the object (6) instead of object itself.
Short summary: if C++ API offers two different objects, where lifetime of one is bounded to the lifetime of the other, it is possible to "decouple" them on XS-layer via using payload. However, if you have control on C++ API, it would be better to "share" objects lifetimes between C++ and Perl using refcounter mechanism as more generic one.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 80:
Deleting unknown formatting code D<>