stevieb has asked for the wisdom of the Perl Monks concerning the following question:

update: You can see code snips I ended up with that works are in Re: XS: Passing an external library's function a Perl XS callback.

Disclaimer: cross-posted at StackOverflow.

I hope I'm describing and depicting my issue properly... In XS, I'm trying to send a callback into an external library's function, where the callback has Perl specific functions. The XSUB is passed as a function pointer to an external C function. The XSUB callback being sent in turn calls back to a sub in the main perl application:

void callback(){ dSP; PUSHMARK(SP); call_pv("p_callback", G_DISCARD|G_NOARGS); } // example extern call externFunc(&callback);

This segfaults. I think it's because the external library doesn't understand the perl functions that are being called. Things work fine if I call the C callback() function directly though.

Is there some magic that I can do to make the external library "see" the Perl C functions, or am I doing something wrong?

Here's the code I'm testing with:

use warnings; use strict; use Inline ('C' => 'DATA', libs => '-lwiringPi'); init(); # the below call is a local XS function that # sets up the external function with the callback setInterrupt(27, 3); # direct call callback(); # on() triggers the external function to exec # the callback on(27); sub p_callback { print "in perl callback\n"; } __DATA__ __C__ #include <stdlib.h> #include <stdio.h> #include <wiringPi.h> void init(); void on(int pin); void off(int pin); void setInterrupt(int pin, int edge); void callback(); void init(){ printf("in init\n"); wiringPiSetup(); } void on(int pin){ pinMode(pin, 1); digitalWrite(pin, 1); } void off(int pin){ digitalWrite(pin, 0); pinMode(pin, 0); } void setInterrupt(int pin, int edge){ wiringPiISR(pin, edge, &callback); } void callback(){ dSP; PUSHMARK(SP); call_pv("p_callback", G_DISCARD|G_NOARGS); }

Output:

in init in perl callback Segmentation fault

If I remove the perl specific C calls from within the callback and just do a printf() or other pure-C work, things proceed without a segfault.

Replies are listed 'Best First'.
Re: XS: Passing an external library's function a Perl XS callback
by stevieb (Canon) on Aug 15, 2016 at 14:23 UTC

    Well, I confirmed that what I was trying to do is definitely possible. By adding the following code to the external application, somewhat simulating what the original function was doing:

    void interruptTest(void(*function)(void)){ isrFunctions[1] = function; isrFunctions[1](); }

    ...and then simplifying my code to use this new function:

    use warnings; use strict; use Inline ('C' => 'DATA', libs => '-lwiringPi'); # direct call callback(); # extern call testing(); sub p_callback { print "in perl callback\n"; } __DATA__ __C__ #include <stdlib.h> #include <stdio.h> #include <wiringPi.h> void testing(); void callback(); void callback(){ dSP; PUSHMARK(SP); PUTBACK; call_pv("p_callback", G_DISCARD|G_NOARGS); FREETMPS; LEAVE; } void testing(){ interruptTest(&callback); }

    I get no segfaults, and the perl callback is called in both instances (internal and external). So its not that the external code doesn't know how to process the callback, it has to be something to do with what's happening in the original function I was sending it into. There's forking happening, and then the callback is executed when an interrupt happens, so I'll keep playing.

