Swig

From MetaSharp

Jump to: navigation, search

Article Author(s): Niels Van Vliet
All Rights Reserved.


Contents

Not finished

Introduction

This article explains a simple and reliable way to re-use C++ classes in C#.

Using Swig you will be able to call your or others C++ libraries. Using swig:

  • It is easy.
  • It is not only about functions.The objects (classes) will also be wrapped.
  • The memory management is clear and easy.
  • It is not intrusive (it can be used on a library that you cannot modify).
  • It is easy to maintain. If the C++ headers change (i.e. one member is added to a class), you do not need to maintain many things.
  • It is a good way for simple usage (ex: I use it to wrapped GSL).
  • It is a good way for object-oriented and large library (ex: QuantLib).

Many articles describe other ways to call unmanaged C++ from .Net.

FIXME: add some link ex:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcmex/html/vcgrfcustommarshaling.asp

This tutorial aims to show a very simple case, not to go into details.

More information can be found on the Web site of Swig [1]. Very quickly here is how Swig will help us:

  • You have a C++ library, with the headers. This has been done by you or others.
  • You have a C# library, that you have written.
  • Swig will need a configuration file. It provides information to export what you want and how you want.
  • Swig will be used to generate 2 sets of files:
    • C++ files. To be added to the C++ library.
    • C# files. To be added to the C# library.

Installation of Swig

On Linux, it is trivial.

On Window, you can downloads a binary (Swig.exe) already compiled [2].

Add the directory of swig.exe to your PATH environment variable, so has to simplify this tutorial [3].

Test if swig.exe is in the PATH in a Dos Command:

\niels>echo %PATH%
C:\WINDOWS\system32;C:\WINDOWS;C:\dl\swigwin-1.3.29\swigwin-1.3.29

(this installation suppose swig.exe is in: C:\dl\swigwin-1.3.29\swigwin-1.3.29\swig.exe).

Test swig.exe in a Dos command:

\niels>swig -version

SWIG Version 1.3.28
Copyright (c) 1995-1998
University of Utah and the Regents of the University of California
Copyright (c) 1998-2005
University of Chicago
Compiled with g++ [i686-pc-mingw32]

Please see http://www.swig.org for reporting bugs and further information

Write your C++ library

Here is small C++ library to test Swig.

Create a c++ empty library project, and add the 2 following files (MyClass.h MyClass.cpp).

Note: do not forget to add an empty line at the end of your files (a C++ file is supposed to be ended by a \n, what is expected by Siwg, and some version of the Microsoft compilers do not check it).

MyClass.h:

// Author: Niels VAN VLIET 2006
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version. 

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#ifndef ProjectCpp_MyClass_H
# define ProjectCpp_MyClass_H
# include <string>
# include <exception>

extern int MyCppRand(int upperBoundNotIncluded);

class MyClass
{
public:
	MyClass(int i, std::string s) throw (std::exception);
	bool LenghtEqualI() const;
	void Add(std::string s2);
	std::string Get() const
	{ return s; }
private:
	int i;
	std::string s;
};

#endif


MyClass.cpp:

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version. 

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "MyClass.h"

int MyCppRand(int upperBoundNotIncluded)
{
	return (int) ((upperBoundNotIncluded)*rand()/(RAND_MAX+1.0));
}

MyClass :: MyClass(int i, std::string s) throw (std::exception)
:i(i), s(s)
{ 
	//Example of exception.
	// Note that 'throw (std::exception)' in the declaration (.h)
	// helps Swig to throw a 'nice' C# exception, with the message.
       // Note: with some version of Swig, the message is not visible.
	if (s == "")
		throw std::exception("I do not want empty strings.");
}

bool MyClass :: LenghtEqualI() const
{ 
	return s.size() == i;
}

void MyClass :: Add(std::string s2)
{
	s += s2;
}

Write your configuration file

There is a lot of tuning available. A full clear and complete tutorial can be found on the web site of Swig. Here we are going to do it as small as possible: we are going to export everything.

To tell Swig how and what you want to export, you can use a .i file. These files are composed of two sections. Here is an example, that we are going to comment.

Put this file in the same directory than your MyClass.h (or change the two 'include' in this file).

Wrapper.i:

#ifndef WRAPPER_SWIG_I
# define WRAPPER_SWIG_I
// First section:
%{ 
# include "MyClass.h"
%}
// Second section:
%include std_string.i
%include "MyClass.h"
#endif

We are going to start to explain the second section, which is the complex one.

Second section: what must exported and how

The second section is not in %{ %}. It is some Swig directives to describe what must be exported and how. The fact is that syntaxe used by Swig to explain how something is exported is VERY VERY close to the syntax of c++.

For example to tell Swig to export a stuct S which contains one int i, the swig syntax is:

struct S
{
   int i;
};

The consequence is that most of the time a quick way to express what you want to export is to copy some part of your headers. But because I am really lazy, I just include the header. You notice the line "%include std_string.i". In fact std::string needs also to be wrapped so as to call the constuctor of MyClass. It is possible to write ourself the wrapper of std::string. But the good news is that for most standard classes, some default swig export files are alredy written. It is the case of std::string. You can learn more on the web site of swig [4].

First section: what must be included in the C++ wrapped file

Swig is going to generate some code. The generated file (wrapper_wrap.cxx) will have to manipulate the data that you want to export to C#. Thus, the top of the the generated c++ file must include the definition of the class (like in any c++ file that want to use a given object). Here we are going to include our header.

