Tuesday, May 25, 2010

DLL wrapper for Ruby 1.9 in Windows

I found myself in a situation where I had to access a Windows DLL to control some external PC peripheral. Hey I thought, this is a good opportunity to hone my Ruby skills.

At first I used Ruby's Win32API. It works fine, provided that the dll function does not return a pointer. For example, if the DLL function is

char * controlx (int a, int b, some_struct * c)

In ruby you can process arguments a, b, and even c, but I almost vomited blood trying to process the return pointer. The thing is, Ruby do not use pointers ! This makes it very tricky to process return pointers from functions.

I was about to use plain old C to do the job, or maybe even Python (I got a Python friend - or fiend - who is telling me that this is no prob in Python, even though Python doesn't use pointers too). Then I googled upon the SWIG project (www.swig.org).

It do me a while to get SWIG working for Ruby. SWIG is pretty well documented, but the below gives the juice of the process and some gotchas. SWIG essentially generates a Ruby wrapper module (in c) for the DLL.

To save hassles, get the same c compiler and environment that created the Ruby windows version you are using. Ruby 1.9 uses MINGW compiler (basically an open source Windows C compiler that uses the Windows libraries, unlike CYGWIN that uses the POSIX libraries). You also need the make program. So I installed MINGW and MSYS, which gives a POSIX-like shell with the essential make program and other usual shell goodies. I think it may be possible to use Microsoft Visual C and nmake.exe (same Windows library calls right?), but I didn't to bother to try that.

You have to create a interface.i file that lists down the DLL functions. It looks like this.

%module your_desired_ruby_name
%{
/* Put header files here or struct declarations that are needed by the functions below */
%}

%feature("autodoc", "1"); /* this is for Ruby's rdoc */
extern char * DLL_function1(void);

%feature("autodoc", "1");
extern bool DLL_function2(void);
...............

Then just execute

> swig -ruby interface.i

or for c++

> swig -c++ -ruby interface.i

SWIG is supposed to be able to wrap both C and C++ functions for Ruby. However when I tried it with the c++ option, the generated c++ wrapper code is not compilable. No problem, I just changed all the bool types in my interface to char, and I had switched to a nice C interface. Anyway why would anyone export C++ functions in a DLL? DLLs are meant to be called from any language.

SWIG generates a c program named interface_wrapper.c. This is just a Ruby c module. So you can now use the standard Ruby ways to compile the module. You can either go the gcc way, or use Ruby built-in mkmf class. mkmf is recommended because it handles all the gcc settings for your Ruby installation (mostly the include dirs etc).

To use mkmf, create a extconf.rb file that looks like this

require 'mkmf'
$libs = append_library($libs, "your_dll_name")
create_makefile('your_ruby_module_name')

Now execute

>ruby extconf.rb

This creates a Makefile in the local directory

>make

This compiles the wrapper Ruby module. Put the DLL in the same directory as the wrapper c code to avoid any linking problems. Then just

>make install

Viola! Your Ruby wrapper module had been compiled and copied to your Ruby installation. In my case it is C:\Ruby19\lib\ruby\site_ruby\1.9.1\i386-msvcrt

To use your DLL functions, just fire up irb or type in a ruby program

irb> require 'your_module_name'
irb> YOUR_MODULE_NAME_IN_CAPITAL.dll_function_xxxx

Oh yeah one last thing. Use rdoc to generate some nice documentation of what the DLL input/output parameters are.

rdoc -f html interface_wrap.c







1 comment:

Unknown said...

Nice blog ,,, This blog help you to find your DLL file is corrupt or missing .please go through this link.
How To Remove Rundll Error
Thanks
Aalia lyon