Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

perlman:perlcall2

by gods (Initiate)
on Aug 25, 1999 at 07:05 UTC ( [id://416]=perlman: print w/replies, xml ) Need Help??

perlcall2

Current Perl documentation can be found at perldoc.perl.org.

Here is our local, out-dated (pre-5.6) version:


Strategies for storing Callback Context Information

Potentially one of the trickiest problems to overcome when designing a callback interface can be figuring out how to store the mapping between the C callback function and the Perl equivalent.

To help understand why this can be a real problem first consider how a callback is set up in an all C environment. Typically a C API will provide a function to register a callback. This will expect a pointer to a function as one of its parameters. Below is a call to a hypothetical function register_fatal which registers the C function to get called when a fatal error occurs.

    register_fatal(cb1) ;

The single parameter cb1 is a pointer to a function, so you must have defined cb1 in your code, say something like this

    static void
    cb1()
    {
        printf ("Fatal Error\n") ;
        exit(1) ;
    }

Now change that to call a Perl subroutine instead

    static SV * callback = (SV*)NULL;

    static void
    cb1()
    {
        dSP ;

        PUSHMARK(SP) ;

        /* Call the Perl sub to process the callback */
        perl_call_sv(callback, G_DISCARD) ;
    }

    void
    register_fatal(fn)
        SV *    fn
        CODE:
        /* Remember the Perl sub */
        if (callback == (SV*)NULL)
            callback = newSVsv(fn) ;
        else
            SvSetSV(callback, fn) ;

        /* register the callback with the external library */
        register_fatal(cb1) ;

where the Perl equivalent of register_fatal and the callback it registers, pcb1, might look like this

    # Register the sub pcb1
    register_fatal(\&pcb1) ;

    sub pcb1
    {
        die "I'm dying...\n" ;
    }

The mapping between the C callback and the Perl equivalent is stored in the global variable callback.

This will be adequate if you ever need to have only one callback registered at any time. An example could be an error handler like the code sketched out above. Remember though, repeated calls to register_fatal will replace the previously registered callback function with the new one.

Say for example you want to interface to a library which allows asynchronous file i/o. In this case you may be able to register a callback whenever a read operation has completed. To be of any use we want to be able to call separate Perl subroutines for each file that is opened. As it stands, the error handler example above would not be adequate as it allows only a single callback to be defined at any time. What we require is a means of storing the mapping between the opened file and the Perl subroutine we want to be called for that file.

Say the i/o library has a function asynch_read which associates a C function ProcessRead with a file handle fh - this assumes that it has also provided some routine to open the file and so obtain the file handle.

    asynch_read(fh, ProcessRead)

This may expect the C ProcessRead function of this form

    void
    ProcessRead(fh, buffer)
    int fh ;
    char *      buffer ;
    {
         ...
    }

To provide a Perl interface to this library we need to be able to map between the fh parameter and the Perl subroutine we want called. A hash is a convenient mechanism for storing this mapping. The code below shows a possible implementation

    static HV * Mapping = (HV*)NULL ;

    void
    asynch_read(fh, callback)
        int     fh
        SV *    callback
        CODE:
        /* If the hash doesn't already exist, create it */
        if (Mapping == (HV*)NULL)
            Mapping = newHV() ;

        /* Save the fh -> callback mapping */
        hv_store(Mapping, (char*)&fh, sizeof(fh), newSVsv(callback), 0) ;

        /* Register with the C Library */
        asynch_read(fh, asynch_read_if) ;

and asynch_read_if could look like this

    static void
    asynch_read_if(fh, buffer)
    int fh ;
    char *      buffer ;
    {
        dSP ;
        SV ** sv ;

        /* Get the callback associated with fh */
        sv =  hv_fetch(Mapping, (char*)&fh , sizeof(fh), FALSE) ;
        if (sv == (SV**)NULL)
            croak("Internal error...\n") ;

        PUSHMARK(SP) ;
        XPUSHs(sv_2mortal(newSViv(fh))) ;
        XPUSHs(sv_2mortal(newSVpv(buffer, 0))) ;
        PUTBACK ;

        /* Call the Perl sub */
        perl_call_sv(*sv, G_DISCARD) ;
    }

For completeness, here is asynch_close. This shows how to remove the entry from the hash Mapping.

    void
    asynch_close(fh)
        int     fh
        CODE:
        /* Remove the entry from the hash */
        (void) hv_delete(Mapping, (char*)&fh, sizeof(fh), G_DISCARD) ;

        /* Now call the real asynch_close */
        asynch_close(fh) ;

So the Perl interface would look like this

    sub callback1
    {
        my($handle, $buffer) = @_ ;
    }

    # Register the Perl callback
    asynch_read($fh, \&callback1) ;

    asynch_close($fh) ;

The mapping between the C callback and Perl is stored in the global hash Mapping this time. Using a hash has the distinct advantage that it allows an unlimited number of callbacks to be registered.

What if the interface provided by the C callback doesn't contain a parameter which allows the file handle to Perl subroutine mapping? Say in the asynchronous i/o package, the callback function gets passed only the buffer parameter like this

    void
    ProcessRead(buffer)
    char *      buffer ;
    {
        ...
    }

Without the file handle there is no straightforward way to map from the C callback to the Perl subroutine.

In this case a possible way around this problem is to predefine a series of C functions to act as the interface to Perl, thus

    #define MAX_CB              3
    #define NULL_HANDLE -1
    typedef void (*FnMap)() ;

    struct MapStruct {
        FnMap    Function ;
        SV *     PerlSub ;
        int      Handle ;
      } ;

    static void  fn1() ;
    static void  fn2() ;
    static void  fn3() ;

    static struct MapStruct Map [MAX_CB] =
        {
            { fn1, NULL, NULL_HANDLE },
            { fn2, NULL, NULL_HANDLE },
            { fn3, NULL, NULL_HANDLE }
        } ;

    static void
    Pcb(index, buffer)
    int index ;
    char * buffer ;
    {
        dSP ;

        PUSHMARK(SP) ;
        XPUSHs(sv_2mortal(newSVpv(buffer, 0))) ;
        PUTBACK ;

        /* Call the Perl sub */
        perl_call_sv(Map[index].PerlSub, G_DISCARD) ;
    }

    static void
    fn1(buffer)
    char * buffer ;
    {
        Pcb(0, buffer) ;
    }

    static void
    fn2(buffer)
    char * buffer ;
    {
        Pcb(1, buffer) ;
    }

    static void
    fn3(buffer)
    char * buffer ;
    {
        Pcb(2, buffer) ;
    }

    void
    array_asynch_read(fh, callback)
        int             fh
        SV *    callback
        CODE:
        int index ;
        int null_index = MAX_CB ;

        /* Find the same handle or an empty entry */
        for (index = 0 ; index < MAX_CB ; ++index)
        {
            if (Map[index].Handle == fh)
                break ;

            if (Map[index].Handle == NULL_HANDLE)
                null_index = index ;
        }

        if (index == MAX_CB && null_index == MAX_CB)
            croak ("Too many callback functions registered\n") ;

        if (index == MAX_CB)
            index = null_index ;

        /* Save the file handle */
        Map[index].Handle = fh ;

        /* Remember the Perl sub */
        if (Map[index].PerlSub == (SV*)NULL)
            Map[index].PerlSub = newSVsv(callback) ;
        else
            SvSetSV(Map[index].PerlSub, callback) ;

        asynch_read(fh, Map[index].Function) ;

    void
    array_asynch_close(fh)
        int     fh
        CODE:
        int index ;

        /* Find the file handle */
        for (index = 0; index < MAX_CB ; ++ index)
            if (Map[index].Handle == fh)
                break ;

        if (index == MAX_CB)
            croak ("could not close fh %d\n", fh) ;

        Map[index].Handle = NULL_HANDLE ;
        SvREFCNT_dec(Map[index].PerlSub) ;
        Map[index].PerlSub = (SV*)NULL ;

        asynch_close(fh) ;

In this case the functions fn1, fn2, and fn3 are used to remember the Perl subroutine to be called. Each of the functions holds a separate hard-wired index which is used in the function Pcb to access the Map array and actually call the Perl subroutine.

There are some obvious disadvantages with this technique.

Firstly, the code is considerably more complex than with the previous example.

Secondly, there is a hard-wired limit (in this case 3) to the number of callbacks that can exist simultaneously. The only way to increase the limit is by modifying the code to add more functions and then recompiling. None the less, as long as the number of functions is chosen with some care, it is still a workable solution and in some cases is the only one available.

To summarize, here are a number of possible methods for you to consider for storing the mapping between C and the Perl callback

  1. Ignore the problem - Allow only 1 callback

    For a lot of situations, like interfacing to an error handler, this may be a perfectly adequate solution.

  2. Create a sequence of callbacks - hard wired limit If it is impossible to tell from the parameters passed back from the C callback what the context is, then you may need to create a sequence of C callback interface functions, and store pointers to each in an array.

  3. Use a parameter to map to the Perl callback A hash is an ideal mechanism to store the mapping between C and Perl.


Alternate Stack Manipulation

Although I have made use of only the POP* macros to access values returned from Perl subroutines, it is also possible to bypass these macros and read the stack using the perlman:perlguts macro (See the perlxs manpage for a full description of the perlman:perlguts macro).

Most of the time the POP* macros should be adequate, the main problem with them is that they force you to process the returned values in sequence. This may not be the most suitable way to process the values in some cases. What we want is to be able to access the stack in a random order. The perlman:perlguts macro as used when coding an XSUB is ideal for this purpose.

The code below is the example given in the section Returning a list of values recoded to use perlman:perlguts instead of POP*.

    static void
    call_AddSubtract2(a, b)
    int a ;
    int b ;
    {
        dSP ;
        I32 ax ;
        int count ;

        ENTER ;
        SAVETMPS;

        PUSHMARK(SP) ;
        XPUSHs(sv_2mortal(newSViv(a)));
        XPUSHs(sv_2mortal(newSViv(b)));
        PUTBACK ;

        count = perl_call_pv("AddSubtract", G_ARRAY);

        SPAGAIN ;
        SP -= count ;
        ax = (SP - PL_stack_base) + 1 ;

        if (count != 2)
            croak("Big trouble\n") ;

        printf ("%d + %d = %d\n", a, b, SvIV(ST(0))) ;
        printf ("%d - %d = %d\n", a, b, SvIV(ST(1))) ;

        PUTBACK ;
        FREETMPS ;
        LEAVE ;
    }

Notes

  1. .

    Notice that it was necessary to define the variable ax. This is because the perlman:perlguts macro expects it to exist. If we were in an XSUB it would not be necessary to define ax as it is already defined for you.

  2. . The code

            SPAGAIN ;
            SP -= count ;
            ax = (SP - PL_stack_base) + 1 ;
    

    sets the stack up so that we can use the perlman:perlguts macro.

  3. . Unlike the original coding of this example, the returned values are not accessed in reverse order. So perlman:perlguts refers to the first value returned by the Perl subroutine and perlman:perlguts refers to the last.


Creating and calling an anonymous subroutine in C

As we've already shown, perlman:perlguts can be used to invoke an anonymous subroutine. However, our example showed how Perl script invoking an XSUB to preform this operation. Let's see how it can be done inside our C code:

 ...

 SV *cvrv = perl_eval_pv("sub { print 'You will not find me cluttering any namespace!' }", TRUE);

 ...

 perl_call_sv(cvrv, G_VOID|G_NOARGS);

perlman:perlguts is used to compile the anonymous subroutine, which will be the return value as well (read more about perlman:perlguts in perlman:perlguts). Once this code reference is in hand, it can be mixed in with all the previous examples we've shown.


SEE ALSO

the perlxs manpage, the perlguts manpage, the perlembed manpage


AUTHOR

Paul Marquess <pmarquess@bfsec.bt.co.uk>

Special thanks to the following people who assisted in the creation of the document.

Jeff Okamoto, Tim Bunce, Nick Gianniotis, Steve Kelem, Gurusamy Sarathy and Larry Wall.


DATE

Version 1.3, 14th Apr 1997



Return to the Library
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (5)
As of 2024-03-29 14:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found