How to wrap classes using SWIG and why?

If you develop a new piece of functionality, using the OpenSim API to build a new model component (e.g. Muscle,  Actuator, Controller or Probe) you need to decide if you want to make this component available publicly to OpenSim users (as part of the OpenSim API).

The public API is available not only in C++ but also in Java to be used in Matlab and for scripting in Python. To make a component available in Java, OpenSim uses a third party tool SWIG (Simple Wrapper Interface Generator www.swig.org). As of now OpenSim 3.1 uses SWIG 2.0.9. In a nutshell, SWIG parses the .h files of the C++ classes and creates Java classes that map 1-1 to corresponding C++ classes (with few exceptions e.g. templates , globals) this process is called “wrapping”, the main file used to specify the mapping of C++ classes to Java classes is OpenSim/Java/swig/javaWrapOpenSim.i and is called the interface file.

Most of the legwork has already been done to make it easy to add classes to the API and have SWIG wrap the new classes into Java.

Pre-requisites:

  1. Have SWIG installed on your machine (as of version 3.1 we use SWIG version 2.0.9)
  2. Have Java installed on your machine (any Java 1.6 release as of version 3.1)

The main steps are:

  1. In CMake turn on the flag to BUILD_JAVA_WRAPPING (it’s off by default and it is located in the advanced section). You have to have both SWIG and Java installed, make sure CMake can locate SWIG and Java, if not point it to the correct location. This will produce additional projects:
    1. JavaWrap: a project that runs SWIG and update Java wrapping
    2. Libraries - osimJavaJNI: a project that builds a dll/so that includes all C++ methods exposed to Java
    3. Tests - testContext: a test case that loads the library created in b. simulating the GUI operation and do something useful with it (pose a model and compute a muscle path length).
  2. Include the header (e.g. <OpenSim/Simulation/Model/MyCustomProbe.h>) in two places in the file:
    1. In the top section of the file, inside the first %{  %} block. These files are included as is in the C++ file used as a glue code between the Java classes and the corresponding C++ classes. Use C++ include syntax as in the line (#include <OpenSim/Simulation/Model/MyCustomProbe.h>)
    2.  In the bottom section of the interface file as (%include <OpenSim/Simulation/Model/MyCustomProbe.h>). This line is what tells SWIG to wrap the class MyCustomProbe and generate a corresponding Java class for it.
    3. The order in which the second line (in b.) appears in the interface file is important since the lines in this block are processed in sequential order. Insert the header line above after all base classes of MyCustomProbe have been included otherwise SWIG will not be able to build the class hierarchy correctly.
    4. C++ specific syntax that’s not supported in Java is handled here, in particular templates are explicitly instantiated so that there’s a Java class corresponding to each instantiated template. Operators in C++ are also not supported in Java so you can either decorate the header file with #ifndef SWIG, use other mechanism, or just ignore the warnings.
  3. Build the project JavaWrap that will run SWIG and generate the necessary glue code (some warnings may come up we're working on cleaning it up)
  4. Build project osimJavaJNI to compile the glue code, this should compile and link cleanly
  5. Run the test case testContext and make sure it passes.

Issues to Consider when Designing classes to be Processed by SWIG

  1. Nested classes are not supported in the interface: Passing in/out instances of nested classes is not supported.
  2. Operators are not supported by target languages as such if functionality needs to be exposed it has to be delivered as a function.
  3. Methods that return a modifiable reference (e.g. updX()) do not mean the same thing in target languages and as such can't be used. setter methods should be made available.If making examples with the intention to have a parallel code in a script, keep that in mind.
  4. Templates need to be instantiated across the interface. So, if you need to instantiate a template with a new class and have that available on the scripting side, more work needs to be done in the interface file.
  5. Memory management: C++ is flexible in allowing new/delete. Scripting languages do garbage collection so if object ownership is transferred by a function call, this has to be annotated explicitly in the interface file.
  6. Exceptions need to be thought out and not thrown in random spots in the code as they need to be captured and messages communicated to users.