| /* -*-C-*- |
| * Perl gateway to wine API calls |
| * |
| * Copyright 2001 John F Sturtz for Codeweavers |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include "config.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "windef.h" |
| |
| #include <EXTERN.h> |
| #include <perl.h> |
| #include <XSUB.h> |
| |
| #undef WORD |
| #include "winbase.h" |
| |
| /* API return type constants */ |
| enum ret_type |
| { |
| RET_VOID = 0, |
| RET_INT = 1, |
| RET_WORD = 2, |
| RET_PTR = 3, |
| RET_STR = 4 |
| }; |
| |
| /* max arguments for a function call */ |
| #define MAX_ARGS 16 |
| |
| extern unsigned long perl_call_wine |
| ( |
| FARPROC function, |
| int n_args, |
| unsigned long *args, |
| unsigned int *last_error, |
| int debug |
| ); |
| |
| /* Thunk type definitions */ |
| |
| #ifdef __i386__ |
| #pragma pack(1) |
| struct thunk |
| { |
| BYTE pushl; |
| BYTE movl[2]; |
| BYTE leal_args[3]; |
| BYTE pushl_args; |
| BYTE pushl_addr; |
| BYTE *args_ptr; |
| BYTE pushl_nb_args; |
| BYTE nb_args; |
| BYTE pushl_ref; |
| SV *code_ref; |
| BYTE call; |
| void *func; |
| BYTE leave; |
| BYTE ret; |
| short arg_size; |
| BYTE arg_types[MAX_ARGS]; |
| }; |
| #pragma pack(4) |
| #else |
| #error You must implement the callback thunk for your CPU |
| #endif |
| |
| /*-------------------------------------------------------------- |
| | This contains most of the machine instructions necessary to |
| | implement the thunk. All the thunk does is turn around and |
| | call function callback_bridge(), which is defined in |
| | winetest.c. |
| | |
| | The data from this static thunk can just be copied directly |
| | into the thunk allocated dynamically below. That fills in |
| | most of it, but a couple values need to be filled in after |
| | the allocation, at run time: |
| | |
| | 1) The pointer to the thunk's data area, which we |
| | don't know yet, because we haven't allocated it |
| | yet ... |
| | |
| | 2) The address of the function to call. We know the |
| | address of the function [callback_bridge()], but |
| | the value filled into the thunk is an address |
| | relative to the thunk itself, so we can't fill it |
| | in until we've allocated the actual thunk. |
| --------------------------------------------------------------*/ |
| static const struct thunk thunk_template = |
| { |
| /* pushl %ebp */ 0x55, |
| /* movl %esp,%ebp */ { 0x89, 0xe5 }, |
| /* leal 8(%ebp),%edx */ { 0x8d, 0x55, 0x08 }, |
| /* pushl %edx */ 0x52, |
| /* pushl (data addr) */ 0x68, NULL, |
| /* pushl (nb_args) */ 0x6a, 0, |
| /* pushl (code ref) */ 0x68, NULL, |
| /* call (func) */ 0xe8, NULL, |
| /* leave */ 0xc9, |
| /* ret $arg_size */ 0xc2, 0, |
| /* arg_types */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } |
| }; |
| |
| |
| /*---------------------------------------------------------------------- |
| | Function: convert_value | |
| | -------------------------------------------------------------------- | |
| | Purpose: Convert a C value to a Perl value | |
| | | |
| | Parameters: type -- constant specifying type of value | |
| | val -- value to convert | |
| | | |
| | Returns: Perl SV * | |
| ----------------------------------------------------------------------*/ |
| static SV *convert_value( enum ret_type type, unsigned long val ) |
| { |
| switch (type) |
| { |
| case RET_VOID: return &PL_sv_undef; |
| case RET_INT: return sv_2mortal( newSViv ((int) val )); |
| case RET_WORD: return sv_2mortal( newSViv ((int) val & 0xffff )); |
| case RET_PTR: return sv_2mortal( newSViv ((int) val )); |
| case RET_STR: return sv_2mortal( newSVpv ((char *) val, 0 )); |
| } |
| croak ("Bad return type %d", type); |
| return &PL_sv_undef; |
| } |
| |
| |
| /*---------------------------------------------------------------------- |
| | Function: callback_bridge | |
| | -------------------------------------------------------------------- | |
| | Purpose: Central pass-through point for Wine API callbacks | |
| | | |
| | Wine API callback thunks are set up so that they call this | |
| | function, which turns around and calls the user's declared | |
| | Perl callback sub. | |
| | | |
| | Parameters: data -- pointer to thunk data area | |
| | args -- array of args passed from Wine API to callback | |
| | | |
| | Returns: Whatever the Perl sub returns | |
| ----------------------------------------------------------------------*/ |
| static int callback_bridge( SV *callback_ref, int nb_args, BYTE arg_types[], unsigned long args[] ) |
| { |
| /* Locals */ |
| int i, n; |
| SV *sv; |
| |
| int r = 0; |
| |
| /* Perl/C interface voodoo */ |
| dSP; |
| ENTER; |
| SAVETMPS; |
| PUSHMARK(sp); |
| |
| /* Push args on stack, according to type */ |
| for (i = 0; i < nb_args; i++) |
| { |
| sv = convert_value (arg_types[i], args[i]); |
| PUSHs (sv); |
| } |
| PUTBACK; |
| |
| /* Call Perl sub */ |
| n = perl_call_sv (callback_ref, G_SCALAR); |
| |
| /* Nab return value */ |
| SPAGAIN; |
| if (n == 1) |
| { |
| r = POPi; |
| } |
| PUTBACK; |
| FREETMPS; |
| LEAVE; |
| |
| /* [todo] Pass through Perl sub return value */ |
| return (r); |
| } |
| |
| |
| /*---------------------------------------------------------------------- |
| | XS module | |
| | | |
| | | |
| ----------------------------------------------------------------------*/ |
| MODULE = wine PACKAGE = wine |
| |
| |
| # -------------------------------------------------------------------- |
| # Function: call_wine_API |
| # -------------------------------------------------------------------- |
| # Purpose: Call perl_call_wine(), which calls a wine API function |
| # |
| # Parameters: function -- API function to call |
| # ret_type -- return type |
| # debug -- debug flag |
| # ... -- args to pass to API function |
| # |
| # Returns: list containing 2 elements: the last error code and the |
| # value returned by the API function |
| # -------------------------------------------------------------------- |
| void |
| call_wine_API(function, ret_type, debug, ...) |
| unsigned long function; |
| int ret_type; |
| int debug; |
| |
| PROTOTYPE: $$$@ |
| |
| PPCODE: |
| /*-------------------------------------------------------------- |
| | Begin call_wine_API |
| --------------------------------------------------------------*/ |
| |
| /* Local types */ |
| struct arg |
| { |
| int ival; |
| void *pval; |
| }; |
| |
| /* Locals */ |
| int n_fixed = 3; |
| int n_args = (items - n_fixed); |
| struct arg args[MAX_ARGS+1]; |
| unsigned long f_args[MAX_ARGS+1]; |
| unsigned int i, n; |
| unsigned int last_error = 0xdeadbeef; |
| char *p; |
| SV *sv; |
| unsigned long r; |
| |
| if (n_args > MAX_ARGS) croak("Too many arguments"); |
| |
| /*-------------------------------------------------------------- |
| | Prepare function args |
| --------------------------------------------------------------*/ |
| if (debug > 1) |
| { |
| fprintf( stderr, " [wine.xs/call_wine_API()]\n"); |
| } |
| for (i = 0; (i < n_args); i++) |
| { |
| sv = ST (n_fixed + i); |
| args[i].pval = NULL; |
| |
| if (! SvOK (sv)) |
| continue; |
| |
| /*-------------------------------------------------------------- |
| | Ref |
| --------------------------------------------------------------*/ |
| if (SvROK (sv)) |
| { |
| sv = SvRV (sv); |
| |
| /*-------------------------------------------------------------- |
| | Integer ref -- pass address of value |
| --------------------------------------------------------------*/ |
| if (SvIOK (sv)) |
| { |
| args[i].ival = SvIV (sv); |
| f_args[i] = (unsigned long) &(args[i].ival); |
| if (debug > 1) |
| { |
| fprintf( stderr, " [RV->IV] 0x%lx\n", f_args[i]); |
| } |
| } |
| |
| /*-------------------------------------------------------------- |
| | Number ref -- convert and pass address of value |
| --------------------------------------------------------------*/ |
| else if (SvNOK (sv)) |
| { |
| args[i].ival = (unsigned long) SvNV (sv); |
| f_args[i] = (unsigned long) &(args[i].ival); |
| if (debug > 1) |
| { |
| fprintf( stderr, " [RV->NV] 0x%lx\n", f_args[i]); |
| } |
| } |
| |
| /*-------------------------------------------------------------- |
| | String ref -- pass pointer |
| --------------------------------------------------------------*/ |
| else if (SvPOK (sv)) |
| { |
| f_args[i] = (unsigned long) ((char *) SvPV (sv, PL_na)); |
| if (debug > 1) |
| { |
| fprintf( stderr, " [RV->PV] 0x%lx\n", f_args[i]); |
| } |
| } |
| } |
| |
| /*-------------------------------------------------------------- |
| | Scalar |
| --------------------------------------------------------------*/ |
| else |
| { |
| |
| /*-------------------------------------------------------------- |
| | Integer -- pass value |
| --------------------------------------------------------------*/ |
| if (SvIOK (sv)) |
| { |
| f_args[i] = (unsigned long) SvIV (sv); |
| if (debug > 1) |
| { |
| fprintf( stderr, " [IV] %ld (0x%lx)\n", f_args[i], f_args[i]); |
| } |
| } |
| |
| /*-------------------------------------------------------------- |
| | Number -- convert and pass value |
| --------------------------------------------------------------*/ |
| else if (SvNOK (sv)) |
| { |
| f_args[i] = (unsigned long) SvNV (sv); |
| if (debug > 1) |
| { |
| fprintf( stderr, " [NV] %ld (0x%lx)\n", f_args[i], f_args[i]); |
| } |
| } |
| |
| /*-------------------------------------------------------------- |
| | String -- pass pointer to copy |
| --------------------------------------------------------------*/ |
| else if (SvPOK (sv)) |
| { |
| p = SvPV (sv, n); |
| if ((args[i].pval = malloc( n+2 ))) |
| { |
| memcpy (args[i].pval, p, n); |
| ((char *)(args[i].pval))[n] = 0; /* add final NULL */ |
| ((char *)(args[i].pval))[n+1] = 0; /* and another one for Unicode too */ |
| f_args[i] = (unsigned long) args[i].pval; |
| if (debug > 1) |
| { |
| fprintf( stderr, " [PV] 0x%lx\n", f_args[i]); |
| } |
| } |
| } |
| } |
| |
| } /* end for */ |
| |
| /*-------------------------------------------------------------- |
| | Here we go |
| --------------------------------------------------------------*/ |
| r = perl_call_wine( (FARPROC)function, n_args, f_args, &last_error, debug ); |
| |
| /*-------------------------------------------------------------- |
| | Handle modified parameter values |
| | |
| | There are four possibilities for parameter values: |
| | |
| | 1) integer value |
| | 2) string value |
| | 3) ref to integer value |
| | 4) ref to string value |
| | |
| | In cases 1 and 2, the intent is that the values won't be |
| | modified, because they're not passed by ref. So we leave |
| | them alone here. |
| | |
| | In case 4, the address of the actual string buffer has |
| | already been passed to the wine API function, which had |
| | opportunity to modify it if it wanted to. So again, we |
| | don't have anything to do here. |
| | |
| | The case we need to handle is case 3. For integers passed |
| | by ref, we created a local containing the initial value, |
| | and passed its address to the wine API function, which |
| | (potentially) modified it. Now we have to copy the |
| | (potentially) new value back to the Perl variable passed |
| | in, using sv_setiv(). (Which will take fewer lines of code |
| | to do than it took lines of comment to describe ...) |
| --------------------------------------------------------------*/ |
| for (i = 0; (i < n_args); i++) |
| { |
| sv = ST (n_fixed + i); |
| if (! SvOK (sv)) |
| continue; |
| if (SvROK (sv) && (sv = SvRV (sv)) && SvIOK (sv)) |
| { |
| sv_setiv (sv, args[i].ival); |
| } |
| } |
| |
| /*-------------------------------------------------------------- |
| | Put appropriate return value on the stack for Perl to pick |
| | up |
| --------------------------------------------------------------*/ |
| EXTEND(SP,2); |
| if (last_error != 0xdeadbeef) PUSHs(sv_2mortal(newSViv(last_error))); |
| else PUSHs( &PL_sv_undef ); |
| PUSHs (convert_value (ret_type, r)); |
| |
| /*-------------------------------------------------------------- |
| | Free up allocated memory |
| --------------------------------------------------------------*/ |
| for (i = 0; (i < n_args); i++) |
| { |
| if (args[i].pval) free(args[i].pval); |
| } |
| |
| |
| # -------------------------------------------------------------------- |
| # Function: load_library |
| # -------------------------------------------------------------------- |
| # Purpose: Load a Wine library |
| # |
| # Parameters: module -- module (dll) to load |
| # |
| # Returns: module handle |
| # -------------------------------------------------------------------- |
| void |
| load_library(module) |
| char *module; |
| PROTOTYPE: $ |
| |
| PPCODE: |
| ST(0) = newSViv( (I32)LoadLibraryA(module) ); |
| XSRETURN(1); |
| |
| |
| # -------------------------------------------------------------------- |
| # Function: get_proc_address |
| # -------------------------------------------------------------------- |
| # Purpose: Retrive a function address |
| # |
| # Parameters: module -- module handle |
| # -------------------------------------------------------------------- |
| void |
| get_proc_address(module,func) |
| unsigned long module; |
| char *func; |
| PROTOTYPE: $$ |
| |
| PPCODE: |
| ST(0) = newSViv( (I32)GetProcAddress( (HMODULE)module, func ) ); |
| XSRETURN(1); |
| |
| |
| # -------------------------------------------------------------------- |
| # Function: alloc_thunk |
| # -------------------------------------------------------------------- |
| # Purpose: Allocate a thunk for a wine API callback |
| # |
| # This is used when a Wine API function is called from Perl, and |
| # that API function takes a callback as one of its parameters. |
| # |
| # The Wine API function, of course, must be passed the address of |
| # a C function as the callback. But if the API is called from Perl, |
| # we want the user to be able to specify a Perl sub as the callback, |
| # and have control returned there each time the callback is called. |
| # |
| # This function takes a code ref to a Perl sub as one of its |
| # arguments. It then creates a unique C function (a thunk) on the |
| # fly, which can be passed to the Wine API function as its callback. |
| # |
| # The thunk has its own data area (as thunks are wont to do); one |
| # of the things stashed there is aforementioned Perl code ref. So |
| # the sequence of events is as follows: |
| # |
| # 1) From Perl, user calls alloc_callback(), passing a ref |
| # to a Perl sub to use as the callback. |
| # |
| # 2) alloc_callback() calls this routine. This routine |
| # creates a thunk, and stashes the above code ref in |
| # it. This function then returns a pointer to the thunk |
| # to Perl. |
| # |
| # 3) From Perl, user calls Wine API function. As the parameter |
| # which is supposed to be the address of the callback, the |
| # user passes the pointer to the thunk allocated above. |
| # |
| # 4) The Wine API function gets called. It periodically calls |
| # the callback, which executes the thunk. |
| # |
| # 5) Each time the thunk is executed, it calls callback_bridge() |
| # (defined in winetest.c). |
| # |
| # 6) callback_bridge() fishes the Perl code ref out of the |
| # thunk data area and calls the Perl callback. |
| # |
| # Voila. The Perl callback gets called each time the Wine API |
| # function calls its callback. |
| # |
| # Parameters: [todo] Parameters ... |
| # |
| # Returns: Pointer to thunk |
| # -------------------------------------------------------------------- |
| void |
| alloc_thunk(...) |
| |
| PPCODE: |
| |
| /* Locals */ |
| struct thunk *thunk; |
| int i; |
| |
| /* Allocate the thunk */ |
| if (!(thunk = malloc( sizeof(*thunk) ))) croak( "Out of memory" ); |
| |
| (*thunk) = thunk_template; |
| thunk->args_ptr = thunk->arg_types; |
| thunk->nb_args = items - 1; |
| thunk->code_ref = SvRV (ST (0)); |
| thunk->func = (void *)((char *) callback_bridge - (char *) &thunk->leave); |
| thunk->arg_size = thunk->nb_args * sizeof(int); |
| |
| /* Stash callback arg types */ |
| for (i = 1; i < items; i++) thunk->arg_types[i - 1] = SvIV (ST (i)); |
| |
| /*-------------------------------------------------------------- |
| | Push the address of the thunk on the stack for return |
| | |
| | [todo] We need to free up the memory allocated somehow ... |
| --------------------------------------------------------------*/ |
| ST (0) = newSViv ((I32) thunk); |
| XSRETURN (1); |