Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I have a problem with $title.

Motivation/background: I need to control an instrument (more specifically, read data from it), however, there are no simple, direct means to do this. All I have is a vendor-supplied DLL and a .h header that lists the functions and their parameters.

I already have a perl program that drives the rest of the measurement setup, so I thought I could integrate into it the control of this specific instrument, if I could just use that DLL.

I first tried Win32::API. It looked promising, but it turned out that it doesn't work. If I used the
Win32::API->Import($S{DLL_path}, 'int RegisterClient2(int *pnClientId, + char *pszIPAddress)'); ... my $result = RegisterClient2($client_id, $S{read_IP});
syntax, it gave me an error message: Can't call method "Call" on an undefined value at (eval 4) line 2.

If I used the
my $RegisterClient2 = Win32::API->new($S{DLL_path}, 'int RegisterClien +t2(int *pnClientId, char *pszIPAddress)'); ... my $result = $RegisterClient2->Call($client_id, $S{read_IP});
syntax, it just crashed.

After some googling I've found a number of reports here and elsewhere with similar negative experiences - so I gave up on this approach.

My next idea was to use Inline::C. I used that module on linux earlier, to great satisfaction. And I've read it somewhere that it is possible to use it on Windows too, if one installs ActiveState's MinGW package first.

Indeed, Inline::C itself worked, as far as simple examples (with no external libraries) were concerned.

But I couldn't get it to work with my DLL.

use strict; use warnings; use Inline C => Config => MYEXTLIB => 'd:\path\to\CrappyLibrary.dll', #use Inline C => Config => LIBS =>'-Ld:/path/to -lCrappyLibrary', INC => '-Id:\path\to',BUILD_NOISY => 1; use Inline C => 'DATA'; my $result = RegisterClient2($client_id, $S{read_IP}); __DATA__ __C__ #include "Easy4ApiDef.h"

I get the following error message:
Set up gcc environment - 3.4.5 (mingw-vista special r3) Use of inherited AUTOLOAD for non-method main::RegisterClient2() is de +precated at inline.pl line 115. Can't locate auto/main/RegisterCli.al in @INC (@INC contains: D:\meres +\SAR\_Inline\lib C:/Perl/site/lib C:/Perl/lib .) at inline.pl line 11 +5

The example function RegisterClient2 has the following prototype by the way:
extern "C" __declspec(dllexport) int RegisterClient2(int *pnClientId, +const char *pszIPAddress);

The DLL itself is functioning. I know that because there supplied an example application with it that works correctly (also there is a ghastly LabView application that calls the functions from the DLL and it also works.)

My questions:

-1) What am I doing wrong?
0) Is it possible to salvage the Win32::API solution somehow, or is it really hopeless as I've thought?
1) Is it possible to make it work with Inline::C? If yes, how?
2) Are there further alternatives, or am I f&@#ed thoroughly?

Thanks in advance for your answers.
kikuchiyo

Replies are listed 'Best First'.
Re: Calling a function form an external DLL with Inline::C on windows
by dasgar (Priest) on Aug 05, 2010 at 19:11 UTC

    This reminds me of similar struggles that I had about a year ago. I posted a question about my situation (How to use Win32::API to access variable in DLL file?) and got very useful help from BrowserUk about how accomplish what I wanted using just the Win32 module. You might want to check it out to see if the information there might help you get started.

    Of course, that was dealing with variables. For functions, you would use Win32::API. The combination of the post linked above and some sample code below might help get you going in the right direction.

    use strict; use Win32::API; # Load the DLL my $dll = Win32::LoadLibrary('CrappyLibrary.dll') || die $^E; # Import the function my $func = new Win32::API('CrappyLibrary.dll','RegisterClient2','PP',' +I'); if (not defined $func) {die "Unable to import 'RegisterClient2' functi +on.\n";} my $client_id = 1; my $ip_addr = "192.168.1.1"; # Call the function my $value = $func->Call($client_id,$ip_addr); # unload DLL Win32::FreeLibrary($dll);

    Hope this helps!

      If I try it with this syntax ('PP', 'I'), the perl interpreter crashes (I get that lovely "The program Perl Command Line Interpreter encountered an error and needs to close" dialog box.)
