Using SWIG to create a .Net wrapper around a C library

A consultant has written a C library to neatly pack images on a space. They have integrated it in their Ruby On Rails / Node.js online app through the power of extending Ruby. I’m quite jealous, because it’s not quite as easy to do in .Net. Especially when, like me, you know absolutely nothing about C/C++.

Two options are available to me:

  • Create a C++/CLI project, that will generate a .Net DLL
  • Use SWIG to generate a .Net wrapper around the C code.

Now, since I can’t change much the C code (it must be built on Mac through Ruby), I thought that I couldn’t easily switch it to CLI (I was wrong ; see the next post).

So I tried using SWIG. And it’s a pain to use. Let me walk you through creating a wrapper around a non-trivial C library.

Generating basic wrapper methods is pretty easy: it’s a matter of creating a .i file with a few lines of code:

Run SWIG on this file to generate a few C# files and a C wrapper class:

Then, you create an empty C/C++ library project with Visual Studio, include all the necessary files, and generate it. It builds a DLL, and you can include both the C# files and the DLL in your .Net project to use. Very nice, very simple.

A problem arises when you have a C method taking an array as argument: SWIG generates a C# signature taking a single instance as argument:

You can tell SWIG to create an “intermediate” array type by adding this to your .i file:

This creates an “image_t_array” class, with methods for setting and getting items, old-school-style. There is also a method to cast it as “image_t”, with pointers handling to go back and forth in the array.

Now, creating an array class, and cast as instance to pass it as parameter is not very intuitive, and I’m trying to create a usable wrapper. I want to pass an actual, managed array to my method. Is that so hard?

The SWIG documentation has a whole part about passing managed arrays to non-managed code. The problem is that (once again), the documentation is incomplete. It shows an example for a managed arrays of ints… but not an array of structs. Apparently it doesn’t work the same way, because SWIG complains about a missing typemap when using this:

Actually, a totally undocumented feature needs to be called just above this line:

Thanks to a nice fellow on StackOverflow for reading the SWIG sources… Googling for “CSHARP_ARRAYS” yeilds exactly zero result in the documentation, but only a few relevant results on forums and mailing lists. Why is it not documented, while it’s obviously necessary? If it’s not the right way, which is? Why not documenting the warning message that appears when you don’t use it?

So anyway, now my C# method takes a managed array as argument, which makes this library so much more easy to use. It throws a memory access exception when running the “pack” method, though (System.AccessViolationException).

Thankfully I have both the source files, and the pdb file for the unmanaged dll. I just have to copy the pdb to the target directory (using my test program post-build events), and once I activate unmanaged code debugging (managed project properties / debug  /activate unmanaged code debugging), I can step through the library, using the actual C code! Awesome.

At the actual point that an error occurs, I can see that the library tries to access an image in the array that is not defined. Weird. By adding a few breakpoints in the C library (I can’t imagine how I would have debugged that without the source code…), I quickly notice that my “*images” (in the pack) have very weird values, and that they do not correspond to the sent values at all.

Since passing values as arrays might do weird things with pointers, I decide to switch back to using the “image_t_array” class… and sure enough, now the values are correctly passed. I still have memory access problems, though, but that seems to be on the C side… but it runs nicely when a C++ program calls it, so I still have some digging to do.

At this point, debugging the wrapper becomes very complicated with all the layers involved, so I decided to try the C++/CLI way which, as you may see in my next post, is not that hard after all.