http://www.perlmonks.org?node_id=548285

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

Hello monks,

I have a DLL, from which I want to call a function with the following signature (as specified in the .h file that comes with the DLL):
short WINAPI foo(short a, short b, short c, short d)
Importing (specifying SHORT as the type of all arguments in the new 'C declaration parsing' version, or 'ssss' in the old style) with Win32::API::Import seems to go fine. However, when I actually call the imported function, I get an access violation message box.

Curiously, when I declare the arguments to be integers ('iiii'), there's no access violation, although I'm still not sure I get back the same answer.

1) Has anyone run into such a problem ? What can cause it ?

2) How is the call actually executed with Win32::API, re argument packing ? Are they all appended into one big lump of bytes ?

3) My DLL comes from a 3rd party library for controlling a certain PCI card. They supply the DLL, the .h files and a .LIB import file for simpler linking from C/C++. C++ linking of this DLL works fine, and according to them it should also work without problems in VB, Delphi and LabView. So I suppose nothing should prevent it from working in Perl with Win32::API ? Should the DLL be compiled in some special way to work with Win32::API, or does it work with all DLLs ? Thanks in advance

Replies are listed 'Best First'.
Re: Win32::API and 'short' arguments
by BrowserUk (Patriarch) on May 09, 2006 at 18:48 UTC

    This can be complicated. The problem is that WINAPI can mean different things depending upon the compiler options used. If you look in Windef.h you'll find it variously defined as either _cdecl or _stdcall (or _pascal which is the same thing).

    The difference between these two is that with the former, the caller is responsible for cleaning up the stack; with the latter, the called code is responsible for cleaning up the stack.

    There is also the possibility of _fastcall, in which some of the parameters are passed via registers rather than on the stack.

    It will also depend upon which compiler you are using, and which compiler was used to build the DLL.

    For the most part, Win32::API should be able to call functions from any DLL, but you may have to play with the prototypes/templates in order to make it work.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Win32::API and 'short' arguments
by bart (Canon) on May 09, 2006 at 19:58 UTC
    If I remember correctly, all integer parameters in the Win32 API are 4 bytes long. Short, long... even boolean: all are padded to 4 bytes. And as Windows/Intel is in Little Endian mode, lowest byte first, you just have to use long for every integer.

    That's also why "N" is the only integer prototype in Win32::API (old school style).

      That sounds like something that can explain the problem. Can you recall where it's documented, or where I can find some info / examples about it ?

      According to the Win32::API docs, 'N' is not the only integer prototype, there's also 'N'

        Can you recall where it's documented, or where I can find some info / examples about it ?
        No, it's just one of the first things you learn when using the API from (pre-.NET) VB. In fact, I often use the API declarations for VB as the starting point to convert them to Win32::API. You can do the same, just Google for inurl:Win32API.txt to find a copy.

        I've been Googling around for it on the net yesterday, but I haven't found it back in one sentence, at all. Sorry about that.

        According to the Win32::API docs, 'N' is not the only integer prototype, there's also 'N'
        Eh? I'm sure you mean 'I', and AFAIK that does mean the same as 'N'.
