Loading methods of a specific class from a C++ library using JNI
I have a C++ library xyz. It has many classes like xyzA
, xyzB
etc. I want to use the method getAge()
from the class xyzA
which is in the xyz library.
The xyz.so
file already exists.
Steps I have followed:
Created a Java class
xyz.java
class xyz { public native int getAge(); public static void main(String[] args) { new xyz().getAge(); } static { System.loadLibrary("xyz"); } }
Created the header for the Java class.
/* DO NOT EDIT THIS FILE - it is machine ge开发者_运维问答nerated */ #include <jni.h> /* Header for class xyz */ #ifndef _Included_xyz #define _Included_xyz #ifdef __cplusplus extern "C" { #endif /* * Class: xyz * Method: getAge * Signature: ()I */ JNIEXPORT jint JNICALL Java_xyz_getAge (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
The cpp wrapper class looks like:
#include <stdio.h> #include "xyz.h" #include <jni.h> JNIEXPORT jint JNICALL Java_xyz_getAge(JNIEnv *, jobject) { // some code }
I successfully compile the class as follows:
gcc -fPIC -shared -l stdc++ -I/grid/0/gs/java/jdk64/current/include -I/grid/0/gs/java/jdk64/current/include/linux xyz.cpp
Then run the Java prog as:
java -Djava.library.path=/grid/0/tmp/direct/lib xyz
I get the following error:
Exception in thread "main" java.lang.UnsatisfiedLinkError: xyz.getAge()I at xyz.getAge(Native Method) at xyz.main(xyz.java:6)
It cannot find the method getAge()
specific to the class xyzA
. How can that method be accessed? Also, is the library getting linked through my wrapper class?
Any pointers would be appreciated.
Thanks.
If you are running on Unix, the shared library has to be named libxyz.so
, not xyz.so
.
This error normally means that the library has been successfully loaded,
but that there is an inconsistency in the signature of the function.
Globally, your code looks correct, but we do put an extern "C"
in
front of JNIEXPORT
in our code base. We don't use the generated
headers, so the definition of the function is the only place we could
specify extern "C"
. In your case, I think that the compiler is
supposed to recognize that the function declared in the header and the
one defined in your .cpp
are the same, and define the function as
extern "C"
, but having never done it this way, I'm not 100% sure. You
can verify by doing something like:
nm -C libxyz.so | egrep getAge
The function should appear as a C function, with a T in the second column; without the -C, it should appear unmangled. Note that the slightest difference in the definition and the declaration will mean that you're defining a different function; I don't see one in the code you've posted, but it's worth double checking.
(I'd also wrap the call to LoadLibrary in a try block, just to be sure.)
EDITED to add some additional information:
I'm not sure when and how Linux links the additional libraries needed;
we've always loaded all of our libraries explicitly. You might try
adding a call to dlopen
in either JNI_OnLoad
or the constructor of a
static object. (I would recommend this anyway, in order to control the
arguments to dlopen
. We found that when loading several different
.so
, RTTI didn't work accross library boundaries if we didn't.) I
would expect not doing so to result in a loader error, but it all
depends on when Linux tries to load libxyz.so
; if it only does so when
the JVM calls dlsym
, then you'll get the above error. (I think this
depends on the arguments the JVM passes to dlopen
when you call
java.lang.System.LoadLibrary
: if it passes RTLD_LAZY
or RTLD_NOW
.
By forcing the load in JNI_OnLoad
or the constructor of a static
object, you more or less guarantee that loader errors appear as loader
errors, and not link errors, later.)
Exported function names in C++ libraries are mangled: The plain function name is decorated with class and namespace names, parameter and return types. For instance, here is one of the methods from a boost library:
?estimate_max_state_count@?$perl_matcher@PB_WV?$alloca
tor@U?$sub_match@PB_W@boost@@@std@@U?$regex_traits@_WV?$w32_regex_traits@_W@boos
t@@@boost@@@re_detail@boost@@AAEXPAUrandom_access_iterator_tag@std@@@Z
The format of the name is not standardised and varies from compiler to compiler. The end result is that it is difficult to call an exported C++ member function unless you're writing another module in C++ and compiling it using the same compiler. Trying to call from Java is more trouble than it is worth.
Instead, if the C++ library is yours, export helper functions using the extern "C"
calling convention:
Foo * foo;
extern "C"
{
void myfunc()
{
foo->bar(); // Call C++ function within a C-style function.
}
}
If the library is third-party, you should wrap it with your own library which exposes necessary functions using the C-style export, as per above.
精彩评论