# See the bottom of this file for the POD documentation.  Search for the
# string '=head'.

#######################################################################
#
# Win32::API::Callback - Perl Win32 API Import Facility
#
# Author: Aldo Calpini <dada@perl.it>
# Author: Daniel Dragan <bulkdd@cpan.org>
# Maintainer: Cosimo Streppone <cosimo@cpan.org>
#
#######################################################################

package Win32::API::Callback;
use strict;
use warnings;
use vars qw( $VERSION @ISA $Stage2FuncPtrPkd );

$VERSION = '0.72';


require Exporter;      # to export the constants to the main:: space
require DynaLoader;    # to dynuhlode the module.
@ISA = qw( Exporter DynaLoader );
#use Data::Dumper;

sub DEBUG {
    if ($WIN32::API::DEBUG) {
        printf @_ if @_ or return 1;
    }
    else {
        return 0;
    }
}

use Win32::API qw ( WriteMemory ) ;
use Win32::API::Type;
use Config;
#use Win32::API::Struct; #not implemented


BEGIN {
    #there is supposed to be 64 bit IVs on 32 bit perl compatibility here
    #but it is untested
    eval "sub IVSIZE () { ".length(pack('J',0))." }";
    #what kind of stack processing/calling convention/machine code we needed
    eval "sub ISX64 () { ".(index($Config{'archname'},"MSWin32-x64") == 0 ?  1 : 0)." }";
    sub CONTEXT_XMM0();
    sub CONTEXT_RAX();
    *UseMI64 = *Win32::API::UseMI64; #keep UseMI64 out of export list
    *IsBadStringPtr = *Win32::API::IsBadStringPtr;
}
#######################################################################
# dynamically load in the API extension module.
#
bootstrap Win32::API::Callback;

#######################################################################
# PUBLIC METHODS
#
sub new {
    my ($class, $proc, $in, $out, $callconvention) = @_;
    my $self = bless {}, $class; #about croak/die safety, can safely bless here,
    #a ::Callback has no DESTROY, it has no resources to release, there is a HeapBlock obj
    #stored in the ::Callback hash, but the HeapBlock destroys on its own
    # printf "(PM)Callback::new: got proc='%s', in='%s', out='%s'\n", $proc, $in, $out;

    $self->{intypes} = []; #XS requires this, do not remove
    if (ref($in) eq 'ARRAY') {
        foreach (@$in) {
            push(@{$self->{intypes}}, $_);
        }
    }
    else {
        my @in = split '', $in;
        foreach (@in) {
            push(@{$self->{intypes}}, $_);
        }
    }
    $self->{inbytes} = 0;
    foreach(@{$self->{intypes}}){ #calc how long the c stack is
        if($_ eq 'Q' or $_ eq 'q' or $_ eq 'D' or $_ eq 'd'){
            $self->{inbytes} += 8; #always 8
        }
        else{
            $self->{inbytes} += IVSIZE; #4 or 8
        }
    }
    $self->{outtype} = $out;
    $self->{out} = Win32::API->type_to_num($out);
    $self->{sub} = $proc;
    $self->{cdecl} = Win32::API::calltype_to_num($callconvention);

    DEBUG "(PM)Callback::new: calling CallbackCreate($self)...\n";
    my $hproc = MakeCB($self);

    DEBUG "(PM)Callback::new: hproc=$hproc\n";

    $self->{code} = $hproc;

    #### cast the spell
    return $self;
}

sub MakeStruct {
    my ($self, $n, $addr) = @_;
    DEBUG "(PM)Win32::API::Callback::MakeStruct: got self='$self'\n";
    my $struct = Win32::API::Struct->new($self->{intypes}->[$n]);
    $struct->FromMemory($addr);
    return $struct;
}

