FMOD in Java

I spent the better part of a few weeks making up Java bindings to the C++ api for FMOD (an audio middleware for games), and most of this time was spent scouring the internet for clues about specific cases. I figured it might be helpful to consolidate my problems and solutions here. Hopefully this will help out some newcomers stumbling along.

If you're interested in the final bindings, code, or have any questions feel free to drop me a line on twitter.

The following is more encyclopedia than fiction, enjoy as you will!

Primitive Output Pointer Types

Java doesn't support primitive output types (ie. writing into an int function parameter), but we can achieve something similar by using an int[1] wherever an output parameter is needed. We can have SWIG do this for us automatically with:

%apply int *OUTPUT { int * }

See this StackOverflow post.

I wasn't able to find a way to do this for all primitives, so I created a simple macro to repeat this for each.

%define PRIMITE_OUTPUT_TYPE(primitiveCppType) 
%apply primitiveCppType *OUTPUT { primitiveCppType * }
%enddef

SWIG is even smart enough to convert C++ usages of unsigned long long into parameters in Java of java.math.BigInteger[].

C++ to Java Enums

In order to support JDK < 1.5, SWIG doesn't use Java enums by default. They can be enabled with

%include "enums.swg"

Again by default, SWIG will not use Java constant values, instead calling into JNI to retrieve constant values. Not all constant values in C++ can be initialized in Java, so this is for safety. If you want to squeeze a little more performance, you can have SWIG generate Java constant values with

%javaconst(1);

Custom Struct Initialization

A number of structs within the FMOD api require the size of the struct to be set in the struct.cbSize field before the struct pointer is passed into an initialization function.

For instance:

FMOD_STUDIO_ADVANCED_SETTINGS* settings = NULL;
settings.cbSize = sizeof(FMOD_ADVANCEDSETTINGS); // need this line

FMOD::Studio::System::getAdvancedSettings(settings);

SWIG was generating the classes properly, but obviously never included that line. The easiest solution I found was to hide the class constructor in Java (so it couldn't be created manually) and have it call into a manual init method in JNI native code (defined within a macro):

%define STRUCT_SETTINGS_INIT_CBSIZE(cppType, javaType)

// Set the java constructor method ("javaType()") to protected
%javamethodmodifiers javaType() "protected";

// Typemap to initialize settings struct. Injected into the cpp inside "cppType" class
%extend cppType {
  void init() { 
    $self->cbSize = sizeof(cppType);
  }
}

// Typemap to add a static method to the generated java class.
%typemap(javacode) cppType %{
  public static javaType create_and_init() {
    javaType set = new javaType();
    set.init();
    return set;
  }
%}

%enddef
TechnicalJohn Austin