Calling a C Library Through SWIG
Credit: Garrett Rooney
Problem
You want to use a C library in your Ruby code, but you don want to have to write any C code to do it.
Solution
Use SWIG to generate the C extension for you. SWIG is a programming tool that takes as its input a file containing the information about C functions. It produces source code that lets you access those C functions from a variety of programming languages, including Ruby.
All you you need to write is an interface file, containing the prototypes for the C functions you want to call. The interface file also contains a few directives to control things like the name of the resulting module. Process that file with the swig command-line tool, build your extension, and you e up and running.
Lets build a SWIG extension that lets Ruby access functions from the standard C library. Itll provide access to enough functionality that you can read data from one file and write it to another. In Recipe 22.1, we wrote the C code for a similar extension ourselves, but here well let SWIG do it.
First well need a SWIG interface file, libc.i:
%module libc FILE *fopen(const char *, const char *); int fread(void *, size_t, size_t, FILE *); int fwrite(void *, size_t, size_t, FILE *); int fclose(FILE *); void *malloc(size_t);
This file specifies the name of our extension as "libc". For SWIG Ruby extensions, this means the extension will be named "libc", and the code will be contained in a Ruby module claled Libc. This file also provides the prototypes for the functions we e going to want to call.
Youll also need an extconf.rb program, similar to the one we used in the previous two recipes:
# extconf.rb require mkmf dir_config( cl) dir_config(libc) create_makefile(libc)
To generate the C extension, we process the header file with the swig command-line tool. We then run Rubys extconf.rb program to generate a makefile, and run make to compile the extension:
$ swig -ruby libc.i $ ls extconf.rb libc.i libc_wrap.c $ ruby extconf.rb --with-tcl-include=/usr/include/tcl8.4 creating Makefile $ make … $ ls Makefile extconf.rb libc.i libc.so libc_wrap.c libc_wrap.o
Once the module is compiled, we can use it just like any other Ruby extension. This code uses a Ruby interface to prepopulate a file with random data, then uses the C interface to copy the contents of that file to another file:
random_data = "" 10000.times { random_data << rand(255) } open(source.txt, w) { |f| f << random_data } require libc f1 = Libc.fopen(source.txt, ) f2 = Libc.fopen(dest.txt, w+) buffer = Libc.malloc(1024) nread = Libc.fread(buffer, 1, 1024, f1) while nread > 0 Libc.fwrite(buffer, 1, nread, f2) nread = Libc.fread(buffer, 1, 1024, f1) end Libc.fclose(f1) Libc.fclose(f2) # dest.txt now contains the same random data as source.txt. random_data == open(dest.txt) { |f| f.read } # => true
There you have it: without writing a line of C code, weve been able to call into a C library from Ruby.
Discussion
The great advantage of SWIG over writing your own interface to a C library is that you don have to write your own interface to a C library. The disadvantage is that you get the exact same interface (or a subset) as the C library. The Libc module exposes a Ruby module thats nothing more than a collection of C functions. If you want a friendlier interface, you need to write it yourself on top of the SWIGgenerated module.
In addition to the actual function prototypes, the interface file needs to have a little metadata about your extension. At the minimum, youll need a %module line that tells SWIG what to call the extension it generates. Depending on your C code, you might also need to tell SWIG how to handle C constructs that don map directly to Ruby; see the SWIG documentation on %typemap for details.
There are two main ways to create an interface file. The simplest way is simply to copy the prototypes for your C functions right from your header file into your SWIG interface file. Alternatively, you can use the %import filename directive to include a C header file in a SWIG interface file.
One more thing: note the references to tcl in the extconf.rb file and in the commandline invocation of extconf.rb. Our Libc module has nothing to do with Tcl, but SWIGs Ruby bindings always generate code that relies on the Tcl libraries. Unless your Tcl header files live in one of your systems standard include directory, you need to tell extconf.rb where to find them.
See Also
- http://www.swig.org/
- On Debian GNU/Linux systems, you can install SWIG as the swig package
Категории