#this was rewritten in XS
#sub MakeParamArr { #on x64, never do "$i++;       $packedparam .= $arr->[$i];"
#    #on x86, structs and over word size params appears on the stack,
#    #on x64 anything over the size of a "word" is passed by pointer
#    #nothing takes more than 8 bytes per parameter on x64
#    #there is no way to formally specify a pass by copy struct in ::Callback
#    #this only matters on x86, a work around is a bunch of N/I parameters,
#    #repack them as Js, then concat them, and you have the original pass by copy
#    #x86 struct
#    my ($self, $arr)  = @_;
#    my ($i, @pass_arr) = (0);
#    for(@{$self->{intypes}}){ #elements of intypes are not 1 to 1 with stack params
#        my ($typeletter, $packedparam, $finalParam, $unpackletter)  = ($_, $arr->[$i]);
#        
#        #structs dont work, this is broken code from old version
#        #$self->{intypes} is letters types not C prototype params
#        #C prototype support would have to exist for MakeStruct to work
#        if(    $typeletter eq 'S' || $typeletter eq 's'){
#            die "Win32::API::Callback::MakeParamArr type letter \"S\" and struct support not implemented";
#            #push(@pass_arr, MakeStruct($self, $i, $packedparam));
#        }elsif($typeletter eq 'I'){
#            $unpackletter = 'I', goto UNPACK;
#        }elsif($typeletter eq 'i'){
#            $unpackletter = 'i', goto UNPACK;
#        }elsif($typeletter eq 'f' || $typeletter eq 'F'){
#            $unpackletter = 'f', goto UNPACK;
#        }
#        elsif($typeletter eq 'd' || $typeletter eq 'D'){
#            if(IVSIZE == 4){ #need more data, 32 bit machine
#                $packedparam .= $arr->[++$i];
#            }
#            $unpackletter = 'd', goto UNPACK;
#        }
#        elsif($typeletter eq 'N' || $typeletter eq 'L' #on x64, J is 8 bytes
#               || (IVSIZE == 8 ? $typeletter eq 'Q': 0)){
#            $unpackletter = 'J', goto UNPACK;
#        }elsif($typeletter eq 'n' || $typeletter eq 'l'
#               || (IVSIZE == 8 ? $typeletter eq 'q': 0)){
#            $unpackletter = 'j', goto UNPACK;
#        }elsif(IVSIZE == 4 && ($typeletter eq 'q' || $typeletter eq 'Q')){
#            #need more data, 32 bit machine
#            $finalParam = $packedparam . $arr->[++$i];
#        }elsif($typeletter eq 'p' || $typeletter eq 'P'){
#            if(!IsBadStringPtr($arr->[$i], ~0)){ #P letter is terrible design
#                $unpackletter = 'p', goto UNPACK;
#            }#else undef
#        }
#        else{ die "Win32::API::Callback::MakeParamArr unknown in type letter $typeletter";}
#        goto GOTPARAM;
#        UNPACK:
#        $finalParam = unpack($unpackletter, $packedparam);
#        GOTPARAM:
#        $i++;
#        push(@pass_arr, $finalParam);
#    }
#    return \@pass_arr;
#}

