Java Native Interface problems

S

Stephen Kellett

Hi Folks,

History:
I'm working on a Java product for blind and partially sighted people.
The product uses a 3rd party, proprietary speech engine. We started this
project in the 1997, so we had to use native libraries (which is why we
aren't using the free Sun Speech engine). As a result, we are still
using the native interface.

Problem:
In the past I've built this native library and used it with no problems,
using VisualCafe 3.0 and JDK1.1.3. I've recently had to upgrade to
JDK1.4
(1.4.2_03 to be exact) to get access to some changes in Java
functionality. So I'm using NetBeans 3.51 with JDK1.4.

As a result I've had to rebuild my native libraries - no problem, had to
change a few library linkages, but all went fine. However when I try to
use
the libraries, the correct native library loads, but then any of the
methods
gets an Unsatisfied link error. I've tried commenting out complex
methods
leaving only the trivial methods and they fail. I've recreated the
headers
using javah only to find there is no change between the previous headers
I
used and these new ones.

Included below is the native class definition, the header file and the
java
error messages - anyone got any ideas what is going wrong. The correct
DLL is being loaded, I know that much.

I hope some of you can help. Replies by email or to the newsgroup will
be most appreciated.

Stephen

Loading native library 'SpeechIO'...
Native library 'SpeechIO' loaded.
Jan 27, 2004 12:45:52 PM java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Depends-On
Jan 27, 2004 12:45:52 PM java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Depends-On
Jan 27, 2004 12:45:52 PM java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Depends-On
Jan 27, 2004 12:45:52 PM java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Depends-On
Jan 27, 2004 12:45:52 PM java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Depends-On
Jan 27, 2004 12:45:52 PM java.util.jar.Attributes read
WARNING: Duplicate name in Manifest: Depends-On
java.lang.UnsatisfiedLinkError: connect
at cw.speech.SpeechIO.connect(Native Method)
at cw.speech.SpeechIO.<init>(SpeechIO.java:76)
at cw.speech.Speech.initSpeech(Speech.java:997)
at cw.speech.Speech.<init>(Speech.java:724)
at cw.wordAloud.CommandHandler.<init>(CommandHandler.java:582)
at

cw.wordAloud.LearningDisabledUI.setupCommandHandler(LearningDisabledUI.ja
va:1621)
at cw.wordAloud.Spoken.main(Spoken.java:535)

package cw.speech;

public class SpeechIO
{
public SpeechIO()
{
long id = 0;

try
{
// tell the speechio.dll where the product is installed
// note that we must convert semi colons to something else
// so that speechIO.dll's parameter parser doesn't mangle
the
// values

String str;
String dir;

str = "InstallationDirectory_";
dir = cw.wordAloud.fileLocations.getInstallationDirectory();
dir = dir.replace(':', '*');
str += dir;

// finally add a dummy value

str += ":0";

command(str, 0);

// now connect to the speechio.dll, this starts the comms
thread
// in the speechio.dll so that windows messages get serviced

id = connect();
}
catch (Exception e)
{
System.out.println("SpeechIO failed to connect to speech

engine\n");
}
//System.out.println("SpeechIO ID is " + id + ". It should be
non

zero");
}

public void finalize()
{
try
{
close();
}
catch (Exception e)
{
}
}

public native long connect();

public native void close();

public native void speak(String data,
int token);

public native void command(String data,
int token);

public native void configure(String data,
int token);

public native void convertDocToHTML(String converter,
String fileToConvert,
String outputFile,
String title);

static
{
System.out.println("Loading native library
'SpeechIO'...");
try
{
System.loadLibrary("speechio");

System.out.println("Native library 'SpeechIO'

loaded.");
}
catch(UnsatisfiedLinkError e)
{
System.out.println("Failed to load SpeechIO

library");
System.out.println(e);
}
catch(SecurityException e)
{
System.out.println("Failed to load SpeechIO

library");
System.out.println(e);
}
}
}

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class SpeechIO */

#ifndef _Included_SpeechIO
#define _Included_SpeechIO
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: SpeechIO
* Method: connect
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_SpeechIO_connect
(JNIEnv *, jobject);

/*
* Class: SpeechIO
* Method: close
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_SpeechIO_close
(JNIEnv *, jobject);

/*
* Class: SpeechIO
* Method: speak
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_SpeechIO_speak
(JNIEnv *, jobject, jstring, jint);

/*
* Class: SpeechIO
* Method: command
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_SpeechIO_command
(JNIEnv *, jobject, jstring, jint);

/*
* Class: SpeechIO
* Method: configure
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_SpeechIO_configure
(JNIEnv *, jobject, jstring, jint);

// this function added by hand...

JNIEXPORT void JNICALL Java_SpeechIO_convertDocToHTML(JNIEnv *, jobject,


jstring, jstring, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
 
M

Manfred Rosenboom

Hi Stephen,

your Java code and the javah generated header file do not match:
package cw.speech;

public class SpeechIO
{
...
public native long connect();
...
}

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class SpeechIO */

#ifndef _Included_SpeechIO
#define _Included_SpeechIO
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: SpeechIO
* Method: connect
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_SpeechIO_connect
(JNIEnv *, jobject);
...
#ifdef __cplusplus
}
#endif
#endif

The class SpeechIO is located in the package cw.speech
The package name is part of the javah generated prototype in the header
file. This part is missing in your example.

IMHO you added the package name while changing from JDK 1.1.x to 1.4.x, because
JDK 1.4 needs packages. If this is true, you must run javah again and adapt
the name of the native routines to the new names, generated by javah.

Best,
Manfred
 
S

Stephen Kellett

Manfred Rosenboom said:
The class SpeechIO is located in the package cw.speech
The package name is part of the javah generated prototype in the header
file. This part is missing in your example.

IMHO you added the package name while changing from JDK 1.1.x to 1.4.x, because
JDK 1.4 needs packages.

Yup. Caused me a lot of confusion the class compiling but other classes
failing to import it. So I added it to a package just to please the
compiler.
If this is true, you must run javah again and adapt
the name of the native routines to the new names, generated by javah.

Thank you. Thats exactly the response I expected, and its what I looked
from in the header files. I ran javah again and got the header I posted.
Anyway thanks for the reply, at least I now know that my instincts were
right and that something has gone wrong generating the header etc.

Appreciated.

Stephen
 
S

Stephen Kellett

Manfred Rosenboom said:
your Java code and the javah generated header file do not match:
The class SpeechIO is located in the package cw.speech
The package name is part of the javah generated prototype in the header
file. This part is missing in your example.

I've re-worked everything based on your feedback - I've even examined
the byte code and sure enough it is stating the class is
cw/speech/SpeechIO - so the correct source file is compiled. I've made
javah verbose and it is picking up the correct SpeechIO.class.

d:\j2sdk1.4.203\bin\javah.exe is not generating any package specific
header files. As a result I get prototypes of the following form.

/*
* Class: SpeechIO
* Method: command
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_SpeechIO_command
(JNIEnv *, jobject, jstring, jint);

I've no idea what is going wrong. I'll edit the header files myself. My
question is, given that it is refusing to put in the correct package
name hints, what form do they take? The package is cw.speech.

Would it be:
JNIEXPORT void JNICALL Java_cw_speech_SpeechIO_command
(JNIEnv *, jobject, jstring, jint);

or is it something else?

Regards

Stephen
 
G

Gordon Beaton

d:\j2sdk1.4.203\bin\javah.exe is not generating any package specific
header files.

You need to specify a fully qualified classname when you run javah, if
you want it to generate the package information.

/gordon
 
S

Stephen Kellett

Gordon Beaton <[email protected]> said:
You need to specify a fully qualified classname when you run javah, if
you want it to generate the package information.

Thanks - that was certainly not obvious.

The class file and the java file both contain information that indicate
the package. If I'm sitting in directory cw/speech and run javah on
SpeechIO and it picks up ./SpeechIO.class then it should be clever
enough to think 'Aha! The class name is "cw/speech/SpeechIO"' as that is
what is encoded in the byte code (I checked).

Once again, thanks, I've changed things and now have it working.

Stephen
 
C

Chris Smith

Stephen said:
Thanks - that was certainly not obvious.

The class file and the java file both contain information that indicate
the package. If I'm sitting in directory cw/speech and run javah on
SpeechIO and it picks up ./SpeechIO.class then it should be clever
enough to think 'Aha! The class name is "cw/speech/SpeechIO"' as that is
what is encoded in the byte code (I checked).

I agree that it should check the bytecode of the class file against the
class name you've given, and fail with a helpful error message if they
don't match. I certainly don't agree that it should continue to run
even though you've given it the wrong class name!

--
www.designacourse.com
The Easiest Way to Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation
 
S

Stephen Kellett

Chris Smith said:
I agree that it should check the bytecode of the class file against the
class name you've given, and fail with a helpful error message if they
don't match. I certainly don't agree that it should continue to run
even though you've given it the wrong class name!

If I'm in cw/speech, that implies I am in package cw.speech, therefore
specifying SpeechIO is correct as that is specify cw.speech implicitly.
The byte code agrees with this position, as it correctly identifies the
class as cw.speech.SpeechIO. I have to disagree with you.

If I specify cw.speech.SpeechIO whilst in cw/speech it fails with an
error message. I need to do:
cd ..
cd ..
then
specify cw.speech.SpeechIO
which produces the header file, in the wrong directory of course.
"Wrong" being "not the same directory as the class". Yes there is an
option for this, I just think the program does not work in a logical
manner.

Either way, I've got the program working again and I'm thankful for the
help provided.
 
G

Gordon Beaton

Thanks - that was certainly not obvious.

One way to avoid the symbol naming issue is to use RegisterNatives()
from the JNI_OnLoad() function in your library. There doesn't need to
be any correlation between the names of your native methods and the
ones generated by javah, package or no package.

Define a set of methods and call them what you like, but obey the
signatures described by javah. Then define an array mapping the method
names you declared in the java source to the names you used in the C
source:

static JNINativeMethod all_methods[] = {
{ "foo", "()V", do_foo },
{ "baz", "()V", do_baz },
};

static int n_methods = sizeof(all_methods)/sizeof(*all_methods);

One caveat if you're using C++, note that your native methods must be
declared "extern C". That happens automatically if you use the exact
same names as in the header file, but if you break the naming
convention you need to declare them that way yourself.

JNI_OnLoad() will be invoked when the library is loaded. At that time,
you can register your native functions "manually" with the following
code. You need to provide the (fully qualified!) classname in just one
place:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv *env;
jclass cls;

if ((*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_2) == JNI_OK) {

if ((cls = (*env)->FindClass(env,"my/package/MyClass"))) {
if (!((*env)->RegisterNatives(env,cls,all_methods,n_methods))) {
return JNI_VERSION_1_2;
}
}

if ((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionDescribe(env);
}
}

fprintf(stderr,"%s:%d: JNI_OnLoad failed\n",__FILE__,__LINE__);
return -1;
}

I find this helps make my native code more reader friendly, and I
don't need to cut and paste a whole set of method declarations from
the generated header file to the source if I change the package
declaration of a class. Just one line (usually a constant macro at the
start of the file) needs to be changed.

/gordon
 
S

Stephen Kellett

Gordon Beaton <[email protected]> said:
One way to avoid the symbol naming issue is to use RegisterNatives()
from the JNI_OnLoad() function in your library. There doesn't need to
be any correlation between the names of your native methods and the
ones generated by javah, package or no package.

Nice, I didn't know about that - been doing a lot of C++ for while,
hence out of the loop on Java a bit (although very in the loop with
JVMDI and JVMPI).

Stephen
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,736
Latest member
AdolphBig6

Latest Threads

Top