Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Mixed-Language Development & SWIG


"Wrapping" with SWIG


There is generally an inverse relationship between speed of development and speed of execution. Code developed in C/C++ tends to execute much faster than code written in scripting languages such as Ruby, Perl, or Python — languages I like to refer to as "agile languages." In many instances, it takes much less time to develop applications in agile languages. Often, a mixed-language approach makes sense: Develop the parts of the application where execution speed is an issue in C/C++, and develop other parts of the application in a scripting language where speed of development is important. The agile languages really shine when it comes to string manipulation (most include support for regular expressions), file manipulation, parsing, and rapid GUI development — areas where developing in C/C++ can be cumbersome.

I recently worked on a project to create a computational geometry engine in C++. One of the main requirements was speed — we had millions of polygons to check for intersection and overlap. As is often the case, the schedule for developing the application was aggressive. There were also quality concerns that led us to consider unit testing all of our classes to ensure that the various methods on those classes did the right thing. I began to investigate unit testing frameworks for C++. I tried out one of the frameworks, but didn't have much success and, after about an hour, I decided to try a different approach — I used SWIG (http://www.swig.org/) to wrap our C++ classes so that they were available from Ruby (http://www.ruby-lang.org/), then used Ruby's unit testing framework, Test::Unit, for unit testing. Our application code was written in C++, but we were now writing our unit tests in Ruby.

SWIG is an interface generator that connects C/C++ programs with scripting languages such as Perl, Python, Ruby, and Tcl. SWIG uses C/C++ header file declarations to generate the wrapper code that scripting languages need to access C/C++ code. (For more information, see "Rapid Prototyping with SWIG," by Daniel Blezek, CUJ, November 1998.)

As the project progressed, we needed to be able to parse polygon description files and instantiate polygon objects based on information in these files. These files essentially contained a label for a polygon followed by a list of points that made up the vertices of the polygon. Using lex and yacc to write a parser for these files would have been overkill, but writing a parser in C++ didn't seem like a good option either. We decided that, since we already had all of our C++ classes wrapped with SWIG, we would go ahead and write the parser in Ruby (see the sidebar "Wrapping' with SWIG"). This saved a significant amount of time and required much less effort than it would have taken to write the equivalent parser in C++. In Ruby, the parser was less than 20 lines of code; it would have taken much more code in C++.

Earlier in the project, it was suggested that embedding a scripting language into our C++ application would be a nice feature to have because it would let users of our application script it. As time pressures increased, the chances that we would implement this particular feature became unlikely. However, after we began to do our file parsing in Ruby, it became clear that we could look at the application from the other direction: Instead of embedding a scripting language into our application, we were now using a scripting language (Ruby) to tie together our C++ components. The scripting language was now our "main" and we could expose this to the users of the application so they could customize the application by writing Ruby code.

Example: Creating an Extension with SWIG

Say you have C++ classes that represent geometrical objects (points and lines, for example), and suppose you would like to initially do unit testing of those classes using a scripting language. I use Ruby in this example, but any language that SWIG targets could be used. Listing 1 is the Point class in C++.

The Point class is simple; it has two attributes to represent the x and y location of the point, a constructor, and accessors.

The Line class (Listing 2) defines accessors as well as methods to determine the slope, y-intercept of the line, and whether the line is parallel to another line. The intersection method is a stub for now as it returns NULL.

To wrap these two classes so that you can access them from a scripting language, you need to create an interface file; see Listing 3. The interface file gives SWIG information about the C++ header files to wrap and the namespace they will appear in when accessed from the scripting language. The %module declaration, in this case, defines the namespace GEO in the target language. In Ruby, this would be equivalent to defining a module named "Geo": All classes, functions, and variables defined in C++ will appear to be in the Geo module on the Ruby side. Of special note are the two %predicate directives; these directives are intended for use only when Ruby is the target language. Ruby allows method names to contain a trailing "?" character; by convention, methods ending in "?" test for a condition and return either true or false. Since C++ does not allow the trailing "?" in method names, the %predicate directive tells SWIG to generate the wrapper such that the methods is_vertical and is_horizontal will appear as is_vertical? and is_horizontal? in Ruby. This is a simple interface file; details on creating SWIG interface files can be found in the SWIG documentation.