#on x64
#void RunCB($self, $EBP_ESP, $retval)
#on x86
#void RunCB($self, $EBP_ESP, $retval, $unwindcount, $F_or_D)
if(! ISX64 ) {
*RunCB  = sub {#32 bits
    my $self = $_[0];
    my (@pass_arr, $return, $typeletter, $inbytes);
    $inbytes = $self->{inbytes};
    #first is ebp copy then ret address
    $inbytes += IVSIZE * 2;
    my $paramcount = $inbytes / IVSIZE ;
    my $stackstr = unpack('P['.$inbytes.']', pack('J', $_[1]));
    my @arr = unpack("(a[J])[$paramcount]",$stackstr);
    #print Dumper(\@arr);
    shift @arr, shift @arr; #remove ebp copy and ret address
    $paramcount -= 2;
    $return = &{$self->{sub}}(@{MakeParamArr($self, \@arr)});
    
    #now the return type
    $typeletter = $self->{outtype};
    #float_or_double flag, its always used
    #float is default for faster copy of probably unused value
    $_[4] = 0;
    #its all the same in memory
    if($typeletter eq 'n' || $typeletter eq 'N'
          || $typeletter eq 'l' || $typeletter eq 'L'
          || $typeletter eq 'i' || $typeletter eq 'I'){
        $_[2] = pack('J', $return);
    }elsif($typeletter eq 'q' || $typeletter eq 'Q'){
        if($self->{'UseMI64'} || ref($return)){ #un/signed meaningless
            $_[2] = Math::Int64::int64_to_native($return);
        }
        else{
            warn("Win32::API::Callback::RunCB return value for return type Q is under 8 bytes long")
            if length($return) < 8;
            $_[2] = $return.''; #$return should be a 8 byte string
            #will be garbage padded in XS if < 8, but must be a string, not a IV or under
        }
    }elsif($typeletter eq 'f' || $typeletter eq 'F' ){
        $_[2] = pack('f', $return);
    }elsif($typeletter eq 'd' || $typeletter eq 'D' ){
        $_[2] = pack('d', $return);
        $_[4] = 1; #use double
    }else { #return null
        $_[2] = pack('J', 0);
    }
    
    if(! $self->{cdecl}){
        $_[3] = IVSIZE * $paramcount; #stack rewind amount in bytes
    }
    else{$_[3] = 0;}
};
}
else{ #64 bits
*RunCB  = sub {
    my $self = $_[0];
    my (@pass_arr, $return, $typeletter);
    my $paramcount = $self->{inbytes} / IVSIZE;
    my $stack_ptr = unpack('P[J]', pack('J', ($_[1]+CONTEXT_RAX())));
    my $stack_str = unpack('P['.$self->{inbytes}.']', $stack_ptr);
    my @stack_arr = unpack("(a[J])[$paramcount]",$stack_str);
    #not very efficient, todo search for f/F/d/D in new() not here
    my $XMMStr = unpack('P['.(4 * 16).']',  pack('J', ($_[1]+CONTEXT_XMM0())));
    #print Dumper([unpack('(H[32])[4]', $XMMStr)]);
    my @XMM = unpack('(a[16])[4]', $XMMStr);
    #assume registers are copied to shadow stack space already
    #because of ... prototype, so only XMM registers need to be fetched.
    #Limitation, vararg funcs on x64 get floating points in normal registers
    #not XMMs, so a vararg function taking floats and doubles in the first 4
    #parameters isn't supported
    if($paramcount){
        for(0..($paramcount > 4 ? 4 : $paramcount)-1){
            my $typeletter = ${$self->{intypes}}[$_];
            if($typeletter eq 'f' || $typeletter eq 'F' || $typeletter eq 'd'
               || $typeletter eq 'D'){
        #x64 calling convention does not use the high 64 bits of a XMM register
        #although right here the high 64 bits are in @XMM elements
        #J on x64 is 8 bytes, a double will not corrupt, this is unreachable on x86
        #note we are copying 16 bytes elements to @stack_arr, @stack_arr is
        #normally 8 byte elements, unpack ignores the excess bytes later
                $stack_arr[$_] =  $XMM[$_];
            }
        }
    }
    #print Dumper(\@stack_arr);
    #print Dumper(\@XMM);
    $return = &{$self->{sub}}(@{MakeParamArr($self, \@stack_arr)});
    
    #now the return type
    $typeletter = $self->{outtype};
    #its all the same in memory
    if($typeletter eq 'n' || $typeletter eq 'N'
          || $typeletter eq 'l' || $typeletter eq 'L'
          || $typeletter eq 'i' || $typeletter eq 'I'
          || $typeletter eq 'q' || $typeletter eq 'Q'){
        $_[2] = pack('J', $return);
    }
    elsif($typeletter eq 'f' || $typeletter eq 'F' ){
        $_[2] = pack('f', $return);
    }
    elsif($typeletter eq 'd' || $typeletter eq 'D' ){
        $_[2] = pack('d', $return);
    }
    else { #return null
        $_[2] = pack('J', 0);
    }
};
}