Re: Calling a function form an external DLL with Inline::C on windows
by BrowserUk (Patriarch) on Aug 05, 2010 at 18:48 UTC
    it gave me an error message: Can't call method "Call" on an undefined value at (eval 4) line 2.

    From the error message, it seems likely that it either: failed to load the dll; or locate the function. If you added an error check, it might tell you which, and why. Eg:

    Win32::API->Import( $S{DLL_path}, 'int RegisterClient2(int *pnClientId, char *pszIPAddress)' ) or die $^E;
    use Inline C => Config => MYEXTLIB => 'd:\path\to\CrappyLibrary.dll',

    That should point to the CrappyLibrary.lib, not the .dll. You do have the .lib file?

    Do you have the utility: dumpbin.exe on your system?


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      As for the Win32::API method, I think it finds both the dll and the function in it. If it failed at either task, it would croak something like "Undefined sub &main::RegisterClient2 in..." - which it did, when I misspelled the path. I'll try to check $^E tomorrow, but the fact that the whole program crashed when I used the $function = Win32::API->new(), $function->Call() syntax doesn't fill me with too much hope.

      As for the Inline method: no, no .lib, only the .dll. I've found some advice on the net that said MinGW is able to use the dll directly. However, there might be a path problem related to MinGW's setup (or whatever) here that I can't figure out.

      I don't have dumpbin.exe, at least I'm not aware of it. (Can't tell now, as I'm writing from a different machine)

        Given this C:

        #include <stdio.h> __declspec(dllexport) int RegisterClient2(int *pnClientId, const char *pszIPAddress) { printf( "id:%d addr: %s\n", *pnClientId, pszIPAddress ); return 12345; }

        Compiled to a DLL like this:

        C:\test>cl /LD /MT 853196dll.c Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64 Copyright (C) Microsoft Corporation. All rights reserved. 853196dll.c Microsoft (R) Incremental Linker Version 9.00.21022.08 Copyright (C) Microsoft Corporation. All rights reserved. /out:853196dll.dll /dll /implib:853196dll.lib 853196dll.obj Creating library 853196dll.lib and object 853196dll.exp

        And this Inline::C:

        #! perl -slw use strict; use Inline C => Config => BUILD_NOISY => 1, LIBS => 'c:\test\853196dll +.lib'; use Inline C => <<'END_C', NAME => '_853198_IC', CLEAN_AFTER_BUILD => + 0; int RegClient( SV *id, SV *IP ) { int c_id = SvIV( id ); char *c_ip = SvPVX( IP ); return RegisterClient2( &c_id, c_ip ); } END_C print RegClient( 123, 'fred' );

        Running the Perl code gives:

        C:\test>853196-ic.pl Starting Build Preprocess Stage Finished Build Preprocess Stage Starting Build Parse Stage Finished Build Parse Stage Starting Build Glue 1 Stage Finished Build Glue 1 Stage Starting Build Glue 2 Stage Finished Build Glue 2 Stage Starting Build Glue 3 Stage Finished Build Glue 3 Stage Starting Build Compile Stage Starting "perl Makefile.PL" Stage Writing Makefile for _853198_IC Finished "perl Makefile.PL" Stage Starting "make" Stage Microsoft (R) Program Maintenance Utility Version 9.00.21022.08 Copyright (C) Microsoft Corporation. All rights reserved. C:\Perl64\bin\perl.exe C:\Perl64\lib\ExtUtils\xsubpp -typem cl -c -IC:/test -nologo -GF -W3 -MD -Zi -DNDEBUG -Ox -GL _853198_IC.c _853198_IC.xs(7) : warning C4244: 'initializing' : conversion from ' _853198_IC.xs(9) : warning C4013: 'RegisterClient2' undefined; assum Running Mkbootstrap for _853198_IC () C:\Perl64\bin\perl.exe -MExtUtils::Command -e "chmod" -- 644 C:\Perl64\bin\perl.exe -MExtUtils::Mksymlists -e "Mksymlists link -out:blib\arch\auto\_853198_IC\_853198_IC.dll -dll -nolo 2.lib" "C:\Program Files\Microsoft SDKs\Windows\v6.1\Lib\X64\winspool rosoft SDKs\Windows\v6.1\Lib\X64\uuid.lib" "C:\Program Files\Microsof \Microsoft Visual Studio 9.0\VC\Lib\amd64\msvcrt.lib" -def:_853198_IC Creating library blib\arch\auto\_853198_IC\_853198_IC.lib and obje Generating code Finished generating code if exist blib\arch\auto\_853198_IC\_853198_IC.dll.manifest mt if exist blib\arch\auto\_853198_IC\_853198_IC.dll.manifest de C:\Perl64\bin\perl.exe -MExtUtils::Command -e "chmod" -- 755 C:\Perl64\bin\perl.exe -MExtUtils::Command -e "cp" -- _85319 C:\Perl64\bin\perl.exe -MExtUtils::Command -e "chmod" -- 644 Finished "make" Stage Starting "make install" Stage Microsoft (R) Program Maintenance Utility Version 9.00.21022.08 Copyright (C) Microsoft Corporation. All rights reserved. Files found in blib\arch: installing files in blib\lib into architectu +re Installing C:\test\_Inline\lib\auto\_853198_IC\_853198_IC.dll Installing C:\test\_Inline\lib\auto\_853198_IC\_853198_IC.exp Installing C:\test\_Inline\lib\auto\_853198_IC\_853198_IC.lib Installing C:\test\_Inline\lib\auto\_853198_IC\_853198_IC.pdb Finished "make install" Stage Starting Cleaning Up Stage Finished Cleaning Up Stage Finished Build Compile Stage id:123 addr: fred 12345

        Whether you can adapt that to work with MinGW, and no .lib I don't know.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.

        Do you at least know what calling convention the DLL was built with?