In this first section, most of the time, you will just add some #include directives.


Runing Swig

Here is an example of a pre-build directive of C++ library:

swig -Wall -c++ -csharp -outdir $(SolutionDir)\ProjectCSharp\ -module ProjectCpp -namespace Wrapper Wrapper.i

Minimal explaination:

  • -Wall basic warnings
  • -c++ the input is C++ (it could be C also)
  • -csharp the output is C# (it could be a lot of other languages: Python, Java, C, Perl, ...)
  • -outdir output directory. Here, I added this rule to the pre-build rules of the C++ library. This will generate the C# in the C# project.
  • -module global function and global object are not supported by C#. In C# these kinds of objects must be static object of a class. Swig will put the C# interface of all these objects in class named ProjectCpp (if you write -module ProjectCpp).
  • -namespace C# namespace
  • Wrapper.i input swig directives.

What has been generated and how to use it

C++ generated file

First the file Wrapper_wrap.cxx has been generated. It is a C++ file.

* Add it to the c++ library.
* Add a post build rule, which copy the cpp library in Windows\System or in the directory of the C# application. In my case:
copy $(TargetPath) $(SolutionDir)\ProjectCSharp\bin\$(IntDir)\

Note that if you can not modify the original c++ library, you can just compile a new dll. But here for sake of simplicity, we just add the generated file to the c++ project.

Generated Wrapper_wrap.cxx:

/* ----------------------------------------------------------------------------
 * This file was automatically generated by SWIG (http://www.swig.org).
 * Version 1.3.29
 * 

[...] // A lot of swig things. such as #define SWIG.

// All the things that you wrote in the 'first section'.
# include "MyClass.h"
[...] // And then a lot of wrapped classe. 
[...] // Which call your c++ function with a C style. For example:
SWIGEXPORT unsigned int SWIGSTDCALL CSharp_MyClass_LenghtEqualI(void * jarg1) {
  unsigned int jresult ;
  MyClass *arg1 = (MyClass *) 0 ;
  bool result;
  
  arg1 = (MyClass *)jarg1; 
  result = (bool)((MyClass const *)arg1)->LenghtEqualI();
  jresult = result; 
  return jresult;
} [...]

C# generated file

Several C# files are generated. In our case:

  • ProjectCpp.cs. Contains wrapper to global object, in our case the function MyCppRand.
  • ProjectCppPINVOKE.cs. It contains many functions usefull for swig, for example for the exceptions.
  • MyClass.cs. It contains a C# class which has pretty much the same desing than the C++ one. All method calls are wrapped to the c++ library.

Add these files to your C# library.

We are going to add all these files to the C# project. Here is MyClass.cs (generated). Here is a part:

[...]
public class MyClass : IDisposable {
  private HandleRef swigCPtr;
  protected bool swigCMemOwn;

  internal MyClass(IntPtr cPtr, bool cMemoryOwn) {
    swigCMemOwn = cMemoryOwn;
    swigCPtr = new HandleRef(this, cPtr);
  }

  internal static HandleRef getCPtr(MyClass obj) {
    return (obj == null) ? new HandleRef(null, IntPtr.Zero) : obj.swigCPtr;
  }

  ~MyClass() {
    Dispose();
  }

  public virtual void Dispose() {
    if(swigCPtr.Handle != IntPtr.Zero && swigCMemOwn) {
      swigCMemOwn = false;
      ProjectCppPINVOKE.delete_MyClass(swigCPtr);
    }
    swigCPtr = new HandleRef(null, IntPtr.Zero);
    GC.SuppressFinalize(this);
  }

  public MyClass(int i, string s) : this(ProjectCppPINVOKE.new_MyClass(i, s), true) {
    if (ProjectCppPINVOKE.SWIGPendingException.Pending) throw ProjectCppPINVOKE.SWIGPendingException.Retrieve();
  }

  public bool LenghtEqualI() {
    bool ret = ProjectCppPINVOKE.MyClass_LenghtEqualI(swigCPtr);
    return ret;
  }
[...]

Using the C++ from a C# file

Now we can use the class MyClass in C#. As you can see it is very natural.

using System;

namespace SwigTutorial
{
    class Program
    {
        static void Main(string[] args)
        {
            Wrapper.MyClass m = new Wrapper.MyClass(5, "Hello");
            //Display 'True\n'
            Console.WriteLine(m.LenghtEqualI()); 
            m.Add(" World");
            string result = m.Get();
            //Display 'Hello World\n'
            Console.WriteLine(result); 
            bool nb_letter_of_Hello_is_5 = m.LenghtEqualI();
            //Display 'False\n'
            Console.WriteLine(m.LenghtEqualI());
            //Display numbers in [0..10[
            for (int i = 0; i < 50; ++i)
                Console.WriteLine(Wrapper.ProjectCpp.MyCppRand(10));
        }
    }
}

Conclusion

This is a very simple tutorial, but it shows the power of Swig:

  • not intrusive
  • easy
  • keep the object oriented design of the library.

A small note on performance: I did some tests (matrix computation using lapack), and using swig was approximatively as efficient as writing PInvoke code by hand. But keep in mind that most of the time the object are copied. For example the string in this example is copied. As usual: it is not negligeable if the c++ function is short and called many times; and it is faster to past the argument using pointers.

Finally a complex example is the wrapping of QuantLib in C#.

FIXME: add the zip project.

If you have trouble, or question feel free to send an email to niels AT nvv.name.

Personal tools