Re: Win32::API and 'short' arguments
by BrowserUk (Patriarch) on May 10, 2006 at 01:19 UTC

    Example: This DLL

    #include <windows.h> __declspec(dllexport) short _stdcall test( short a, short b, short c, short d ) { return a+b+c+d; }

    Built with this.DEF file

    LIBRARY TESTDLL.DLL EXPORTS test

    And this command line

    cl /LD testdll.c /link /DEF:testdll.def

    Can be successfully accessed using this perl/win32::API code:

    #! perl -slw use strict; use Win32::API::Prototype;; ApiLink( 'testdll', q[SHORT test( SHORT a, SHORT b, SHORT c, SHORT d )] ) or die $^E;; print "$_*4 = ", test( ($_) x 4 ) for 1 .. 10; __END__ C:\test>testdll 1*4 = 4 2*4 = 8 3*4 = 12 4*4 = 16 5*4 = 20 6*4 = 24 7*4 = 28 8*4 = 32 9*4 = 36 10*4 = 40

    However, if you omit the .DEF file above, then the exported entrypoint will be mangled and look like this:

    Dump of file testdll.dll ... ordinal hint RVA name 1 0 00001000 _test@16

    instead of like this (when built with the def file):

    Dump of file testdll.dll File Type: DLL ... ordinal hint RVA name 1 0 00001000 test

    And you would have to change the import and invokation to look like this (Note the use of the mangled name and the disabling of strict):

    #! perl -slw #use strict; use Win32::API::Prototype;; ApiLink( 'testdll', q[SHORT _test@16( SHORT a, SHORT b, SHORT c, SHORT d )] ) or die $^E;; print "$_*4 = ", &{'_test@16'}( ($_) x 4 ) for 1 .. 10; __END__ C:\test>testdll 1*4 = 4 2*4 = 8 3*4 = 12 4*4 = 16 5*4 = 20 6*4 = 24 7*4 = 28 8*4 = 32 9*4 = 36 10*4 = 40

    Alternatively, if the module was built using compiler options that mean that WINAPI translates to _cdecl (the standard C calling convention):

    #include <windows.h> __declspec(dllexport) short _cdecl test( short a, short b, short c, short d ) { return a+b+c+d; }

    Then although if you build it without a def file and inspect the dll it looks the same as when built with _stdcall and a .def file:

    Dump of file testdll.dll ... ordinal hint RVA name 1 0 00001000 test

    When you try to call it with Win32::API

    #! perl -slw #use strict; use Win32::API::Prototype;; ApiLink( 'testdll', q[SHORT test( SHORT a, SHORT b, SHORT c, SHORT d )] ) or die $^E;; print "$_*4 = ", test( ($_) x 4 ) for 1 .. 10; __END__

    You will get a segfault because of the mis-match in the calling conventions.

    It is not always possible to tell which calling convention was used to build the DLL by simple inspection.

    You say you have the .def file; it may be possible to determine from that. You also say that you don't get segfaults when you are calling the code, which is a pretty strong indication that it was built using _stdcall, which is generally the MS compiler default.

    If the DLL was built with non-MS tools, things can be more complicated still.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Thanks for so detailed an answer ! A few followup questions:
      1. How do you dump a DLL in such way ? I want to examine mine and see if there's name mangling.
      2. My DLL has no DEF file with it (it has a LIB file for linking from C++) and its .h file just places WINAPI as I demonstrated, so I guess I can't know which compiler options were enabled. Though I'm pretty sure it was compiled with a MS compiler.
      3. So is there a chance to successfully call functions from the DLL if it was compiled with _cdecl ?
      4. I get a segfault when I declare the arguments / return value as SHORT, but don't get it when I declare them INT. How can this be affected by what you described ?
      5. Finally: will all the same problems surface using an XS wrapper for the DLL ?
      Thanks in advance.
        1. dumpbin.exe /exports the.dll
        2. If you have a tool available to disassemble the dll, you can work out which calling convention was used. For the example above, the when built using _stdcall, the disassembly looks like:
          Disassembly of Function test (0x00331000) SYM:test 0x331000: PUSH EBP 0x331001: MOV EBP,ESP 0x331003: MOVSX EAX,DWORD PTR [EBP+0x8] ; ARG:0x8 0x331007: MOVSX ECX,DWORD PTR [EBP+0xC] ; ARG:0xC 0x33100B: ADD EAX,ECX 0x33100D: MOVSX ECX,DWORD PTR [EBP+0x10] ; ARG:0x10 0x331011: ADD EAX,ECX 0x331013: MOVSX ECX,DWORD PTR [EBP+0x14] ; ARG:0x14 0x331017: ADD EAX,ECX 0x331019: POP EBP 0x33101A: RET 0x10

          When built with _cdecl it looks like

          Disassembly of Function test (0x00331000) SYM:test 0x331000: PUSH EBP 0x331001: MOV EBP,ESP 0x331003: MOVSX EAX,DWORD PTR [EBP+0x8] ; ARG:0x8 0x331007: MOVSX ECX,DWORD PTR [EBP+0xC] ; ARG:0xC 0x33100B: ADD EAX,ECX 0x33100D: MOVSX ECX,DWORD PTR [EBP+0x10] ; ARG:0x10 0x331011: ADD EAX,ECX 0x331013: MOVSX ECX,DWORD PTR [EBP+0x14] ; ARG:0x14 0x331017: ADD EAX,ECX 0x331019: POP EBP 0x33101A: RET

          The significant part of that is the last (RET) line. In the former, the called code (the function itself) is responsible for cleaning up the stack, hence RET 0x10.

          In the latter case, the calling code is responsible for cleaning up the stack, hence the bare return.

        3. Not with Win32::API that I am aware of.

          It might be possible to build a modified version to do it, but it would be messy. Using XS would be simpler and better.

        4. I don't really understand this. Are you using W::API::Prototype::ApiLink() or Win32::API->new() and 'ssss'?

          If the latter, according to the docs, the 's' template does not mean 'short', but 'struct'.

          As mentioned above, shorts are placed on the stack as ints; the difference is in how they are addressed (16-bit indirect operands instead of 32-bit indirect etc.). I think you would have to use 'IIII' for the latter method.

        5. I'm far from expert with XS.

          You would still need to determine which calling convention was used, but it should be possible to prototype the external call in the XS code to cater for either successfully, though you may need to do a little manual intervention if the dll was built with _cdecl.

        If you are not getting traps when using 'IIII' (or ApiLink and 'SHORT'), then odds are that it was built using _stdcall and Win32::API may be all you need. Though if you are prepared to get stuck in with writing an XS wrapper, it would be considerably more efficient.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Win32::API and 'short' arguments
by ikegami (Patriarch) on May 09, 2006 at 18:13 UTC

    1) Don't know.

    2) They are pushed on the stack.

    3) Win32::API should work with all DLLs.