sub MakeCB{
    
    my $self = $_[0];
    #this x86 function does not corrupt the callstack in a debugger since it
    #uses ebp and saves ebp on the stack, the function won't have a pretty
    #name though
    my $code =  (!ISX64) ? ('' #parenthesis required to constant fold
    ."\x55" #          push    ebp
    ."\x8B\xEC" #      mov     ebp, esp
    ."\x83\xEC\x0C"#   sub     esp, 0Ch
    ."\x8D\x45\xFC" #  lea     eax, [ebp+FuncRtnCxtVar]
    ."\x50"#           push    eax
    ."\x8D\x45\xF4"#   lea     eax, [ebp+retval]
    ."\x50"#           push    eax
    ."\x8B\xC5"#       mov     eax,ebp 
    ."\x50"#           push    eax
    ."\xB8").PackedRVTarget($self)#B8 mov imm32 to eax, a HV * winds up here
    .("\x50"#           push    eax
    ."\xB8").$Stage2FuncPtrPkd # mov     eax, 0C0DE0001h
    .("\xFF\xD0"#       call    eax
    #since ST(0) is volatile, we dont care if we fill it with garbage
    ."\x80\x7D\xFE\x00"#cmp    [ebp+FuncRtnCxtVar.F_Or_D], 0
    ."\x74\x05"#       jz      5 bytes
    ."\xDD\x45\xF4"#   fld     qword ptr [ebp+retval] (double)
    ."\xEB\x03"#       jmp     3 bytes
    ."\xD9\x45\xF4"#   fld     dword ptr [ebp+retval] (float)
    #rewind sp to entry sp, no pop push after this point
    ."\x83\xC4\x24"#   add     esp, 24h
    ."\x8B\x45\xF4"#   mov     eax, dword ptr [ebp+retval]
    #edx might be garbage, we dont care, caller only looks at volatile
    #registers that the caller's prototype says the caller does
    ."\x8B\x55\xF8"#   mov     edx, dword ptr [ebp+retval+4]
    #can't use retn op, it requires a immediate count, our count is in a register
    #only one register available now, this will be complicated
    ."\x0F\xB7\x4D\xFC"#movzx  ecx, word ptr [ebp+FuncRtnCxtVar.unwind_len]
    ."\x01\xCC"#       add     esp, ecx , might be zero or more
    ."\x8B\x4D\x04"#   mov     ecx, dword ptr [ebp+4] ret address
    ."\x8B\x6D\x00"#   mov     ebp, dword ptr [ebp+0] restore BP
    ."\xFF\xE1")#       jmp     ecx


    #begin x64 part
    #these packs dont constant fold in < 5.17 :-(
    #they are here for readability
    :(''.pack('C', 0b01000000 #REX base
            | 0b00001000 #REX.W
            | 0b00000001 #REX.B
    ).pack('C', 0xB8+2) #mov to r10 register
    .PackedRVTarget($self)
    .pack('C', 0b01000000 #REX base
            | 0b00001000 #REX.W
    ).pack('C', 0xB8) #mov to rax register
    .$Stage2FuncPtrPkd
    ."\xFF\xE0");#       jmp     rax
#making a full function in Perl in x64 was removed because RtlAddFunctionTable
#has no effect on VS 2008 debugger, it is a bug in VS 2008, in WinDbg the C callstack
#is correct with RtlAddFunctionTable, and broken without RtlAddFunctionTable
#in VS 2008, the C callstack was always broken since WinDbg and VS 2008 both
#*only* use Unwind Tables on x64 to calculate C callstacks, they do not, I think,
#use 32 bit style EBP/RBP walking, x64 VC almost never uses BP addressing anyway.
#The easiest fix was to not have dynamic machine code in the callstack at all,
#which is what I did. Having good C callstacks in a debugger with ::API and
#::Callback are a good goal.
#
##--- c:\documents and settings\administrator\desktop\w32api\callback\callback.c -
#    $code .= "\x4C\x8B\xDC";#         mov         r11,rsp
#    $code .= "\x49\x89\x4B\x08";#      mov         qword ptr [r11+8],rcx 
#    $code .= "\x49\x89\x53\x10";#      mov         qword ptr [r11+10h],rdx 
#    $code .= "\x4D\x89\x43\x18";#      mov         qword ptr [r11+18h],r8 
#    $code .= "\x4D\x89\x4B\x20";#      mov         qword ptr [r11+20h],r9 
#    $code .= "\x48\x83\xEC\x78";#      sub         rsp,78h 
#    #void (*LPerlCallback)(SV *, void *, unsigned __int64 *, void *) =
#    #( void (*)(SV *, void *, unsigned __int64 *, void *)) 0xC0DE00FFFF000001;
#    #__m128 arr [4];
#    #__m128 retval;
##     arr[0].m128_u64[0] = 0xFFFF00000000FF10;
##00000000022D1017 48 B8 10 FF 00 00 00 00 FF FF mov         rax,0FFFF00000000FF10h 
##arr[0].m128_u64[1] = 0xFFFF00000000FF11;
##     arr[1].m128_u64[0] = 0xFFFF00000000FF20;
##     arr[1].m128_u64[1] = 0xFFFF00000000FF21;
##     arr[2].m128_u64[0] = 0xFFFF00000000FF30;
##     arr[2].m128_u64[1] = 0xFFFF00000000FF31;
##     arr[3].m128_u64[0] = 0xFFFF00000000FF40;
##     arr[3].m128_u64[1] = 0xFFFF00000000FF41;
#
##    LPerlCallback((SV *)0xC0DE00FFFF000002, (void*) arr, (unsigned __int64 *)&retval,
##                  (DWORD_PTR)&a);
##00000000022D1021 4D 8D 4B 08      lea         r9,[r11+8] #no 4th param
#    $code .= "\x4D\x8D\x43\xA8";#      lea         r8,[r11-58h] #&retval param
##00000000022D1029 49 89 43 B8      mov         qword ptr [r11-48h],rax 
##00000000022D102D 48 B8 11 FF 00 00 00 00 FF FF mov         rax,0FFFF00000000FF11h 
#    $code .= "\x49\x8D\x53\xB8";#     lea         rdx,[r11-48h] #arr param
##00000000022D103B 49 89 43 C0      mov         qword ptr [r11-40h],rax 
##00000000022D103F 48 B8 20 FF 00 00 00 00 FF FF mov         rax,0FFFF00000000FF20h 
##00000000022D1049 48 B9 02 00 00 FF FF 00 DE C0 mov         rcx,0C0DE00FFFF000002h
#    $code .= "\x48\xB9".PackedRVTarget($self);# mov         rcx, the HV *
##00000000022D1053 49 89 43 C8      mov         qword ptr [r11-38h],rax 
##00000000022D1057 48 B8 21 FF 00 00 00 00 FF FF mov         rax,0FFFF00000000FF21h 
##00000000022D1061 49 89 43 D0      mov         qword ptr [r11-30h],rax 
##00000000022D1065 48 B8 30 FF 00 00 00 00 FF FF mov         rax,0FFFF00000000FF30h 
##00000000022D106F 49 89 43 D8      mov         qword ptr [r11-28h],rax 
##00000000022D1073 48 B8 31 FF 00 00 00 00 FF FF mov         rax,0FFFF00000000FF31h 
##00000000022D107D 49 89 43 E0      mov         qword ptr [r11-20h],rax 
##00000000022D1081 48 B8 40 FF 00 00 00 00 FF FF mov         rax,0FFFF00000000FF40h 
##00000000022D108B 49 89 43 E8      mov         qword ptr [r11-18h],rax 
##00000000022D108F 48 B8 41 FF 00 00 00 00 FF FF mov         rax,0FFFF00000000FF41h 
##00000000022D1099 49 89 43 F0      mov         qword ptr [r11-10h],rax 
##00000000022D109D 48 B8 01 00 00 FF FF 00 DE C0 mov         rax,0C0DE00FFFF000001h
#    $code .= "\x48\xB8".$Stage2FuncPtrPkd; # mov         rax,0C0DE00FFFF000001h 
#    $code .= "\xFF\xD0";#            call        rax  
##    return *(void **)&retval;
#    $code .= "\x48\x8B\x44\x24\x20";#   mov         rax,qword ptr [retval] 
##}
#    $code .= "\x48\x83\xC4\x78";#      add         rsp,78h 
#    $code .= "\xC3";#              ret              

#$self->{codestr} = $code; #save memory
#32 bit perl doesn't use DEP in my testing, but use executable heap to be safe
#a Win32::API::Callback::HeapBlock is a ref to scalar, that scalar has the void *
my $ptr = ${($self->{codeExecAlloc} = Win32::API::Callback::HeapBlock->new(length($code)))};
WriteMemory($ptr, $code, length($code));
return $ptr;
}


