Overview

ffmbindings is a set of Java bindings to native libraries (mainly C++ libraries).

There are two ways to call native code from Java:

One benefit of FFM over JNI is that it allows to call native functions directly. There is no need to create any "transitional" native library to call native functions (as opposite to JNI, where "transitional" library is required and need to contain definition of JNI functions).

To simplify FFM access to C libraries, Java provides jextract tool.

Unfortunately, it does not generate bindings to C++ code. The work to support C++ is still in experimental phase:

Ideas behind ffmbindings are:

  1. Provide set of Java utility classes which can help users to manually define bindings to C++ classes
  2. Provide bindings to some existing C++ libraries
  3. Keep further experimenting with C++ and demonstrate how far FFM can be extended to support C++ libraries. This may help to provide some data for future C++ bindings generation tools (possibly to jextract or others).

List of supported C++ features:

Alternatives to manually defining bindings for C++

Here are the list of some of the alternative ideas for creating C++ bindings. These are just an ideas so they may not work.

pybind

Many of C++ libraries come with pybind bindings already available (example Open3D).

Therefore instead of creating bindings for C++ code we can try to create bindings for PYBIND11_MODULE functions. The benefit here is that pybind covers issues with templates and all other C++ features.

pybind supports C++ 11, 14, 17, 20

The drawback is that it still requires additional native library, except now it will be not a JNI library but pybind generated library.

javabind

javabind is a Java alternative to pybind.

The drawback of javabind vs pybind approach is that many C++ libraries provide pybind libraries by default but they do not provide javabind libraries. And currently javabind is using legacy JNI (hopefully this could change).

Requirements

Most of the ffmbindings were tested under Ubuntu 24.04.1 LTS.

Usage

Most of the ffmbindings Java classes (the one which implement CppClass) does not provide constructors. Instead, they come with factory classes which implement CppClassFactory (see CppClassFactory documentation for more details). For example to create StdString (binding to std::string) users suppose to use its factory StdStringFactory.

List of ffmbindings

Currently none of ffmbindings modules are available in Maven Central. It means in order to add them to Java projects users have to clone/build these modules locally.

Version format of ffmbindings

Version format for ffmbindings modules follows such convention:

<NATIVE_LIBRARY_VERSION>-<FFMBINDING_VERSION>

For example "ffmbindings.stdcpp:6.0.28-1.0" reads as: "ffmbindings.stdcpp" module version 1.0, for native library "libstdc++" version 6.0.28.

Defining bindings to C++ classes

Users can define FFM bindings manually, using FFM bindings API, but this is not an easy process. ffmbindings tries to provide set of Java classes, types and utilities to simplify this.

Steps:

  1. Create Java class binding for respective C++ class by implementing CppClass. ffmbindings provides default implementation of it already which users can extend (AbstractCppClass).
  2. Define MemoryLayout of a C++ class (see CppClassLayout)
  3. Create VarHandle to required fields of a C++ class
  4. Create MethodHandle to required functions of a C++ class
  5. Define Java constructor to allocate and align memory for C++ class. Users can use AbstractCppClass constructor for this.
  6. Define Java method bindings by forwarding Java calls to respective MethodHandle previously defined

The examples of all these steps are demonstrated in ScalableVolume class (see more tests inside same repository).

virtual functions

If C++ class defines virtual functions then:

To find pointer to "vtable", nm command can be used.

Example how to find "vtable" symbol of ScalableTSDFVolume class inside libOpen3D:

% nm -CDg /opt/open3d-devel-linux-x86_64-cxx11-abi-0.17.0/lib/libOpen3D.so | grep ScalableTSDFVolume | grep vtable
000000000b2b6338 V vtable for open3d::pipelines::integration::ScalableTSDFVolume
% nm -Dg /opt/open3d-devel-linux-x86_64-cxx11-abi-0.17.0/lib/libOpen3D.so | grep 000000000b2b6338
000000000b2b6338 V _ZTVN6open3d9pipelines11integration18ScalableTSDFVolumeE

Templates

Examples on templates support see in ffmbindings.eigen

Notes

Mangled C++ symbols

To create FFM binding to functions inside any library, their symbols need to be found first.

Because C++ symbols are mangled it is not as straight forward as with C. To find C++ symbols nm command can be used.

Example how to find mangled symbol for "basic_string" inside libstdc++:

  1. List all libstdc++ symbols in readable format and find unique address of "basic_string":
  2. % nm -CDg /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 | grep basic_string
    ...
    0000000000144420 W std::__cxx11::basic_string&lt;char, std::char_traits&lt;char>, std::allocator&lt;char> >::assign(char const*)
    ...
  3. By unique address 0000000000144420 obtain mangled symbol:
  4. % nm -Dg /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 | grep 0000000000144420
    0000000000144420 W _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6assignEPKc
    

Clang can be used to do this programmatically.

sizeof and MemoryLayout::byteSize

sizeof in C++ automatically accounts for padding: "When applied to a class type, the result is the number of bytes occupied by a complete object of that class, including any additional padding required to place such object in an array."

Example:
class Base {
  double d1;
  double d2;
  int type;
};
cout << sizeof(Base) << endl;

Will return 24.

MemoryLayout::byteSize does not account for padding automatically:

var base = MemoryLayout.structLayout(
    ValueLayout.JAVA_DOUBLE.withName("d1_"),
    ValueLayout.JAVA_DOUBLE.withName("d2_"),
    ValueLayout.JAVA_INT.withName("type_"));
System.out.println("base=" + base);
System.out.println("base.byteSize=" + base.byteSize());

Will return 20.

When defining MemoryLayout for C++ classes in Java you may probably want to compare if the size of both matches correctly.

If you are using sizeof and MemoryLayout::byteSize for that, keep in mind that they may not always match (which not necessary means that layout definition is wrong). For more details about this please check Jorn`s note in jextract-dev mail list