A few months ago I managed to control a National Instruments Digital IO card (sitting in a PCI slot in my PC) from Perl. I accomplished this by installing the Win32::API module, and loading the card's .dll API. I had a few struggles with Win32::API as some things weren't obvious, but after some searching and good advice from Perlmonks, it worked. Today I had another encounter with Win32::API. I have some C code I want to access from Perl. So, I compiled it in Visual C++ into a DLL, but Win32::API kept segfaulting, although loading the same DLL from another C++ program worked fine. Another round of investigation began... To cut a long story short, here is the correct way to compile C code into a DLL and access it from Perl.

The C code

I'm going to write a simple C function that demonstrates some interesting concepts like passing data in and out with pointers. Here is the .h file:

int __stdcall test1(char* buf, 
                    int num, char* outbuf);
This is a (almost) normal declaration of a function named test1 that takes two pointers to a char and one integer as arguments, and returns an integer. __stdcall is a Visual C++ compiler keyword that specifies the stdcall calling convention. The stdcall convention is used by Windows API functions. There's another common calling convention - __cdecl which is usually used for "normal" (not Windows API) code. The Win32::API Perl module supports only __stdcall, so while we could use __cdecl for binding this DLL to another piece of C / C++ code, it doesn't work with Win32::API. The .c file provides the implementation:

#include "dll_test.h"

int __stdcall test1(char* buf, 
                    int num, char* outbuf)
{
    int i = 0;

    for (i = 0; i < num; ++i)
    {
        outbuf[i] = buf[i] * 3;
    }

    return num;
}

DEF file

A module definition (.def) file provides the linker with information about exported symbols, which is useful when writing DLLs. I create a new text file, name it dll_test.def and put it into the project directory:

LIBRARY DLL_TEST.DLL

EXPORTS
    test1
In this file I specify the library name, and the name of the exported function (several names appear on separate lines). Now this .def file should be given as an option to the linker. Add /DEF dll_test.def as a linker option, or provide "dll_test.def" in the "Module definition file" field (Input category) in the project properties (Linker options). After this, build the project and the DLL will be created.

Without the DEF file ?

It is possible to create the DLL without using the .def file. If you prepend __declspec(dllexport) to the function declaration, the linker will export it without consulting the .def file. While this works well in C++ code calling the functions from the DLL, this method isn't recommended when using Win32::API, because __stdcall mangles the names of functions and it may be difficult (though possible) to import them to Perl. The DEF file instructs the linker to create an unmangled name for the function, in spite of using __stdcall, so it is the preferred method. In any case, the dumpbin command line tool (built into Windows) allows to see the names of exported functions in a DLL by calling:

dumpbin /exports 

The Perl code

Finally, we can use Win32::API to import the C function we created from the DLL and use it:

use warnings;
use strict;
$|++;
use Win32::API;

# Import the test1 function from the DLL
#
my $test1 = Win32::API->new('dll_test', 
                            'test1', 
                            'PNP', 
                            'N');
die unless defined $test1;

# the input must be a buffer of bytes,
# so we use pack
#
my $buf = pack('C*', (1, 2, 3, 4, 5));

# allocate space for the output buffer
#
my $outbuf = ' ' x 5;

# Call the imported function
#
my $ret = $test1->Call($buf, 5, $outbuf);

# Results
#
print "Returned $ret\n";
print join ' ', unpack('CCCCC', $outbuf), "\n";

P.S.

A good discussion of this topic is given in this Perlmonks thread.

Comments

comments powered by Disqus