JNI callback from Tomcat using FindClass

C

colm.dougan

Hi,

I've inherited some code that uses JNI from Tomcat to call down to C to
initialise/subscribe to some messaging with a callback the other way (C
to Java) when a message is going to be passed back up to Java. This
used to work fine but a change of platform (slightly older Tomcat/4.1
than befoe, upgrade to latest Java 1.4.2 from 1.4.0) it has stopped
working.

The offending code is below (debug statements removed for brevity). It
fails on the FindClass(). It seems that the class loader is not the
one it expects to have. It seems to be assumed that the tomcat
classloader will be used but this doesn't seem to be the case. I have
been reading various internet knowdlege bases that imply that FindClass
will use the system/boot class loader and so this shouldn't work at
all. But how could it ever have worked? Was it previously relying on
bugs? Or is this a reasonable thing to do as the VM is kept attached.

I tried some things suggested on various sites/groups to hold a pointer
to the jobject initially passed down from Java to C in the native
method using env->NewGlobalRef(jobj); When I used the jobj later it
either crashed my tomcat or the GetStaticMethodID returned 0. I'm
aware there may be threading issues with that approach.

Any advice? I'm primarily interested in what the "right thing" is to
do here because it is not clear to me and I'm new to JNI. Is this even
the rigtht what to do callbacks of this type?

Thanks,
Colm


// a static instance of this class is held globally
// ...

int CommJNIHandler::setupThread( JNIEnv **env )
{


// JavaVM *m_pjVM is a private member variable
int rv = m_pjVM->GetEnv((void**)env, JNI_VERSION_1_2 );
if ( rv != JNI_OK )
{
if ( m_pjVM->AttachCurrentThread((void**)env, NULL) != 0 )
return -1;
}
return 0;
}


int CommJNIHandler::doCallBack ( int pMsgData ) {
JNIEnv *env;
if ( setupThread( &env ) != 0 )
{
return -1;
}

jclass cls = env->FindClass("COM/name/of/class");
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
return -1;
}

jmethodID mid = env->GetStaticMethodID(
cls, "executeMessageHandlerFromNative", "(I)I" );

if ( mid == 0 )
{
return -1;
}

int nRet = env->CallStaticIntMethod( cls, mid, (jint)pMsgData );
if ( env->ExceptionOccurred() )
{
env->ExceptionDescribe();
env->ExceptionClear();
}

return nRet;
}
 
C

Chris Uppal

The offending code is below (debug statements removed for brevity). It
fails on the FindClass(). It seems that the class loader is not the
one it expects to have. It seems to be assumed that the tomcat
classloader will be used but this doesn't seem to be the case. I have
been reading various internet knowdlege bases that imply that FindClass
will use the system/boot class loader and so this shouldn't work at
all. But how could it ever have worked? Was it previously relying on
bugs? Or is this a reasonable thing to do as the VM is kept attached.

It's difficult to be sure what's going on here. I think it depends on things
like how you use C++ threads, when callbacks are invoked, and perhaps even on
where your .classfiles are kept :-(

Here's how I understand it.

FindClass() uses the classloader of the Java code where the currently executing
native method is defined. It not clear from the spec what happens in other
cases, but I /assume/ that you are in JNI code because you are using the
invocation API (not the case for your application) then it'll use the system
classloader. I also assume (which may be relevant -- see below) that when the
DLL (or equivalent) is initialised (the JNI_OnLoad() handler function in the
DLL) that FindClass() will use the classloader of the class which is calling
System.loadLibrary(). Lastly, I assume that if a C++ thread (i.e. started from
C++ and calling AttachCurrentThread(), rather than from Java code), uses
FindClass() then that will use the system classloader.

NB: all the above assumptions are /only/ assumptions -- i.e. I don't know for
sure (although they seem reasonable).

So, which classloader will be used depends on which code looks for it. (Nice
and simple isn't it ;-). From your code snippet it appears that you are using
independent C++ threads, which might be one source of your problems. If you
are, then I suggest either doing the FindClass() in the DLL's JNI_OnLoad(), or
in some native method which is used to initialise your sub-system (if there is
one). You would then have to cache a global reference to it (as you have
already tried). Other than that, I don't see a problem with invoking
callbacks from off-thread (assuming the code is correct).

Possibly the change in Tomcat versions causes a difference in the
initialisation order, or something similar so that the FindClass() happens in a
different context -- but that's pure guesswork.

OTOH, if your JNI code is invoked from Java, so that the callbacks are just
calls back "up the stack" as it were, then I don't see why you should be having
problems at all, so maybe the problem is caused by something else entirely.

Another issue which may be relevant (probably not, but I'll mention it anyway),
is that JNI will only load each DLL for one classloader. So if you have
multiple applications under Tomcat which use the "same" class (and its native
code) then you /have/ to put that class in the shared classes area. You can't
have native code duplicated in two applications, it has to be shared.

-- chris
 

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

Similar Threads


Members online

Forum statistics

Threads
473,968
Messages
2,570,153
Members
46,699
Latest member
AnneRosen

Latest Threads

Top