SWIG and a Miss
There are so many pitfalls you’ll encounter after using SWIG for any extended period of time or with a large enough codebase. I thought I would go over some of the more notable ones I’ve encountered creating a C# wrapper that had me sighing and doubling my caffeine intake. That said, I do like SWIG. If you color within the lines it works quite well and has in the end saved me more time than it has cost me.
I’m still debating if it’s worth investing time into my own cleaner/purpose built wrapper generator. I’d like to hear some thoughts from others who have had to deal with this decision and what you chose. If you went with writing your own, did you use any helpful libraries (like for parsing C++)****? Also, feel free to add to the this list in the comments if you’ve run into another notable problem you ran into with SWIG.
1) Consistent Generator Configuration
Using different build configurations for code generation is a bad idea. Even though your generated code will compile using different build configurations, the #defines you set on the swig command line (-DMY_DEFINE) should be consistent in all build configurations. Varying the generator configuration can lead to things like stale files (unless you rebuild every time), as well as an inconsistent wrapper interface.
2) Automatic Macro Expansion Is Evil
If you have a macro that does different things based upon Debug or Release SWIG will pick the one matching the -D command line define and expand the code to match that. It won’t just insert your macro as you would if you were writing the code naturally. The best way I’ve found to prevent this from happening is just block off the macros it’s trying to expand.
3) Const Correctness
C# does not have a way of mimicking C++’s const’ness. So objects returned in C++ from a function as const Type& are treated as a regular pointer. Allowing your wrapper user to try and change the underlying data in the object even though it’s suppose to be const. To avoid this, you should probably remove the function entirely. Alternatively you can treat it as a new object and generate a new C++ object on the heap and copy it there so that any edits made to it wont be reflected in the actual object. But that has many negative sides also, like not being able to see changes in the object the reference was for. You also create a memory leak since SWIG has no idea it has created a new object, so you need to find a way to tell swig you created new memory. To create a wrapper class for it you can use the %typemap(out) macro like this,
4) Return Reference
By default SWIG treats TYPE & as a pointer. Which is in some sense good and in others horrible. Like it makes perfect sense for a List class to have a function called Get(int index) that returns the TYPE& to the object in the list. In a case like that, treating it as a pointer is fantastic. However, suppose you have the following C++ class,
Then in C# you do the following,
Creating the Matrix4 from C# sets the flag that SWIG needs to delete the underlying native Matrix4 instance when this object goes out of scope.
Next we call Identity() which using the default C# generated code will return a pointer to the same native Matrix4 instance, but a new managed C# wrapper class will be used to wrap it. This new C# class will have the flag set to NOT clean up the memory since it’s just holding onto a pointer and wasn’t responsible for creating it.
Finally we return the C# object created using the Identity() method, which is a different instance than the one wrappedMatrix points to, even though they both point to the same native class. Then we return from the scope we are in, wrappedMatrix is garbage collected, the Identity() spawned matrix continues to live on in managed code, but is now pointing at a completely bogus location in memory since wrappedMatrix was responsible for deleting the native object and did so.
So how do you solve this one? The easiest way is the same as #3, safe bet is just to not have those functions wrapped, instead provide functions that return void. Ideally, SWIG would provide some way for me to markup the class and say,
SWIG_THIS would indicate that I’m just returning the same object, so instead of creating a new wrapper C# class when you return from the wrapped Identity() function, just return “this”.
5) Customizable Allocation
There’s no standardized way of changing the allocation mechanism SWIG uses. It will create/destroy arrays using stock malloc and free. It will create/destroy everything else using new/delete. The way around this is to modify the SWIG script files it uses to generate the default code. (swig\Lib*.swg,*.i). In particular, carrays.i for changing array allocation. For object creation, you’ll have to modify swigs scripts for your destination language of choice. For C# you can find them here (swig\Lib\csharp\csharp.swg).
6) Alignment Fail
SWIG and aligned data types don’t get along together. Presumably you have your own allocator and have either,
- Overridden global new/delete
- Created a common base class with new/delete functions
- Created a macro to use on every class you want to override new/delete on.
If you have then in the case of object creation, you’re safe. Though you may want to replace SWIGs use of new/delete anyway with your new/delete macros as I am sure you’d like to make sure you track exactly where those allocations came from. If you have any arrays SWIG creates, you’ll definitely need to replace their usage of malloc and free with one going through your allocator so that you can handle alignment.
7) Nested Types
SWIG doesn’t handle nested data types. I haven’t found a way around it, you just have to pull them out of the parent class if you want them wrapped.
8) Where Are The Out/Ref Parameter Flags
SWIG doesn’t map method parameters that are passed by (non-const) reference as “out” or “ref” parameters in C#. The way around this is to do the following,
That will make all instances of those types being used in the context void MyMethod(int& x, int& y) would be mapped to void MyMethod(out int x, out int y) in C#. Alternatively if I had done,
The method void MyMethod(int& x, int& y) would be mapped to void MyMethod(ref int x, ref int y) in C#.
9) IntPtr == void*
For some unknown reason SWIG does not map void* to anything useful in C#. So I came up with the following to map it to the IntPtr struct in C#.
10) string –> char**
For C# string is not mapped to char**. So here is how you map it,
Operators are not wrapped by default. There is probably a way to map them to operators in C#, but I went with another route. You can rename them so that they are, for example
12) Memory Lifecycle Management
SWIG has this internal flag in generated classes that tells it if it needs to clean up the memory for the native object when the instance is garbage collected by .Net. If you have pesky life-cycle management requirements in your system you’ll need to be able to control this flag on the fly. The way I’ve found to do this is with the %typemap macro,
Now you can control on a per instance basis whether or not .Net garbage collecting the object will clean it up.
If you need to do callbacks use the “%feature(“director”)”. The documentation explains it more in depth, but it wasn’t clear to me at first how to do it until I learned about directors.
14) Extension Macro
You can extend/add functionality to the C++ class prior to the wrapper code getting generated using the “%extend” macro. The extension code is added as a C function though, so you might wonder, how do I access the class that this function is supposed to be part of. The code SWIG generates refers to the ‘this’ pointer as the variable ‘self’ so you can access anything public on the class by just doing
15) Unfriendly Template Class Names
SWIG generates unfriendly names for templated classes. The best way around this is to use the %template macro. It works like this,
16) Tracking New Memory
If a function creates a new object and returns it, you need to tell SWIG this, otherwise you’ll have a memory leak. By default SWIG does not destroy any object it wraps unless it created a temporary object in the heap for a value type on the stack. The way you inform swig a method returns a new object is by doing,
As it stands I don’t have a good solution to this one. I thought I should mention it though, because it has been a real thorn in my side. If you have a method in C++ that you wrap and you return a base class like BaseObject*. SWIG will wrap that inside a BaseObject C# class. Now all the other SWIG wrapped classes that derived from BaseObject will exist and will similarly inherit from a C# BaseObject.
However, attempting to cast your new C# BaseObject into anything other than its parent classes will result in an exception. Because SWIG has no way of knowing the true type of the BaseObject* it returned from the function. So the C# object it creates is going to be BaseObject and not the actual C# wrapper class for the native types true type.