Re: Calling a function form an external DLL with Inline::C on windows
by syphilis (Archbishop) on Aug 06, 2010 at 10:20 UTC
    Hi,

    As regards Inline::C, you have:
    use strict; use warnings; use Inline C => Config => MYEXTLIB => 'd:\path\to\CrappyLibrary.dll', INC => '-Id:\path\to', BUILD_NOISY => 1; use Inline C => 'DATA'; my $result = RegisterClient2($client_id, $S{read_IP}); __DATA__ __C__ #include "Easy4ApiDef.h"
    I think you've *nearly* nailed it. To make it work you could probably invoke the help of the 'autowrap' config option ... see the Inline::C docs (I'm not really up to speed with this aspect of Inline::C).

    Instead, I would probably do the wrapping myself. Something like:
    use strict; use warnings; use Inline C => Config => MYEXTLIB => 'd:\path\to\CrappyLibrary.dll', INC => '-Id:\path\to', BUILD_NOISY => 1; use Inline C => 'DATA'; my $client_id = 5; #numeric my $read_IP = 'whatever'; #string my $result = wrap_RegisterClient2($client_id, $S{read_IP}); __DATA__ __C__ #include "Easy4ApiDef.h" int wrap_RegisterClient2(int cl_id, char* read_ip) { return RegisterClient2(cl_id, read_ip); }
    This should work ok with the gcc compiler (no hope at all with M$ compilers, as they can't link to dll's).

    Update: And now ... let's check out yesterday's news ...

    Cheers,
    Rob
      Thanks to the help from BrowserUK, I've already figured out that I have to write wrapper functions.

      I've tried it the way you did above:

      int wrap_RegisterClient2(int cl_id, char* read_ip) { return RegisterClient2(cl_id, read_ip); }

      but it doesn't work, I get the same error message as in the case without the wrapper.

      It works with BrowserUK's method. In fact, I have to go one step further:

      int wRegisterClient2( SV *id, SV *IP ) { int c_id = SvIV( id ); char *c_ip = SvPVX( IP ); int result = RegisterClient2( &c_id, c_ip ); printf("%d", c_id); sv_setiv(id, (IV) c_id); return result; }

      Basically, the purpose of this RegisterClient2 function is to establish contact with the instrument that listens on the given IP address, and it gives back a unique numeric id in nClientId that I have to use in further calls. And if I want to access this id from Perl, I have to write back the integer value into the SV that was passed to the wrapper.

      So what follows now is me rolling up my sleeves and go elbow-deep into perlguts.

      Thanks to the advice anyway.
        I've tried it the way you did above:
        int wrap_RegisterClient2(int cl_id, char* read_ip) { return RegisterClient2(cl_id, read_ip); }
        but it doesn't work, I get the same error message as in the case without the wrapper.

        The problem, I believe, is that unless all the parameters, and the return type, are types that the default typemap knows how to handle, Inline::C doesn't wrap and export it. And it appears that the default typemap doesn't know how to handle int *.

        So, whilst this works fine:

        #! perl -slw use strict; #use Inline C => Config => BUILD_NOISY => 1; use Inline C => <<'END_C', NAME => '_test', CLEAN_AFTER_BUILD => 0; int test( int i ) { return i; } END_C print test( 123 ); __END__ C:\test>test 123

        This doesn't:

        #! perl -slw use strict; #use Inline C => Config => BUILD_NOISY => 1; use Inline C => <<'END_C', NAME => '_test', CLEAN_AFTER_BUILD => 0; int test( int *i ) { return *i; } END_C print test( 123 ); __END__ C:\test>test Warning. No Inline C functions bound to Perl in C:\test\test.pl Check your C function definition(s) for Inline compatibility Use of inherited AUTOLOAD for non-method main::test() is deprecated at + C:\test\test.pl line 12. Can't locate auto/main/test.al in @INC (@INC contains: C:\test\_Inline +\lib C:/Perl64/site/lib C:/Perl64/lib .) at C:\test\test.pl line 12

        There is the TYPEMAPS configuration option, but I haven't seen any (good) documentation on that. And finding the default typemap and looking at that doesn't yield many clues.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        I've already figured out that I have to write wrapper functions.

        Yes, the reference (in the update) to "yesterday's news" was meant to be acknowlegement of the fact that the isssue had already been resolved :-)

        Cheers,
        Rob
