I'm currently refactoring a Tcl plugin library written in C++. Originally the code was hand-written. A second library exists that does the same thing for Java.
The refactored libr开发者_开发问答ary will be a single C++ library that can be used to create bindings to different languages.
My first tests with SWIG are promising. However, a lot of junk is generated as well. Various base classes and utilities are all exported. These don't make sense from the scripting point of view and only increase clutter.
Possible solutions that I can think of are:
- Use
#ifndef SWIG
in the original codebase to filter out unneeded code - Create a SWIG-compatible wrapper around the API classes.
- Differentiate between public and private headers. Public headers are pure abstract base classes that contain no implementation. The private headers inherit and implement them. Only SWIG the public headers.
- The opposite of the above solution: inherit a SWIG-compatible class for each API class.
I'm currently leaning towards solution 3 at the moment. However, I'm not really certain so I'd like to know the SO community's opinion on this. Feel free to share your thoughts.
Update
I forgot to list one solution:
- Code that should not exported by SWIG should probably not be in the public section of your class.
Perhaps this is the answer. I'll have another look on Monday.
Update
I settled with a solution. See my answer.
Any approach that means that the C++ library becomes less useful to the C++ user is not the ideal solution.
- #ifdef SWIG in the middle of .hpp files: Muddies up your C++ with unnecessary cruft, so it's not ideal
- SWIG Specific Interface: This is a viable option, but only makes sense if the code you want to expose to SWIG is significantly higher level then the base C++ API.
- Public vs Private interface: Might make sense, but again you have to ask at what cost to the C++ user of the API? Are you limiting the public interface too much? Who has access to the private interface? Should the pImpl idiom be considered instead?
- SWIG Compatible Class for each interface: Probably more work than necessary.
First and foremost, to keep your SWIG related code separate from the rest of the API.
You probably don't want to import the .hpp files directly into SWIG (if SWIG wasn't considered during the initial design of the library), but if you do, you want to use a SWIG .i file to help you clean up the mess. There are three basic approaches we use, each with different use cases.
First, direct inclusion. This is useful if you know your API is nice and clean and well suited for parsing by SWIG:
// MyClass.i
%{
#include "MyClass.hpp" // included for the generated .cxx file
%}
%include "MyClass.hpp" // included and parsed directly by SWIG
The second case is for code that is most of the way there. This is code that had SWIG taken into consideration, but really needed some stuff for the C++ user that we didn't want to expose to SWIG:
// MyClass.i
%{
#include "MyClass.hpp" // included for the generated .cxx file
%}
%ignore MyClass::someFunction(); // This function really just causes us problems
%include "MyClass.hpp" // included and parsed directly by SWIG
The third case, and probably the one you want to use, is to directly choose which functions you want to expose to SWIG.
// MyClass.i
%{
#include "MyClass.hpp" // included for the generated .cxx file
%}
// With this example we provide exactly as much information to SWIG as we want
// it to have. Want it to know the base class? Add it. Don't want it to know about
// a function? Leave it out. want to add a new function? %extend it.
class MyClass
{
void importantFunction();
void importantFunction2();
}
I'd use apprach #3 too. I'm using a similar approach in my projects, and it is used by COM too (interfaces inherithed by private implementation class).
It is really easy to detect errors and maintain code in that way! Unfortunately you will end implementing all functions as virtual, but it should not be a big issue...
Separating the interface will keep it really clean and understandable!
My final solution: simply SWIG the original code base. In order to avoid generation of non-relevant code I use the following techniques. In order of preference:
Make non-swig code private or protected. If it doesn't need to be swigged, then it probably doesn't need to be public.
If possible, change the original code to make it more compatible with SWIG. I replaced a curiously recurring template pattern with abstract base classes. I was willing to make that sacrifice for SWIG :)
Add
%ignore
statements to the interface file.Use
#ifndef SWIG
to filter it out. I don't like to pollute my original code so I only use this as a last resort.
Concerning my previous ideas:
- Create a SWIG-compatible wrapper around the API classes.
- Differentiate between public and private headers. Public headers are pure abstract base classes that contain no implementation. The private headers inherit and implement them. Only SWIG the public headers.
- The opposite of the above solution: inherit a SWIG-compatible class for each API class.
All these solutions require writing SWIG-compatible wrapper code. This is a bit silly because you are ditching SWIG's the strongest selling point: automatic generation of wrapper code. If I write my own wrapper code for SWIG then I might just as well write regular JNI code.
That said, I realize that for some projects writing wrapper code may the most cost-efficient solution. However, I this was not the case in my situation.
精彩评论