Syntactic Confectionery Delight | |
PerlMonks |
perlman:perlcall2by gods (Initiate) |
on Aug 25, 1999 at 07:05 UTC ( [id://416]=perlman: print w/replies, xml ) | Need Help?? |
perlcall2Current Perl documentation can be found at perldoc.perl.org. Here is our local, out-dated (pre-5.6) version: Strategies for storing Callback Context InformationPotentially 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(cb1) ;
The single parameter
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 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
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
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(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
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
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
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
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
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 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
Alternate Stack Manipulation
Although
I have made use of only the
Most of the time the
The code below is the example given in the section Returning a list
of values recoded to use perlman:perlguts instead of
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
Creating and calling an anonymous subroutine in CAs 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 ALSOthe perlxs manpage, the perlguts manpage, the perlembed manpage
AUTHORPaul 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.
DATEVersion 1.3, 14th Apr 1997 Return to the Library |
|