I’ve been experimenting with the Perl XS C API and have hit a roadblock.
I have simplified my example below. Assuming an existing struct MyObject then to access property “a” or “b” and create a hash for either one I could use the following code:
typedef struct {
const char *prop_a;
const char *prop_b;
struct {
const char **items;
int num;
} names;
} MyObjectInfo;
typedef MyObjectInfo *MyObject;
MODULE = my_obj PACKAGE = MyObject PREFIX = my_obj_
SV *
my_obj_a(o)
MyObject o
CODE:
SV *info = newHV();
hv_store(info, “a”, 1, newSVpvn(o->prop_a, strlen(o->prop_a)), 0);
int i;
for(i = 0; i < o->names.num; i++) {
const char *n = o->names.items[i];
hv_store(info, n, strlen(n), newSViv(i), 0);
}
RETVAL = sv_2mortal(newrv_noinc(val));
OUTPUT:
RETVAL
SV *
my_obj_b(o)
MyObject o
CODE:
SV *info = newHV();
hv_store(info, “b”, 1, newSVpvn(o->prop_b, strlen(o->prop_b)), 0);
int i;
for(i = 0; i < o->names.num; i++) {
const char *n = o->names.items[i];
hv_store(info, n, strlen(n), newSViv(i), 0);
}
RETVAL = sv_2mortal(newrv_noinc(val));
OUTPUT:
RETVAL
What I want to do is share some of the functionality in a utility function like this
SV *create_obj_hash(MyObjectInfo *o, const char *k, const char *p) {
SV *val = newHV();
hv_store(val, k, strlen(k), newSVpvn(p, strlen(p)), 0);
int i;
for(i = 0; i < o->names.num; i++) {
const char *n = o->names.items[i];
hv_store(info, n, strlen(n), newSViv(i), 0);
}
return val;
}
MODULE = my_obj PACKAGE = MyObject PREFIX = my_obj_
SV *
my_obj_a(o)
MyObject o
CODE:
SV *info = create_obj_hash(o, “a”, o->prop_a);
RETVAL = sv_2mortal(newrv_noinc(val));
OUTPUT:
RETVAL
SV *
my_obj_b(o)
MyObject o
CODE:
SV *info = create_obj_hash(o, “b”, o->prop_b);;
RETVAL = sv_2mortal(newrv_noinc(val));
OUTPUT:
RETVAL
But, when I do the macro expansion within create_obj_hash() fails with the following messages.
myobj.xs: In function 'create_obj_hash':
/usr/lib/x86_64-linux-gnu/perl/5.28/CORE/perl.h:175:16: error: 'my_perl' undeclared (first use in this function); did you mean 'my_fork'?
# define aTHX my_perl
^~~~~~~
ppport.h:6145:41: note: in definition of macro 'MUTABLE_PTR'
# define MUTABLE_PTR(p) ({ void *_p = (p); _p; })
^
/usr/lib/x86_64-linux-gnu/perl/5.28/CORE/hv.h:651:17: note: in expansion of macro 'MUTABLE_HV'
#define newHV() MUTABLE_HV(newSV_type(SVt_PVHV))
^~~~~~~~~~
/usr/lib/x86_64-linux-gnu/perl/5.28/CORE/perl.h:188:18: note: in expansion of macro 'aTHX'
# define aTHX_ aTHX,
^~~~
/usr/lib/x86_64-linux-gnu/perl/5.28/CORE/embed.h:532:40: note: in expansion of macro 'aTHX_'
#define newSV_type(a) Perl_newSV_type(aTHX_ a)
^~~~~
/usr/lib/x86_64-linux-gnu/perl/5.28/CORE/hv.h:651:28: note: in expansion of macro 'newSV_type'
#define newHV() MUTABLE_HV(newSV_type(SVt_PVHV))
^~~~~~~~~~
myobj.xs:42:19: note: in expansion of macro 'newHV'
return (void*)newHV();
Thank you very much in advance, Brian
First of all, you might be missing some or all of the following:
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
The main issue is that you aren't providing the context to the API calls.
Some builds of Perl allow processes to have multiple instances of the interpreter running at once. If -Dmultiplicity
was used when Perl was created, the build will support this. (You can check this using perl -V:usemultiplicity
.) -Dmultiplicity
is implied by -Dusethreads
, the option to build a perl
with thread support (since an instance of the interpreter is created for each thread).
As such, a large number of Perl API calls require the caller to provide a context ("THX") which identifies the interpreter to use. Think of the interpreter as an object (in the OOP sense of the word), and the context as the invocant.
In XS code, a variable containing the context is automatically created for you. This variable is automatically passed to Perl API call through the use of macros.
#define newSVpvn(a,b) Perl_newSVpvn(aTHX_ a,b)
// ^^^^^
// Causes the context to be passed
// to Perl_newSVpvn, the real name
// of newSVpvn.
As such, you'll need the context to make this work (no matter which of newSVpvn
and Perl_newSVpvn
you use). To obtain the context, use the following macros:
pTHX
as the first parameter of your function declaration.aTHX
as the first argument in calls to your function.pTHX_
as the first parameter of your function declaration.aTHX_
as the first argument in calls to your function."p
" stands for "parameter", "a
" stands for "argument", and "_
" represents a comma.
In your case, you'd use
STATIC SV *create_obj_hash(pTHX_ MyObjectInfo *o, const char *k, const char *p) {
#define create_obj_hash(a,b,c) create_obj_hash(aTHX_ a,b,c)
...
}
Thanks to the #define
, you can continue using
SV *info = create_obj_hash(o, "b", o->prop_b);
Untested. Let me know if there are any problems.