Learning XS - Exporting
What is Exporting in Perl?
Exporting in Perl is a mechanism that allows you to make functions or variables available to the user of a module without requiring them to fully qualify the names. This is typically done using the 'Exporter' module, which provides a simple way to export symbols from a module, however there are many other variants of exporters on cpan. When I say symbol this is any variable or subroutine/function.
We have already exported functions in some of our previous examples, today we are going to continue from our last post https://blogs.perl.org/users/robert_acock/2025/06/learning-xs---invocation.html and extend to export the 'sum', 'min', 'max', 'mean' functions. We will do this entirely in XS this time and not use 'Exporter', essentially we are going to re-implement the following code:
package Stats::Basic;
my %EXPORT = (
sum => \&sum,
min => \&min,
max => \&max,
mean => \&mean,
);
sub import {
my ($pkg, @symbols) = @_;
my $caller = caller;
foreach my $symbol (@symbols) {
die "Unknown import: $symbol" unless exists $EXPORT{$symbol};
no strict 'refs'; # note this is what Exporter does just in a more convoluted way
*{"${caller}::$symbol"} = $EXPORT{$symbol}
if exists $EXPORT{$symbol};
}
}
...
1;
Now we would be able to use our module like this:
use Stats::Basic qw(sum min max mean);
print sum { $_ } 1, 2, 3, 4, 5; # prints 15
An XS module is essentially a Perl module thus when a XS module is loaded, Perl checks the package for an 'import' function, if found then the code will be executed. So for our module we simply need to follow the same pattern as the pure perl version and implement a XSUB for 'import' that will export the functions we want. To actually export the functions we will use the 'newXSproto' macro that allows us to create a new XSUB in another namespace with a prototype, if you do not need the prototype then use the 'newXS' variation. First as always lets write a new test 't/05-export.t' to test our new functionality.
use Test::More;
use Stats::Basic qw(sum min max mean);
is((sum { $_ } 1, 2, 3, 4, 5), 15, 'sum works');
is((min { $_ } 1, 2, 3, 4, 5), 1, 'min works');
is((max { $_ } 1, 2, 3, 4, 5), 5, 'max works');
is((mean { $_ } 1, 2, 3, 4, 5), 3, 'mean works');
done_testing;
Now we can implement the 'import' XSUB in our XS file 'Stats/Basic.xs' add the following definition to the end:
void
import(...)
CODE:
char *pkg = HvNAME((HV*)CopSTASH(PL_curcop));
int pkg_len = strlen(pkg);
STRLEN retlen;
int i = 1;
for (i = 1; i < items; i++) {
char * ex = SvPV(ST(i), retlen);
int name_len = pkg_len + retlen + 3;
char *name = (char *)malloc(name_len);
snprintf(name, name_len, "%s::%s", pkg, ex);
if (strcmp(ex, "sum") == 0) {
newXSproto(name, XS_Stats__Basic_sum, __FILE__, "&@");
} else if (strcmp(ex, "min") == 0) {
newXSproto(name, XS_Stats__Basic_min, __FILE__, "&@");
} else if (strcmp(ex, "max") == 0) {
newXSproto(name, XS_Stats__Basic_max, __FILE__, "&@");
} else if (strcmp(ex, "mean") == 0) {
newXSproto(name, XS_Stats__Basic_mean, __FILE__, "&@");
} else {
croak("Unknown import: %s", ex);
}
free(name);
}
If we step through the code in more detail, we use some new macros I will try to explain them as best as I can. Firstly we use 'Pl_curcop' which is a global variable in Perl's internals that points to the current COP (Code OP) structure, which contains information about the current code block being executed, including the package name. We then use 'CopSTASH' to access the OP stash and 'HvNAME' to get the name of the package as a string. We calculate the length of the package name and store it in 'pkg_len'. Next we loop through the items passed to the 'import' function, starting from index 1 (since index 0 is the module package name). For each item, we retrieve the string value using 'SvPV' and calculate its length. We then allocate memory for the full name of the function to be exported, which includes the package name we are going to import into and the function name. We use 'snprintf' to format the full name as "PackageName::FunctionName". After that, we check which function is being imported and use 'newXSproto' to create a new XSUB in the current package with the appropriate function pointer. The 'newXSproto' macro takes the name of the XSUB, the function pointer, the file name, and the prototype string. You will see i access the function pointer by namespace 'XS_Stats_Basic_XS_sum', I'm unsure if a better way exists but this works. Finally, we free the allocated memory for the name.
If we run our test now we should see it pass.
Next time we will look into how to execute perl Regular Expressions in XS, which will be a fun topic to explore. Until then, I hope you found this post helpful in understanding how to export functions in XS. If you have any questions or suggestions for future topics, feel free to leave a comment below.
Leave a comment