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?