1;

__END__

#######################################################################
# DOCUMENTATION
#

=head1 NAME

Win32::API::Callback - Callback support for Win32::API

=head1 SYNOPSIS

  use Win32::API;
  use Win32::API::Callback;

  my $callback = Win32::API::Callback->new(
    sub { my($a, $b) = @_; return $a+$b; },
    "NN", "N",
  );

  Win32::API->Import(
      'mydll', 'two_integers_cb', 'KNN', 'N',
  );

  $sum = two_integers_cb( $callback, 3, 2 );


=head1 FOREWORDS

=over 4

=item *
Support for this module is B<highly experimental> at this point.

=item *
I won't be surprised if it doesn't work for you.

=item *
Feedback is very appreciated.

=item *
Documentation is in the work. Either see the SYNOPSIS above
or the samples in the F<samples> or the tests in the F<t> directory.

=back

=head1 USAGE

Win32::API::Callback uses a subset of the type letters of Win32::API. C Prototype
interface isn't supported. Not all the type letters of Win32::API are supported
in Win32::API::Callback.

=over 4

=item C<I>: 
value is an unsigned integer (unsigned int)

=item C<i>: 
value is an signed integer (signed int or int)

=item C<N>: 
value is a unsigned pointer sized number (unsigned long)

=item C<n>: 
value is a signed pointer sized number (signed long or long)

