Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Calling a function from an external DLL with Win32::API on windows: paranormal activity

by kikuchiyo (Monk)
on Aug 12, 2010 at 16:31 UTC ( #854708=perlquestion: print w/ replies, xml ) Need Help??
kikuchiyo has asked for the wisdom of the Perl Monks concerning the following question:

This is a followup to earlier thread Calling a function form an external DLL with Inline::C on windows.

With the help of some wise monks I was able to hack together an Inline::C-based solution to my original problem. However, during that process I was sidetracked to Win32::API for a while, and I encountered some strange phenomena with that module. I'll list them here, and I'd like to have comments on them.

1) The documentation for the Win32::API module lists several methods for accessing a function from a library.

Out of these, I've tried Win32::API->Import first, because it seemed that it allows for a simpler syntax when calling the function.
However, the script exited with an error message the first time it tried to call a function thus imported:

Can't call method "Call" on an undefined value at (eval 4) line 2.
(See an example in the previous thread)

The DLL's path and the function's name were correct, because the other methods (Win32::API->new) were able to find the DLL and load the function from it.

2) So I had to use Win32::API->new. According to the documentation, the preferred method is to use the full C function prototype:
my $DLL = 'D:\path\to\CrappyLibrary.dll'; my $IP = "192.168.186.140"; my $RegisterClient2 = Win32::API->new($DLL, 'int RegisterClient2(int * +pnClientId, char *pszIPAddress)'); ... my $result = $RegisterClient2->Call($client_id, $IP);

This caused the script to crash.
I realized that the DLL was built with the _cdecl convention, and that caused the crash. So I wanted to include the calling convention in the prototype:
my $RegisterClient2 = Win32::API->new($DLL, 'int _cdecl RegisterClient +2(int *pnClientId, char *pszIPAddress)');

But it had no effect.

4) There is a third method, said to be deprecated by the documentation:
my $RegisterClient2 = Win32::API->new($S{DLL_path}, 'RegisterClient2', + 'PP', 'I', '_cdecl'); my $result = $RegisterClient2->Call($client_id, $IP);

This finally worked without crashing.
There was an other problem, though.
The value returned in $client_id was not what I expected.
I had to do an extra $client_id = unpack "i", $client_id; to get the value I really wanted.

And there is an other thing. The module documentation says that the variables thus passed to a function must be initialized (to ensure that enough memory is allocated). So I inserted my $client_id = 0 before calling the function.
No good; it crashed. However, when I changed to 0 to something non-zero, it worked. What's wrong with 0 as initial value?

5) The documentation says that if a parameter of the C function is a pointer, the corresponding part in the Perl calling statement must be a variable, not a constant expression.
It seems that it can't be a hash element, either.

my $hash{IP} = "192.168.186.140"; my $result = $RegisterClient2->Call($client_id, $hash{IP}); # fails

6) And finally, the problem that caused me to abandon Win32::API altogether:

I had a function that had a prototype like this:
int GetServerData2(int nClientId, int *pnSetup, double *pdTime,double +*pdX, double *pdY, double *pdZ, double *pdTotal)

So I imported it like this:
my $GetServerData2 = Win32::API->new($S{DLL_path}, 'GetServerData2', ' +IPPPPPP', 'I', '_cdecl');

When I called it:
my ($time, $Ex, $Ey, $Ez, $Eabs, $SAR); $time = $Ex = $Ey = $Ez = $Eabs = 5.0; my $result = $GetServerData2->Call($client_id, $read_setup_no, $time, +$Ex, $Ey, $Ez, $Eabs);

I got back garbage in the $Ex, etc. variables. I tried unpacking them ($Ex = unpack "d", $Ex), but to no avail. The garbage didn't look like any meaningful double.

As this step (getting data from the server) were to be the point of the entire application, the failure of it meant that I had to abandon this entire approach (luckily, Inline::C came to the rescue).

Are these really known limitations of the Win32::API module, or is it I who is doing it wrong?

Comment on Calling a function from an external DLL with Win32::API on windows: paranormal activity
Select or Download Code
Re: Calling a function from an external DLL with Win32::API on windows: paranormal activity
by ikegami (Pope) on Aug 12, 2010 at 17:06 UTC

    I've tried Win32::API->Import first [...]. However, the script exited with an error message [...]. the other methods (Win32::API->new) were able to find the DLL and load the function from it.

    You are mistaken. Win32::API->import simply calls Win32::API->new and sets up a means of calling ->Call. Either both methods work, or neither.

Re: Calling a function from an external DLL with Win32::API on windows: paranormal activity
by ikegami (Pope) on Aug 12, 2010 at 17:27 UTC

    It sounds like you completely overlooked my reply in the earlier thread and recreated it from scratch.

    So I inserted my $client_id = 0 before calling the function. No good; it crashed

    The function expects a pointer to a block of memory that can hold 4 bytes.
    You tell Win32::API to pass a pointer to a block of memory.
    You provide an integer instead of a block of memory to Win32::API.

    I had to do an extra $client_id = unpack "i", $client_id; to get the value I really wanted.

    I don't know why this surprises you. How do you expect Win32::API or Perl to know the buffer contains a native integer if you don't tell it.

    By the way, I think "l" is more appropriate if you specify "I" to Win32::API.

    I got back garbage in the $Ex, etc. variables.

    The function expects a pointer to a block of memory that can hold 8 bytes. (I think it's 8. I use pack to create the right amount of memory.)
    You tell Win32::API to pass a pointer to a block of memory.
    You provide an floating point number instead of a block of memory to Win32::API.

    It seems that it can't be a hash element, either.

    Nonsense. Win32::API couldn't tell the difference between a scalar and a hash's value even if it tried.

    $ perl -MDevel::Peek -e'$x="abc"; %h=(x=>"abc"); sub f { Dump($_[0]) } + f($x); f($h{x});' SV = PV(0x816a040) at 0x817bc40 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x8177bf0 "abc"\0 CUR = 3 LEN = 4 SV = PV(0x816a098) at 0x816c158 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x817d920 "abc"\0 CUR = 3 LEN = 4

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://854708]
Approved by NiJo
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (5)
As of 2014-07-28 08:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite superfluous repetitious redundant duplicative phrase is:









    Results (193 votes), past polls