I have been battling with this problem for some time but now, thank {deity of choice}, it is solved. This post is to share the technical information gleaned in such a way as to preclude some other poor geek going through the same trauma.
While there are many worthy posts on this topic, it takes an age to find the appropriate information. Many talk about the Gnu compiler, Cygwin and name mangling problems or package naming conventions when using javah to generate the JNI header. Alas, none of these issues addressed the problem I encountered!
My company uses Microsoft Visual Studio for all Windows target development. The C/C++ compiler is VC++ 7.0 although I suspect this issue would be the same on VC++ 6.0.
We have a requirement to develop a java interface to allow bi-directional communication with an EFT POS device. No problem, we said, Java JNI to talk to the C code we already use for such communications.
We developed said java interface and, after thorough testing, released it to the client. A couple of days later they asked us if we could make the java class we had implemented into a package. Fine, we said, we'll add a package declaration at the start of the Java file and copy it into a subdirectory, no worries!
But , of course, that doesn't work. Having implemented the bew build files and reconstructed the system as a package, the test harness reported an unsatisfied link error every time it tried to call a JNI function.
Not having a clue what the problem was I dutifully went through all the posts herein and discovered many others had encountered similar problems, but, none of the fixes described applied to my problem.
Common issues raised were that one must change the names of the JNI calls to reflect the fact that it is part of a package.
native declaration in spdh.java
private native int SPDH1_00_INITJNI (int[] Handle, String Port) ;
generate JNI header with :
IngenicoJNI is the name of the package, spdh is the name of the class within this package. Using the . format (IngenicoJNI.spdh) javah works out that the class file will be in subdirectory IngenicoJNI.
javah -jni IngenicoJNI.spdh
produces IngenicoJNI_spdh.h
/*
* Class: IngenicoJNI_spdh
* Method: SPDH1_00_INITJNI
* Signature: ([ILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_IngenicoJNI_spdh_SPDH1_100_1INITJNI
(JNIEnv *, jobject, jintArray, jstring);
Rather than trying to work out what the name generation should be, it's much easier to simply cut and paste the method name from the generated header into the JNI C source file.
Remember to turn off name mangling in the JNI C file
#ifdef __cplusplus
extern "C"
{
#endif
//... All the JNI C code
JNIEXPORT jint JNICALL Java_IngenicoJNI_spdh_SPDH1_100_1INITJNI
(JNIEnv * Env, jobject jobj, jintArray Handle, jstring Port)
{
_ING_BYTE FSMCounter = _ING_LOCAL_FSM_STANDARDPROCESS ;
_ING_BOOL Finished = _ING_FALSE ;
_ING_NUM Condition = _ING_STATE_GOOD ;
.
.
.
.
return ((jint) Condition) ;
}
}
#ifdef __cplusplus
}
#endif
Java package file location
Remember that for packages, the java needs to reside in a subdirectory reflecting the class heirarchy; thus, for the package IngenicoJNI, class spdh (and given a simple classpath of CLASSPATH=.) spdh.java needs to live in subdirectory IngenicoJNI.
The problem
If you build your nice shiny new JNI dll file in a binary editor, you'll notice that the exported function names have @nn appended where the nn reflects the number of bytes pushed onto the stack when the call is made. This is perfectly legitimate mangling and is performed by the linker to help prevent someone changing a DLL interface without changing client code of the DLL interface if the DLL method parameters change. PLEASE NOTE, CONTRARY TO POPULAR OPINION, THE JAVA LOADER IS COOL WITH THIS!!!
So, given the example above, we would have an exported function in the DLL of:
_Java_IngenicoJNI_spdh_SPDH1_100_1INITJNI@16
Note the leading underscore and the appended @16.
Unfortunately, even though you've done everything by the book, your test harness will invariably claim:
Exception in thread "main" java.lang.UnsatisfiedLinkError: IngenicoJNI.spdh.SPDH1_00_INITJNI([ILjava/lang/String;)I
at IngenicoJNI.spdh.SPDH1_00_INITJNI(Native Method)
at IngenicoJNI.spdh.Initialise(spdh.java:30)
at TestSPDH.main(TestSPDH.java:521)
It turned out that my library load statement in the spdh class:
static
{
System.load ("F:\\dev\\ingenico\\Libraries\\FNB PinPad\\spdh0000.dll") ;
}
Is an extremely dangerous way to load a DLL! especially if your newly fixed up DLL (with the correct naming convention) is in a different directory!!
Rather use:
static
{
System.loadLibrary("spdh0000");
}
Which uses the PATH environment variable to determine where the library is (preferably the current working firectory if you are testing!)
Hope this diatribe is of some use to anyone else getting grief from this issue.