| # -------------------------------------------------------------------- |
| # Module: wine.pm |
| # |
| # Purpose: Module to supply wrapper around and support for gateway to |
| # Windows API functions |
| # |
| # 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 |
| # -------------------------------------------------------------------- |
| |
| package wine; |
| |
| use strict; |
| use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $AUTOLOAD $todo_level |
| $successes $failures $todo_successes $todo_failures |
| %return_types %prototypes %loaded_modules); |
| |
| require Exporter; |
| |
| @ISA = qw(Exporter); |
| |
| # Items to export into callers namespace by default. Note: do not export |
| # names by default without a very good reason. Use EXPORT_OK instead. |
| # Do not simply export all your public functions/methods/constants. |
| @EXPORT = qw( |
| AUTOLOAD |
| alloc_callback |
| assert |
| hd |
| ok |
| todo |
| todo_wine |
| trace |
| wc |
| wclen |
| ); |
| |
| $VERSION = '0.01'; |
| bootstrap wine $VERSION; |
| |
| # Global variables |
| $wine::err = 0; |
| $wine::exit_status = 0; |
| $wine::debug = defined($ENV{WINETEST_DEBUG}) ? $ENV{WINETEST_DEBUG} : 1; |
| $wine::platform = defined($ENV{WINETEST_PLATFORM}) ? $ENV{WINETEST_PLATFORM} : "windows"; |
| |
| $todo_level = 0; |
| $successes = 0; |
| $failures = 0; |
| $todo_successes = 0; |
| $todo_failures = 0; |
| %loaded_modules = (); |
| |
| # -------------------------------------------------------------- |
| # | Return-type constants | |
| # | | |
| # | [todo] I think there's a way to define these in a C | |
| # | header file, so that both the C functions in the | |
| # | XS module and the Perl routines in the .pm have | |
| # | access to them. But I haven't worked it out | |
| # | yet ... | |
| # -------------------------------------------------------------- |
| %return_types = ( |
| "void" => 0, |
| "int" => 1, "long" => 1, |
| "word" => 2, |
| "ptr" => 3, |
| "str" => 4, "wstr" => 4 |
| ); |
| |
| |
| # ------------------------------------------------------------------------ |
| # | Sub: AUTOLOAD | |
| # | -------------------------------------------------------------------- | |
| # | Purpose: Used to catch calls to undefined routines | |
| # | | |
| # | Any routine which is called and not defined is assumed to be | |
| # | a call to the Wine API function of the same name. We trans- | |
| # | late it into a call to the call() subroutine, with FUNCTION | |
| # | set to the function invoked and all other args passed thru. | |
| # ------------------------------------------------------------------------ |
| sub AUTOLOAD |
| { |
| # -------------------------------------------------------------- |
| # | Figure out who we are | |
| # -------------------------------------------------------------- |
| my ($pkg, $func) = (split /::/, $AUTOLOAD)[0,1]; |
| |
| # -------------------------------------------------------------- |
| # | Any function that is in the @EXPORT array is passed thru | |
| # | to AutoLoader to pick up the appropriate XS extension | |
| # -------------------------------------------------------------- |
| if (grep ($_ eq $func, @EXPORT)) |
| { |
| $AutoLoader::AUTOLOAD = $AUTOLOAD; |
| goto &AutoLoader::AUTOLOAD; |
| } |
| |
| # -------------------------------------------------------------- |
| # | Ignore this | |
| # -------------------------------------------------------------- |
| return |
| if ($func eq 'DESTROY'); |
| |
| # -------------------------------------------------------------- |
| # | Otherwise, assume any undefined method is the name of a | |
| # | wine API call, and all the args are to be passed through | |
| # -------------------------------------------------------------- |
| if (defined($prototypes{$func})) |
| { |
| return call( $func, @_ ); |
| } |
| die "Function '$func' not declared"; |
| } # End AUTOLOAD |
| |
| |
| |
| # ------------------------------------------------------------------------ |
| # | Sub: call | |
| # | -------------------------------------------------------------------- | |
| # | Purpose: Call a wine API function | |
| # | | |
| # | Usage: call FUNCTION, [ARGS ...] |
| # | | |
| # | Returns: value returned by API function called | |
| # ------------------------------------------------------------------------ |
| sub call($@) |
| { |
| my ($function,@args) = @_; |
| my ($module,$funcptr,$ret_type,$arg_types) = @{$prototypes{$function}}; |
| |
| unless ($funcptr) |
| { |
| my $handle = $loaded_modules{$module}; |
| $funcptr = get_proc_address( $handle, $function ) or die "Could not get address for $module.$function"; |
| ${$prototypes{$function}}[1] = $funcptr; |
| } |
| |
| if ($wine::debug > 1) |
| { |
| print STDERR "==== Call $function("; |
| for (@args) |
| { |
| print STDERR " ", +(ref () ? ("(" . ${$_} . ")") : "$_"); |
| } |
| print STDERR " " if (scalar @args); |
| print STDERR ")\n"; |
| } |
| |
| # Check and translate args before call |
| my @args2; |
| if (defined($arg_types)) { |
| my @arg_types = @$arg_types; |
| |
| if($#args != $#arg_types) { |
| die "$function: Wrong number of arguments, expected " . |
| ($#arg_types + 1) . ", got " . ($#args + 1) . "\n"; |
| } |
| |
| while (defined(my $arg = shift @args) && |
| defined(my $arg_type = shift @arg_types)) |
| { |
| if($arg_type == 1 || $arg_type == 2) { # int || word |
| $arg = int($arg); |
| } |
| push @args2, $arg; |
| } |
| } else { |
| @args2 = @args; |
| } |
| |
| # Now call call_wine_API(), which will turn around and call |
| # the appropriate wine API function. |
| my ($err,$r) = call_wine_API( $funcptr, $ret_type, $wine::debug-1, @args2 ); |
| |
| if ($wine::debug > 1) |
| { |
| print STDERR "==== Ret $function()"; |
| if (defined($r)) { printf STDERR " ret=0x%x", $r; } |
| if (defined($err)) { printf STDERR " err=%d", $err; } |
| print STDERR "\n"; |
| } |
| |
| # Pass the return value back |
| $wine::err = $err; |
| return ($r); |
| } |
| |
| |
| # ---------------------------------------------------------------------- |
| # | Subroutine: declare |
| # ---------------------------------------------------------------------- |
| sub declare($%) |
| { |
| my ($module, %list) = @_; |
| my ($handle, $func); |
| |
| if (defined($loaded_modules{$module})) |
| { |
| $handle = $loaded_modules{$module}; |
| } |
| else |
| { |
| $handle = load_library($module) or die "Could not load '$module'"; |
| $loaded_modules{$module} = $handle; |
| } |
| |
| foreach $func (keys %list) |
| { |
| if(ref($list{$func}) eq "ARRAY") { |
| my ($return_type, $argument_types) = @{$list{$func}}; |
| |
| my $ret_type = $return_types{$return_type}; |
| my $arg_types = [map { $return_types{$_} } @$argument_types]; |
| |
| $prototypes{$func} = [ $module, 0, $ret_type, $arg_types ]; |
| } else { |
| my $ret_type = $return_types{$list{$func}}; |
| |
| $prototypes{$func} = [ $module, 0, $ret_type ]; |
| } |
| } |
| } |
| |
| |
| # ------------------------------------------------------------------------ |
| # | Sub: alloc_callback | |
| # | -------------------------------------------------------------------- | |
| # | Purpose: Allocate a thunk for a Wine API callback function. | |
| # | | |
| # | Basically a thin wrapper over alloc_thunk(); see wine.xs for | |
| # | details ... | |
| # | | |
| # | Usage: alloc_callback SUB_REF, [ ARGS_TYPES ... ] | |
| # | | |
| # | Returns: Pointer to thunk allocated (as an integer value) | |
| # | | |
| # | The returned value is just a raw pointer to a block of memory | |
| # | allocated by the C code (cast into a Perl integer). It isn't | |
| # | really suitable for anything but to be passed to a wine API | |
| # | function ... | |
| # ------------------------------------------------------------------------ |
| sub alloc_callback($@) |
| { |
| # ---------------------------------------------- |
| # | Locals | |
| # | | |
| # | [todo] Check arg types | |
| # ---------------------------------------------- |
| my $sub_ref = shift; |
| my @callback_arg_types = @_; |
| |
| # [todo] Check args |
| # [todo] Some way of specifying args passed to callback |
| |
| # -------------------------------------------------------------- |
| # | Convert arg types to integers | |
| # -------------------------------------------------------------- |
| map { $_ = $return_types{$_} } @callback_arg_types; |
| |
| # -------------------------------------------------------------- |
| # | Pass thru to alloc_thunk() | |
| # -------------------------------------------------------------- |
| return alloc_thunk ($sub_ref, @callback_arg_types); |
| } |
| |
| |
| # ---------------------------------------------------------------------- |
| # | Subroutine: hd | |
| # | | |
| # | Purpose: Display a hex dump of a string | |
| # | | |
| # | Usage: hd STR | |
| # | Usage: hd STR, LENGTH | |
| # | | |
| # | Returns: (none) | |
| # ---------------------------------------------------------------------- |
| sub hd($;$) |
| { |
| # Locals |
| my ($buf, $length); |
| my $first; |
| my ($str1, $str2, $str, $t); |
| my ($c, $x); |
| |
| # Begin sub hd |
| |
| # -------------------------------------------------------------- |
| # | Get args; if no BUF specified, blow | |
| # -------------------------------------------------------------- |
| $buf = shift; |
| $length = (shift or length ($buf)); |
| return |
| if ((not defined ($buf)) || ($length <= 0)); |
| |
| # -------------------------------------------------------------- |
| # | Initialize | |
| # -------------------------------------------------------------- |
| $first = 1; |
| $str1 = "00000:"; |
| $str2 = ""; |
| |
| # -------------------------------------------------------------- |
| # | For each character | |
| # -------------------------------------------------------------- |
| for (0 .. ($length - 1)) |
| { |
| $c = substr ($buf, $_, 1); |
| $x = sprintf ("%02x", ord ($c)); |
| $str1 .= (" " . $x); |
| $str2 .= (((ord ($c) >= 33) && (ord ($c) <= 126)) ? $c : "."); |
| |
| # -------------------------------------------------------------- |
| # | Every group of 4, add an extra space | |
| # -------------------------------------------------------------- |
| if |
| ( |
| ((($_ + 1) % 16) == 4) || |
| ((($_ + 1) % 16) == 12) |
| ) |
| { |
| $str1 .= " "; |
| $str2 .= " "; |
| } |
| |
| # -------------------------------------------------------------- |
| # | Every group of 8, add a '-' | |
| # -------------------------------------------------------------- |
| elsif |
| ( |
| ((($_ + 1) % 16) == 8) |
| ) |
| { |
| $str1 .= " -"; |
| $str2 .= " "; |
| } |
| |
| # -------------------------------------------------------------- |
| # | Every group of 16, dump | |
| # -------------------------------------------------------------- |
| if |
| ( |
| ((($_ + 1) % 16) == 0) || |
| ($_ == ($length - 1)) |
| ) |
| { |
| $str = sprintf ("%-64s%s", $str1, $str2); |
| if ($first) |
| { |
| $t = ("-" x length ($str)); |
| print " $t\n"; |
| print " | $length bytes\n"; |
| print " $t\n"; |
| $first = 0; |
| } |
| print " $str\n"; |
| $str1 = sprintf ("%05d:", ($_ + 1)); |
| $str2 = ""; |
| if ($_ == ($length - 1)) |
| { |
| print " $t\n"; |
| } |
| } |
| |
| } # end for |
| |
| |
| # -------------------------------------------------------------- |
| # | Exit point | |
| # -------------------------------------------------------------- |
| return; |
| |
| } # End sub hd |
| |
| |
| |
| # ---------------------------------------------------------------------- |
| # | Subroutine: wc | |
| # | | |
| # | Purpose: Generate unicode string | |
| # | | |
| # | Usage: wc ASCII_STRING | |
| # | | |
| # | Returns: string generated | |
| # ---------------------------------------------------------------------- |
| sub wc($) |
| { |
| return pack("S*",unpack("C*",shift)); |
| } # End sub wc |
| |
| |
| |
| # ---------------------------------------------------------------------- |
| # | Subroutine: wclen | |
| # | | |
| # | Purpose: Return length of unicode string | |
| # | | |
| # | Usage: wclen UNICODE_STRING | |
| # | | |
| # | Returns: string generated | |
| # ---------------------------------------------------------------------- |
| sub wclen($) |
| { |
| # Locals |
| my $str = shift; |
| my ($c1, $c2, $n); |
| |
| # Begin sub wclen |
| |
| $n = 0; |
| while (length ($str) > 0) |
| { |
| $c1 = substr ($str, 0, 1, ""); |
| $c2 = substr ($str, 0, 1, ""); |
| (($c1 eq "\x00") && ($c2 eq "\x00")) ? last : $n++; |
| } |
| |
| return ($n); |
| |
| } # End sub wclen |
| |
| |
| |
| # ---------------------------------------------------------------------- |
| # Subroutine: ok |
| # |
| # Purpose: Print warning if something fails |
| # |
| # Usage: ok CONDITION [DESCRIPTION] |
| # |
| # Returns: (none) |
| # ---------------------------------------------------------------------- |
| sub ok($;$) |
| { |
| my $assertion = shift; |
| my $description = shift; |
| my ($filename, $line) = (caller (0))[1,2]; |
| if ($todo_level) |
| { |
| if ($assertion) |
| { |
| print STDERR ("$filename:$line: Test succeeded inside todo block" . |
| ($description ? ": $description" : "") . "\n"); |
| $todo_failures++; |
| } |
| else { $todo_successes++; } |
| } |
| else |
| { |
| if (!$assertion) |
| { |
| print STDERR ("$filename:$line: Test failed" . |
| ($description ? ": $description" : "") . "\n"); |
| $failures++; |
| } |
| else { $successes++; } |
| } |
| } |
| |
| |
| # ---------------------------------------------------------------------- |
| # Subroutine: assert |
| # |
| # Purpose: Print error and die if something fails |
| # |
| # Usage: assert CONDITION [DESCRIPTION] |
| # |
| # Returns: (none) |
| # ---------------------------------------------------------------------- |
| sub assert($;$) |
| { |
| my $assertion = shift; |
| my $description = shift; |
| my ($filename, $line) = (caller (0))[1,2]; |
| unless ($assertion) |
| { |
| die ("$filename:$line: Assertion failed" . ($description ? ": $description" : "") . "\n"); |
| } |
| } |
| |
| |
| # ---------------------------------------------------------------------- |
| # Subroutine: trace |
| # |
| # Purpose: Print debugging traces |
| # |
| # Usage: trace format [arguments] |
| # ---------------------------------------------------------------------- |
| sub trace($@) |
| { |
| return unless ($wine::debug > 0); |
| my $format = shift; |
| my $filename = (caller(0))[1]; |
| $filename =~ s!.*/!!; |
| printf "trace:$filename $format", @_; |
| } |
| |
| # ---------------------------------------------------------------------- |
| # Subroutine: todo |
| # |
| # Purpose: Specify a block of code as todo for a given platform |
| # |
| # Usage: todo name coderef |
| # ---------------------------------------------------------------------- |
| sub todo($$) |
| { |
| my ($platform,$code) = @_; |
| if ($wine::platform eq $platform) |
| { |
| $todo_level++; |
| eval &$code; |
| $todo_level--; |
| } |
| else |
| { |
| eval &$code; |
| } |
| } |
| |
| |
| # ---------------------------------------------------------------------- |
| # Subroutine: todo_wine |
| # |
| # Purpose: Specify a block of test as todo for the Wine platform |
| # |
| # Usage: todo_wine { code } |
| # ---------------------------------------------------------------------- |
| sub todo_wine(&) |
| { |
| my $code = shift; |
| todo( "wine", $code ); |
| } |
| |
| |
| # ---------------------------------------------------------------------- |
| # Subroutine: END |
| # |
| # Purpose: Called at the end of execution, print results summary |
| # ---------------------------------------------------------------------- |
| END |
| { |
| return if $?; # got some other error already |
| if ($wine::debug > 0) |
| { |
| my $filename = (caller(0))[1]; |
| printf STDERR ("%s: %d tests executed, %d marked as todo, %d %s.\n", |
| $filename, $successes + $failures + $todo_successes + $todo_failures, |
| $todo_successes, $failures + $todo_failures, |
| ($failures + $todo_failures != 1) ? "failures" : "failure" ); |
| } |
| $? = ($failures + $todo_failures < 255) ? $failures + $todo_failures : 255; |
| } |
| |
| |
| # Autoload methods go after =cut, and are processed by the autosplit program. |
| 1; |
| __END__ |
| |
| |
| |
| # ------------------------------------------------------------------------ |
| # | pod documentation | |
| # | | |
| # | | |
| # ------------------------------------------------------------------------ |
| |
| =head1 NAME |
| |
| wine - Perl extension for calling wine API functions |
| |
| =head1 SYNOPSIS |
| |
| use wine; |
| |
| wine::declare( "kernel32", |
| SetLastError => ["void", ["int"]], |
| GetLastError => ["int", []] ); |
| SetLastError( 1234 ); |
| printf "%d\n", GetLastError(); |
| |
| |
| =head1 DESCRIPTION |
| |
| This module provides a gateway for calling Win32 API functions from |
| a Perl script. |
| |
| =head1 CALLING WIN32 API FUNCTIONS |
| |
| The functions you want to call must first be declared by calling |
| the wine::declare method. The first argument is the name of the |
| module containing the APIs, and the next argument is a list of |
| function names and their return and argument types. For instance: |
| |
| wine::declare( "kernel32", |
| SetLastError => ["void", ["int"]], |
| GetLastError => ["int", []] ); |
| |
| declares that the functions SetLastError and GetLastError are |
| contained in the kernel32 dll. |
| |
| Once you have done that you can call the functions directly just |
| like native Perl functions: |
| |
| SetLastError( $some_error ); |
| |
| The supported return types are: |
| |
| =over 4 |
| |
| =item void |
| |
| =item word |
| |
| =item long |
| |
| =item ptr |
| |
| =item str |
| |
| =item wstr |
| |
| =back |
| |
| =head1 $wine::err VARIABLE |
| |
| In the Win32 API, an integer error code is maintained which always |
| contains the status of the last API function called. In C code, |
| it is accessed via the GetLastError() function. From a Perl script, |
| it can be accessed via the package global $wine::err. For example: |
| |
| GlobalGetAtomNameA ($atom, \$buf, -1); |
| if ($wine::err == 234) |
| { |
| ... |
| } |
| |
| Wine returns 234 (ERROR_MORE_DATA) from the GlobalGetAtomNameA() |
| API function in this case because the buffer length passed is -1 |
| (hardly enough room to store anything in ...) |
| |
| If the called API didn't set the last error code, $wine:;err is |
| undefined. |
| |
| =head1 $wine::debug VARIABLE |
| |
| This variable can be set to 1 to enable debugging of the API calls, |
| which will print a lot of information about what's going on inside the |
| wine package while calling an API function. |
| |
| =head1 OTHER USEFUL FUNCTIONS |
| |
| The bundle that includes the wine extension also includes a module of |
| plain ol' Perl subroutines which are useful for interacting with wine |
| API functions. Currently supported functions are: |
| |
| =over 4 |
| |
| =item hd BUF [, LENGTH] |
| |
| Dump a formatted hex dump to STDOUT. BUF is a string containing |
| the buffer to dump; LENGTH is the length to dump (length (BUF) if |
| omitted). This is handy because wine often writes a null character |
| into the middle of a buffer, thinking that the next piece of code to |
| look at the buffer will be a piece of C code that will regard it as |
| a string terminator. Little does it know that the buffer is going |
| to be returned to a Perl script, which may not ... |
| |
| =item wc STR |
| |
| Generate and return a wide-character (Unicode) string from the given |
| ASCII string |
| |
| =item wclen WSTR |
| |
| Return the length of the given wide-character string |
| |
| =item assert CONDITION |
| |
| Print a message if the assertion fails (i.e., CONDITION is false), |
| or do nothing quietly if it is true. The message includes the script |
| name and line number of the assertion that failed. |
| |
| =back |
| |
| |
| |
| =head1 AUTHOR |
| |
| John F Sturtz, jsturtz@codeweavers.com |
| |
| =head1 SEE ALSO |
| |
| wine documentation |
| |
| =cut |