=item C<Q>: 
value is a unsigned 64 bit integer number (unsigned long long, unsigned __int64)
See next item for details.

=item C<q>: 
value is a signed 64 bit integer number (long long, __int64)
If your perl has 'Q'/'q' quads support for L<pack> then Win32::API's 'q'
is a normal perl numeric scalar. All 64 bit Perls have quad support. Almost no
32 bit Perls have quad support. On 32 bit Perls, without quad support,
Win32::API::Callback's 'q'/'Q' letter is a packed 8 byte string.
So C<0x8000000050000000> from a perl with native Quad support
would be written as C<"\x00\x00\x00\x50\x00\x00\x00\x80"> on a 32 bit
Perl without Quad support. To improve the use of 64 bit integers with
Win32::API::Callback on a 32 bit Perl without Quad support, there is
a per Win32::API::Callback object setting called L<Win32::API/UseMI64>
that causes all quads to be accepted as, and returned as L<Math::Int64>
objects. 4 to 8 byte long pass by copy/return type C aggregate types
are very rare in Windows, but they are supported as "in" and return
types by using 'q'/'Q' on 32 and 64 bits. Converting between the C aggregate
and its representation as a quad is upto the reader. For "out" in
Win32::API::Callback (not "in"), if the argument is a reference, it will
automatically be treated as a Math::Int64 object without having to
previously call this function.

=item C<F>: 
value is a floating point number (float)

=item C<D>: 
value is a double precision number (double)

=item C<Unimplemented types>:
Unimplemented in Win32::API::Callback types such as shorts, chars, and
smaller than "machine word size" (32/64bit) numbers can be processed
by specifiying N, then masking off the high bytes.
For example, to get a char, specify N, then do C<$numeric_char = $_[2] & 0xFF;>
in your Perl callback sub. To get a short, specify N, then do
C<$numeric_char = $_[2] & 0xFFFF;> in your Perl callback sub.

=back

=head2 FUNCTIONS

=head3 new

    $CallbackObj = Win32::API::Callback->new( sub { print "hello world";},
                                            'NDF', 'Q', '__cdecl');
    $CallbackObj = Win32::API::Callback->new( sub { print "hello world";},
                                            $in, $out);

Creates and returns a new Win32::API::Callback object. Calling convetion
parameter is optional.  Calling convention parameter has same behaviour as
Win32::API's calling convention parameter. C prototype parsing of Win32::API
is not available with Win32::API::Callback. If the C caller assumes the
callback has vararg parameters, and the platform is 64 bits/x64, in the first 4
parameters, if they are floats or doubles they will be garbage. Note there is
no way to create a Win32::API::Callback callback with a vararg prototype.
A workaround is to put "enough" Ns as the in types, and stop looking at the @_
slices in your Perl sub callback after a certain count. Usually the first
parameter will somehow indiciate how many additional stack parameters you are
receiving. The Ns in @_ will eventually become garbage, technically they are
the return address, saved registers, and C stack allocated variables of the
caller. They are effectivly garbage for your vararg callback. All vararg
callbacks on 32 bits must supply a calling convention, and it must be '__cdecl'
or 'WINAPIV'.

=head2 METHODS

=head3 UseMI64

See L<Win32::API/UseMI64>.

=head1 AUTHOR

Aldo Calpini ( I<dada@perl.it> ).
Daniel Dragan ( I<bulkdd@cpan.org> ).

=head1 MAINTAINER

Cosimo Streppone ( I<cosimo@cpan.org> ).

=cut