Re: XS: Passing an external library's function a Perl XS callback
by stevieb (Canon) on Aug 16, 2016 at 00:08 UTC

    For completeness sake, I'm posting the relevant code that now has fixed my original problem. I can now use an external library that has ISR (Interrupt Service Routine) capability.

    What I needed to do was allow a user to send in a Perl code reference, and then have the C interrupt handler dynamically call that user-supplied sub when the interrupt occurred.

    XS code that sets up the interrupt through the external lib:

    void interruptHandler(); int setInterrupt(int pin, int edge, char *callback); char *perl_callback; PerlInterpreter *mine; void interruptHandler(){ PERL_SET_CONTEXT(mine); dSP; PUSHMARK(SP); PUTBACK; call_pv(perl_callback, G_DISCARD|G_NOARGS); FREETMPS; LEAVE; } int setInterrupt(int pin, int edge, char *callback){ perl_callback = callback; int interrupt = wiringPiISR(pin, edge, &interruptHandler); return interrupt; } # XS int setInterrupt(pin, edge, callback) int pin int edge char *callback void interruptHandler()

    Perl module code that calls into the XS:

    sub set_interrupt { shift if @_ == 4; my ($pin, $edge, $callback) = @_; setInterrupt($pin, $edge, $callback); }

    ...and finally a basic test script:

    use warnings; use strict; use RPi::WiringPi; use RPi::WiringPi::Constant qw(:all); my $pi = RPi::WiringPi->new(); my $pin = $pi->pin(27); RPi::WiringPi::Core::set_interrupt( $pin->num, EDGE_RISING, 'pin_27_edge_rise' ); $pin->mode(INPUT); sleep 10; # manually change the pin state here to trigger # interrupt $pi->cleanup; sub pin_27_edge_rise { print "pin 27 edge rise callback...\n"; # do other stuff }

      I see where you declare PerlInterpreter *mine to hold the Perl context, but where you assign it?

      In his linked example, BrowserUk has:

      void setCallback( SV *cb1, SV* cb2, SV *cb3, SV *cb4 ) { saved = Perl_get_context();

      but I don't see an equivalent in your code.

        I can confirm that you have to run
        mine = Perl_get_context();
        before you can use this. The run of
        dSP;
        just segfaults if
        mine
        have not been set via
        mine = Perl_get_context();
        I call on initialisation of my module a XS function which does does do
        mine = Perl_get_context();
        and verify that mine is not NULL before trying to call a perl function.

        All I needed is the following to get it working, and call the function "functionname" within of the module I called "setcallback" from.
        The use in C:
        void dorunperlfunction() { if (mine) { PERL_SET_CONTEXT(mine); dSP; ENTER; SAVETMPS; PUSHMARK(SP); PUTBACK; call_pv("functionname", G_DISCARD|G_NOARGS); FREETMPS; LEAVE; }
        The initialisation in XS(!):
        PerlInterpreter * mine = NULL; ... void setcallback() CODE: ... mine = Perl_get_context(); ...
Re: XS: Passing an external library's function a Perl XS callback
by Anonymous Monk on Aug 15, 2016 at 19:36 UTC

    I'm surprised no-one has commented on this yet: are you really attempting to call perl code from an ISR (interrupt service routine)? Perl does allow "PERL_SIGNALS=unsafe", but generally, signal handling is deferred for good reasons.

    What sort of code do you need to invoke from an interrupt handler? Can't you just use the regular dispatch via $SIG{USR1} or something?

      Essentially, an external program is handling the interrupts within threads... I just want to be able to have user-generated perl code executed when the interrupt is triggered.

      I've found that the problem isn't necessarily with the wiringPi software. I've recreated the issue without external libraries. The problem seems to be with threads. The following works fine, until I uncomment the XS code within the wrapper() function...

      use warnings; use strict; use Inline (C => 'DATA', libs => '-lpthread'); create_thread('blah'); sub blah { print "perl callback\n"; } __DATA__ __C__ #include <pthread.h> #include <stdio.h> #include <stdlib.h> void wrapper(void *sub_name_ptr){ char *sub_name = (char *)sub_name_ptr; printf("threaded ok, sub: %s\n", sub_name); /* dSP; PUSHMARK(SP); PUTBACK; call_pv(sub_name, G_DISCARD|G_NOARGS); FREETMPS; LEAVE; */ return NULL; } int create_thread(char *subname){ pthread_t sub_thread; if(pthread_create(&sub_thread, NULL, wrapper, subname)) { fprintf(stderr, "Error creating thread\n"); return 1; } if(pthread_join(sub_thread, NULL)) { fprintf(stderr, "Error joining thread\n"); return 2; } return 0; }

        Under threads, perl code needs the appropriate context. If you callback to code referenced from one thread whilst running under another, you will have the wrong context and crash.

        Read down from here, it may help. There is lots of good information, but it evolves out over a long back&forth thread.


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority". I knew I was on the right track :)
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: XS: Passing an external library's function a Perl XS callback
by Anonymous Monk on Aug 14, 2016 at 20:27 UTC
    What happens if you add
    PUTBACK; FREETMPS; /* free that return value */ LEAVE; /* ...and the XPUSHed "mortal" args.*/

      Do you mean within callback()? I put those lines before and after the call to the actual perl sub, but both cases still result in segfault.