The recommended way is to read carefully the official JNI documentation.
(Disclaimer: my description below may contain errors.)
This requires not one, but two layers of wrappers (item 2 and 3):
- Java application code
- Java JNI wrapper classes (to give an object model to the library)
- C JNI wrapper (to marshal the data between JNI environment and C/C++ code)
- C++ library code
The C JNI wrapper is responsible for gaining access to Java arrays, strings, object member fields, etc. This is necessary because Java can move around arrays and objects in memory, as part of garbage collection. In some sense, C code cannot get hold of anything in the Java environment unless it obtains a reference (which signifies the thing's lifetime to the VM). Even so, one can only obtain addresses of arrays if the array and the VM support pinning (being pinned down at its current memory address). Otherwise, every access to Java data requires copying, and this copying can only be performed with the help of the JNI.
The C JNI function cannot be a class member function, by design. To be able to call C++ object member functions, the C JNI method must obtain the address of the C++ object (typically stored as a 32-bit or 64-bit integer field in the Java JNI wrapper class), and cast it into the C++ object pointer.
The biggest problems are:
It is hard for wrapper generators to generate a satisfactory Java object model given the C/C++ code, regardless of whether the latter was written in an OOP style. Wrapper generators written in the last decade do not understand C/C++ styles deep enough to be able to recognize OOP idioms. (Most might even fail to parse legal C/C++ code, due to the language's syntax complexity.)
A human wrapper-writer will make plenty of style adaptations between the two languages. Until recently, it was hard to imitate such preferences (which are not mechanical and not straightforward) in wrapper generators.
Please also take a look at