Categories
Perl XS

More refactor to our XS module. Bringing the internals to a private C file.

In this article the code I did in the last article to allow the usage of the arguments list as a Hash is going to be moved to a C file to provide a way to reuse this code in all my subroutines of this module without exposing this API to Perl. (In Perl this job is already done.)

Let’s see how, first we are going to make the following directories into mega_openssl_helper_xs src and src/include and we are going to add to the Makefile.PL of mega_openssl_helper_xs an OBJECT and postamble directive so it gets the code into that folders.

use ExtUtils::MakeMaker;

WriteMakefile(
    NAME    => 'Peertube::DL::Mega::Helper',
    VERSION => '0.1',
    XS      => { 'mega.xs' => 'mega.o' },
    INC     => '-Isrc/include',
    OBJECT  => 'src/private.o mega.o',
    LDFLAGS => '-Wl-t',
    DIR     => ['src'],
);  
        
package MY {
            
    sub postamble {
        return . "src: src/Makefile\n" . "\tcd src && $(MAKE) $(PASSTHRU)\n";
    }       
} 

Now we are making a mega_openssl_helper_xs/src/Makefile.PL to compile the C that I am going to put into that directory:

use ExtUtils::MakeMaker;

WriteMakefile(
    NAME    => 'Peertube::DL::Mega::Helper::SRC',
    VERSION => '0.1',
    INC     => '-I./include',
    C       => [ 'private.c', ],
    OBJECT  => '${O_FILES}',
    LDFLAGS => '-Wl-t',
);

We make the src/include/private.h file to declare the subroutines for code reuse:

#include "EXTERN.h"
#include "perl.h"

HV * hash_from_list(SV **, size_t list_len);

And we move the code handling the hashes to this subroutine into src/private.c

#include "EXTERN.h"
#include "perl.h"

HV * hash_from_list(SV **list, size_t list_len) {
    HV * hash = newHV();
    bool is_key = true;
    char *key;
    STRLEN key_len;
    for ( int i = 0; i < list_len; i++ ) {
        if (is_key) {
            key = SvPV(list[i], key_len);
        } else {
            SvREFCNT_inc(list[i]);
            if ( !hv_store(hash, key, key_len, list[i], 0) ) {
                warn("Failed to write into hash.");
                SvREFCNT_dec(list[i]);
            }
        }
        is_key = !is_key;
    }
    if ( !is_key ) {
        warn("Even number of parameters in hash argument list.");
        SV *undef = sv_newmortal();
        SvREFCNT_inc(undef);
        if ( !hv_store(hash, key, key_len, sv_newmortal(), 0) ) {
            warn("Failed to write into hash.");
            SvREFCNT_dec(undef);
        }
    } 
    return hash;
}

Now our mega.xs print XSUB is more compact:

void
print(self, ...)
    Peertube_DL_Mega_Helper self 
    CODE:
        if ( items <= 1 ) {
            croak("Less parameters than expected.");
        }
        size_t list_len = items - 1;
        SV **list = malloc(sizeof (SV *) * list_len);
        for ( int i = 1; i < items; i++ ) {
            list[i-1] = ST(i);
        }

        HV *hash = hash_from_list(list, list_len);        
        free(list);
        char *key = "hello";
        SV **hello = hv_fetch(hash, key, strlen(key), 0);
        if ( hello != NULL && *hello != NULL ) {
            if ( !SvOK(*hello) ) {
                warn("hello is undef.");
            }
            printf("hello: %s\n", SvPV_nolen(*hello));
        } else {
            croak("Parameter hello required.");
        }

I hope you have enjoy this article about XS.

Erratas: sizeof was unfortunatelly not enought to get the size of the array, so I did it passing the size manually to the subroutine which sightly complicates the code.

Erratas II: I left some printf statements not needed.

Erratas III: I left a stdio.h import in the C file which was no longer needed, I used it with debugging purposes.

By sergiotarxz

I am a software developer with high interest on free software.

Leave a Reply

Your email address will not be published. Required fields are marked *