Re: Calling a function form an external DLL with Inline::C on windows
by ikegami (Patriarch) on Aug 06, 2010 at 14:18 UTC

    If int* doesn't work, I would use the following. It's basically the same as what you ended up doing using Inline::C, but in Pure Perl.

    use Win32::API qw( ); my $register_client2 = Win32::API->new( $S{DLL_path}, 'RegisterClient2', 'PP', 'I', ); sub register_client2 { my $ip = $_[1]; my $id = pack('l', 0); # Allocate space my $rv = $register_client2->Call($id, $ip); $_[0] = unpack('l', $id); return $rv; } register_client2(my $id, $ip) or die;

    If the return value is just a boolean, you could do something more perlish.

    sub register_client2 { my ($ip) = @_; my $id = pack('l', 0); # Allocate space $register_client2->Call($id, $ip) or return (); return unpack('l', $id); } my ($id) = register_client2($ip) or die;

    Note: psz would indicate char** rather than char*. That's confusing.

      It turns out that my entire trip to Inline-land was useless1, because Win32::SerialPort doesn't work for some reason if I use it.

      However, I tried Win32::API again as a last resort, and it turned out that it works (and it doesn't crash) if and only if I import the functions like this:

      my $RegisterClient2 = Win32::API->new($DLL, 'RegisterClient2', 'PP', ' +I', '_cdecl');

      So my DLL was compiled with the _cdecl convention, and that's why it failed earlier.

      1) not entirely useless, because I used to think that I can't use Inline on Windows at all. Now that I saw that it works, I have plans to rewrite some of the speed-critical parts of my earlier applications.

      2) I agree that the DLL writer's version of Hungarian notation is confusing, but I can't do anything about it. I decided to go with it to stay in sync with the docs.
Re: Calling a function form an external DLL with Inline::C on windows
by aquarium (Curate) on Aug 06, 2010 at 00:00 UTC
    Maybe you can discover what the dll function does, and hopefully do away with .dll. Use procmon, filemon, and ethereal to sniff out what the example .exe program actually does to read the device. Which port is the device plugged into? Sometimes others have already done some digging for your device and blogged some info. Search the web for the device/model if you haven't already.
    the hardest line to type correctly is: stty erase ^H