To build the Geo extension for Ruby, you run make on the makefile in Listing 4. SWIG is run with the -c++ option, indicating that the source language is C++, and the -ruby option, which indicates that Ruby is the target language. Running SWIG creates an output C++ file named "Geo_wrap.cxx." This file contains all the code necessary to wrap the classes defined in line.h and point.h. It's often helpful to examine this generated file if SWIG reports problems.

Unit Testing C++ Code with Ruby

Once you've created the shared library Geo.so, you can access the C++ components from within Ruby simply by requiring the Geo.so file at the beginning of the Ruby code (require 'Geo.so'). Listing 5 is a simple unit test for the Point class using Ruby's Test::Unit unit testing package. It instantiates a Point object at x=5.0 and y=25.0, then checks the accessor methods to ensure they return the correct values (assert_equal flags an error if both values passed to it are not equal). Similar unit tests could be written for the Line class.

Mixed-Language Prototyping

In Ruby, classes are always open, meaning that methods can be added to classes at any time (including runtime). The implication here is that you can define a class in C++ and, later on, add methods to that class on the Ruby side. It also means that you can redefine C++-defined methods in Ruby.

Notice the intersection method in the Line class defined in Listing 2; the algorithm has not been implemented, it just returns a NULL pointer. Testing for an intersection between two lines can be tricky because there are some special cases to handle. Why not implement the algorithm in Ruby first? Then, after it has been tested and found correct, it could be reimplemented in C++. Since Ruby needs no compile/link cycle, it's easier for doing iterative development to try an idea, then modify it until the algorithm works correctly. Ruby also has an interactive shell (irb), which lets you type in code and see results immediately.

Listing 6 is the first attempt at an intersection method for the Line class, and Listing 7 shows the unit tests for the method. When you try running these unit tests, you encounter a problem — vertical lines have infinite slope. You realize that you need to check for vertical lines and handle them specially. You also realize that you need to consider the case of parallel lines that will never intersect. After making some adjustments to the Ruby code to handle these cases and performing some more tests, you can then translate this code to C++ and move it into the C++ Line class. The same unit tests can then be used to ensure that this translation was done correctly.

You could also apply this methodology for prototyping whole classes. For example, if the project needs a Polygon class, you can implement the Polygon class in Ruby along with unit tests. After some iterative development where you run tests and make adjustments to code as needed, you could then translate the Polygon class into C++ for better performance.

Conclusion

Doing mixed-language development with C++ and Ruby offers several benefits. There seems to be a good synergy between the two languages because they are both object oriented (Python offers similar advantages). The fact that Ruby classes are always open allows for some interesting possibilities where parts of a class are implemented in C++ and other parts of the same class are implemented in Ruby. Since Ruby is not a compiled language, it's easier to do iterative development in Ruby because there is no compile/link cycle. After testing the Ruby implementation to ensure correctness, the code can be translated to C++ in cases where performance is an issue. The same unit tests can be used to ensure that this translation was correct.

It is important to note that C++ supports multiple inheritance, while Ruby supports single inheritance. However, Ruby has support for mixins, meaning that modules of code can be mixed into a class — thus making the methods defined in those modules available to the class. Any number of modules can be mixed into a class. Future releases of SWIG after 1.3.20 will support this mechanism for simulating multiple inheritance in Ruby by treating some parent classes as modules that will be mixed into the resulting Ruby class. This wasn't an issue in this project because I avoided multiple inheritance in C++, even prior to using Ruby in the project.


Phil Tomson is currently a graduate student in Electrical and Computer Engineering at Portland State University. He can be contacted at [email protected].



Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.