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:
List of supported C++ features:
Here are the list of some of the alternative ideas for creating C++ bindings. These are just an ideas so they may not work.
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 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).
Most of the ffmbindings were tested under Ubuntu 24.04.1 LTS.
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.
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 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.
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:
The examples of all these steps are demonstrated in ScalableVolume class (see more tests inside same repository).
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
Examples on templates support see in ffmbindings.eigen
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++:
% nm -CDg /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 | grep basic_string ... 0000000000144420 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::assign(char const*) ...
% 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 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