cperlxs

Looking for a way to call Perl XS C API functions/macros from helper functions


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


Solution

  • 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:

    "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.