Sweden-Number/programs/winetest/include/wine.pm

693 lines
22 KiB
Perl

# --------------------------------------------------------------------
# Module: wine.pm
#
# Purpose: Module to supply wrapper around and support for gateway to
# Windows API functions
# --------------------------------------------------------------------
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