Home » Eclipse Projects » Rich Client Platform (RCP) » JNI cannot find Java Routines with RCP
| |
Re: JNI cannot find Java Routines with RCP [message #442851 is a reply to message #442764] |
Fri, 20 January 2006 16:21 |
Kenneth Evans, Jr. Messages: 77 Registered: July 2009 |
Member |
|
|
Paul,
It is not a problem with the runtime finding the jar files from the
plug-in. In the exported project, the jars are in the directory for the
plugin that owns them. (There are no jars in jars.) The runtime finds
them. If there were no JNI or even if the JNI implementation did not need
to access the classes in the jar, it would work. (I can do that, and I know
it *does* work.)
The problem comes when the *JNI C code* needs to reference a class in
the jar (as for a callback). It uses the (C) function FindClass to do that.
FindClass is part of the JNI package and is part of the J2SDK, not Eclipse.
Thus, it does not know about Eclipe's internal ways of finding jars without
using the classpath.
The classpath used by the RCP project only includes only startup.jar,
and startup.jar only has classes from that RCP project, not from other
plug-ins.
I believe this is a factor that has been overlooked by the Eclipse
developers.
-Ken
"Paul Webster" <pwebster@ca.ibm.com> wrote in message
news:dqo62t$a17$1@utils.eclipse.org...
> When you have a 3rd party jar in a plugin, you need to add it to the
> runtime using a set of steps (like
> http://dev.eclipse.org/newslists/news.eclipse.platform/msg48 366.html +
> eclipse can load jars from within jars, with a performance hit).
>
> Then your JNI should find it.
>
>
> Build paths are used when you launch a program from within eclipse, but
> the manifest runtime is used when you launch the app outside of eclipse.
>
> Later,
> PW
|
|
|
Re: JNI cannot find Java Routines with RCP [message #442864 is a reply to message #442851] |
Fri, 20 January 2006 18:54 |
|
Kenneth Evans wrote:
> Paul,
>
> It is not a problem with the runtime finding the jar files from the
> plug-in.
Hmmm....
I have pluginA that exports ex.base.Helper in the runtime manifest, and
pluginB that depends on pluginA. pluginB has a JNI lib and the library has:
jclass c = env->FindClass(str);
where the string passed in (str) is ex/base/Helper. The JNI class also
has another method with the standard
getClass().getClassLoader().loadClass("ex.base.Helper");
My pluginB native method finds the class (it's not NULL, and didn't
throw a NoClassDefFoundError). Although, when I asked for
"ex.base.Helper" it threw a NoClassDefFoundError. The jni.h file says
it can be used 'env->FindClass("java/lang/String")'
So it's definitely going through the runtime plugin class loader, 'cause
it can find a class from a plugin that it depends on. I'm using Sun's
JDK 1.4.2
Later,
PW
Paul Webster
http://wiki.eclipse.org/Platform_Command_Framework
http://wiki.eclipse.org/Command_Core_Expressions
http://wiki.eclipse.org/Menu_Contributions
|
|
|
Re: JNI cannot find Java Routines with RCP [message #442958 is a reply to message #442864] |
Fri, 20 January 2006 23:29 |
Kenneth Evans, Jr. Messages: 77 Registered: July 2009 |
Member |
|
|
Paul,
That is interesting. You seem to be doing the same thing I am. I have
a plugin A that just has the jar files. I am using it in another plug-in B.
A is a dependency of B. There are no exports. You did not say if yours is
an RCP application. You also did not say what the java.class.path (actually
used) is. (You would probably have to print that out from the code -- what
I am doing.)
1. I do not have trouble with the same JNI in a standalone SWT application
(that is pretty much the same as the plug-in).
2. I since tried it as a plug-in (vs. RCP). The java.class.path is
startup.jar, same as for the RCP application., and it also fails the same
way.
3. It throws a C++ exception, not a NoClassDefFoundError. I cannot trace
any further as FindClass is not built debug.
4. I may have an additional complication in that this application is
threaded. BTW the JNI code and the threaded application it uses are old
code that has been well tested.
5. I can implement the JNI part in Java by changing one flag in the plug-in.
It then works. And it obviously finds the DLL. Also, it works with the JNI
until the offending routine gets called. That is, the GUI, SWT part appears
and functions. Thus, it is apparently not the structure of the plug-in that
is the problem.
This is the full code that returns clazz = null, causing the failure shortly
down the road:
extern "C" {
static int theMessageCallback(const char* pFormat, va_list args) {
jobject callback= (jobject) epicsThreadPrivateGet(messageCallbackID);
if (callback==NULL) return ECA_NORMAL;
JNIEnv* env;
_jvm->AttachCurrentThread((void**)&env, NULL);
char buf[1024];
sprintf(buf, pFormat, args);
jstring msg= env->NewStringUTF(buf);
jclass clazz=
env->FindClass("gov/aps/jca/jni/JNIContextMessageCallback");
jmethodID methodID= env->GetMethodID(clazz, "fire",
"(Ljava/lang/String;)V");
env->CallVoidMethod(callback, methodID, msg);
_jvm->DetachCurrentThread();
return ECA_NORMAL;
}
}
This is a C callback and the idea is to call the Java callback. (The
problem is that clazz is null. env is not null.)
I don't know what to say beyond this. How does this compare to what you are
doing?
Thanks,
-Ken
"Paul Webster" <pwebster@ca.ibm.com> wrote in message
news:dqrbko$2ba$1@utils.eclipse.org...
> Kenneth Evans wrote:
> > Paul,
> >
> > It is not a problem with the runtime finding the jar files from the
> > plug-in.
>
> Hmmm....
>
> I have pluginA that exports ex.base.Helper in the runtime manifest, and
> pluginB that depends on pluginA. pluginB has a JNI lib and the library
has:
>
> jclass c = env->FindClass(str);
>
> where the string passed in (str) is ex/base/Helper. The JNI class also
> has another method with the standard
> getClass().getClassLoader().loadClass("ex.base.Helper");
>
> My pluginB native method finds the class (it's not NULL, and didn't
> throw a NoClassDefFoundError). Although, when I asked for
> "ex.base.Helper" it threw a NoClassDefFoundError. The jni.h file says
> it can be used 'env->FindClass("java/lang/String")'
>
> So it's definitely going through the runtime plugin class loader, 'cause
> it can find a class from a plugin that it depends on. I'm using Sun's
> JDK 1.4.2
>
> Later,
> PW
|
|
|
Re: JNI cannot find Java Routines with RCP [message #443092 is a reply to message #442958] |
Mon, 23 January 2006 14:55 |
|
I can give you a slightly better description. PluginA (com.ex.base) is
a normal plugin. It has the class com.ex.base.Helper, and a standard
manifest:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Base Plug-in
Bundle-SymbolicName: com.ex.base
Bundle-Version: 1.0.0
Bundle-Activator: com.ex.base.Activator
Bundle-Vendor: EX
Bundle-Localization: plugin
Require-Bundle: org.eclipse.core.runtime
Eclipse-LazyStart: true
Export-Package: com.ex.base
It has to export the com.ex.base package or no downstream plugins can
load Helper.
PluginB (com.ex.jni) is my shared lib plugin. It has a couple of GUI
actions, and the shared library in os/linux/x86/libMyEval.so. The code
I tried was pretty simple. Java
static {
System.loadLibrary("MyEval");
}
public native int evaluate(String name);
and C++:
jint JNICALL Java_com_ex_jni_Eval_evaluate
(JNIEnv *env, jobject obj, jstring name)
{
const char *str = env->GetStringUTFChars(name, 0);
printf("Found %s\n", str);
jclass c = env->FindClass(str);
env->ReleaseStringUTFChars(name, str);
if (c) {
return (jint)100;
}
return (jint)5;
}
My manifest for PluginB is that depends on PluginA is:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Jni Plug-in
Bundle-SymbolicName: com.ex.jni; singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: com.ex.jni.Activator
Bundle-Vendor: EX
Bundle-Localization: plugin
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
com.ex.base
Eclipse-LazyStart: true
I deployed these as 2 plugins in an eclipse install (technically not
RCP, but they work the same). My starting classpath was startup.jar.
Eclipse took care of managing the java.library.path.
If it's really not working for you, maybe you could put together a
simplified example of the callback scenario and create a bug report
against Eclipse.
Later,
PW
Paul Webster
http://wiki.eclipse.org/Platform_Command_Framework
http://wiki.eclipse.org/Command_Core_Expressions
http://wiki.eclipse.org/Menu_Contributions
|
|
| |
Re: JNI cannot find Java Routines with RCP [message #443250 is a reply to message #443198] |
Wed, 25 January 2006 13:39 |
|
Alex Blewitt wrote:
> One thing that's worth noting with JNI and Java libraries; when your JNI code is loaded (with whatever code does the System.loadLibrary call), that DLL is associated with the classloader that loaded it. So it can only see classes that are defined in that Jar, or are on that Jar's classpath.
>
>
> If you have:
>
> NativePlugin (does the System.loadLibrary())
> NativeClass
>
> JavaPlugin (depends on NativePlugin)
> JavaClass
>
> then I'd expect that the native code could find 'NativeClass', but not 'JavaClass'. That would be true regardless of which library called the native code.
So that's definitely true, Alex. My native class could find my other
class because my native plugin depended on my java plugin. If your
native plugin was your library plugin and your java plugin depended on
it and provided the class to be loaded, you'd have to solve it the
standard eclipse way (buddy-classloading). But in that case, just a
getClass().getClassLoader().loadClass("x"); in the native plugin would
also fail (the problem being classpaths and not JNI vs Java).
Later,
PW
Paul Webster
http://wiki.eclipse.org/Platform_Command_Framework
http://wiki.eclipse.org/Command_Core_Expressions
http://wiki.eclipse.org/Menu_Contributions
|
|
|
Re: JNI cannot find Java Routines with RCP [message #443268 is a reply to message #443198] |
Wed, 25 January 2006 19:07 |
Kenneth Evans, Jr. Messages: 77 Registered: July 2009 |
Member |
|
|
Alex and Paul,
Alex, it *is* calling routines in the jar that loaded it, and I am not
sure this is necessary, as long as it is in the java.class.path for this
instance of Java[w]. Paul, my class is different from yours in that it is
in a C callback from the native library. This library knows nothing about
Java, and its prototype doesn't have an env as an argument. I can do
whatever I want, however, in this callback, and for this implementation I
call the Java routine that will handle it in Java, getting the env from the
thread. This is different from what you are doing (and what I do
elsewhere). In that case you just have the C implementation of a Java
class, and it gets passed the env, which (indirectly) knows about the
classpath.
I am now convinced this is a bug. The problem is that the C function,
FindClass, which is purely J2SDK, not Eclipse, uses the classpath to find
the class. With an RCP application, Eclipse apparently uses
<app-dir>/startup.jar as the classpath. startup.jar has very little in it
and little, if anything, that is specific to your RCP app. Consequently
FindClass does not find your class in this case. Your RCP app finds the
other classes in whatever manner Eclipse uses to locate jars in plugins,
presumably using its own classloaders.
The reason it works standalone is that I specify the classpath
specifically to include that jar, and FindClass uses that classpath and
finds it.
I was unable to get the RCP app to use my classpath, for instance by
specifying it in vmargs. Eclipse apparently overwrites the classpath with
the above. I would consider this also to be a bug.
I could make it work by manually putting the package in startup.jar.
This allows FindClass to locate it in the jar file (while Eclipse apparently
finds it in the plugin directory). This is not a good solution.
I am going to submit it as a bug.
-Ken
|
|
| |
Re: JNI cannot find Java Routines with RCP [message #443278 is a reply to message #443268] |
Wed, 25 January 2006 23:13 |
Eclipse User |
|
|
|
Originally posted by: paulnews.gmail.com
Here are some more thoughts on JNI and class loaders.
Kenneth Evans wrote:
> call the Java routine that will handle it in Java, getting the env from the
> thread. This is different from what you are doing (and what I do
> elsewhere). In that case you just have the C implementation of a Java
> class, and it gets passed the env, which (indirectly) knows about the
> classpath.
It's JVM control from a C function ... it doesn't matter that my C
function was called because it's a java-native call, control of the
program has passed to my shared library and talking to the JVM is done
through "env" ... I looked at your example more thoroughly and you have
to go to a _jvm variable (which gives you access to the running JVM).
But the class loader that's available is the one that was available when
the JVM called into your C function. I'm guessing if you started the
_jvm from a C program (embedded JVM as opposed to a java native call)
the env your _jvm would return gives you the system class loader ...
which by itself wouldn't have access to plugin classes in the pure Java
case anyway.
> I am now convinced this is a bug. The problem is that the C function,
> FindClass, which is purely J2SDK, not Eclipse, uses the classpath to find
> the class.
And that's not true. FindClass(*), the J2SDK function, will correctly
use the classloader that's in its "context". In my example, it is using
the plugin classloader when in C code that's called from a plugin. So I
know that works.
> I was unable to get the RCP app to use my classpath, for instance by
> specifying it in vmargs. Eclipse apparently overwrites the classpath with
> the above. I would consider this also to be a bug.
Actually, it's a feature that plugins control the classpath.
Looking back through your messages, I guess I'm missing bits of
information like 1) how do you start your program 2) how do you start
the JVM so that it calls eclipse 3) is it a java native call that sets
up the C callback to the native library, and if so when 4) which bits of
code/native libraries/java calls are in which plugins
If you post the bug number we can monitor it.
Later,
PW
|
|
| |
Re: JNI cannot find Java Routines with RCP [message #444716 is a reply to message #442735] |
Sun, 19 February 2006 01:20 |
Kenneth Evans, Jr. Messages: 77 Registered: July 2009 |
Member |
|
|
I submitted this as a bug:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=125250. However, changing the
Eclipse behavior is not feasible. I found a workaround, and this is a
summary of the problem and solution. Thanks to those of you who helped.
An added note to what is below: I have not determined if FindClass *always*
uses the default class loader in situations, such as callbacks, where it is
necessary to call AttachCurrentThread, or whether it is because my callback
came back in a native thread that was independent of the Java VM. Class
loaders are changed on a per thread basis (Thread.setContextCalssLoader) ,
so the Eclipse class loader would not have been the default (after using
AttachThread to make it a Java thread) on that thread in any event.
-Ken
JNI implementations that work outside of Eclipse, may not work under
Eclipse.
In particular, there is a problem using Eclipse with a JNI
implementation that uses FindClass in a function where the JNIEnv
pointer is not available, such as in a C callback. The reason is that
FindClass, in this case, seems to use the classpath to find the class.
If the desired function is in the classpath, as it would typically be
in a stand-alone application, there is no problem. However, under
Eclipse, it is not in the classpath, because Eclipse, by itself or as
an RCP application, overwrites the classpath with startup.jar, which
only includes generic launcher classes. Eclipse then uses its own
class loader to find classes.
The Eclipse class loader does appear to be used by FindClass in JNI
functions which are passed the JNIEnv pointer, but not when you have
to use AttachCurrentThread to get the JNIEnv pointer.
Example:
static JavaVM* jvm; // Global variable
void myCallback(void) {
JNIEnv* env;
jvm->AttachCurrentThread((void**)&env, NULL);
// Fails if "some/class" is not in the classpath:
jclass cls = env->FindClass("some/class");
jmethodID methodID = env->GetMethodID(cls, "methodName",
"(Ljava/lang/String;)V or whatever signature");
env->CallVoidMethod(callback, methodID, ...);
jvm->DetachCurrentThread();
}
}
A solution is to cache the method ID, for example:
static jmethodID mid; // Global variable
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
....
// Store the JavaVM pointer
jvm = vm;
// Find the class and store the method ID
// Will use the class loader that loaded the JNI library
jclass cls = env->FindClass(className"some/class");
if(!cls) goto ERR;
mid = env->GetMethodID(cls, "methodName",
"(Ljava/lang/String;)V or whatever signature");
if(!mid) goto ERR;
....
}
void myCallback(void) {
JNIEnv* env;
jvm->AttachCurrentThread((void**)&env, NULL);
env->CallVoidMethod(callback, mid, ...);
// Handle error ...
jvm->DetachCurrentThread();
}
}
|
|
|
Re: JNI cannot find Java Routines with RCP [message #444718 is a reply to message #444716] |
Sun, 19 February 2006 15:37 |
Stepan Rutz Messages: 52 Registered: July 2009 |
Member |
|
|
Hi Kenneth,
i don't believe this is related to eclipse at all. It is a jni/jvm only
issue in my opinion.
Now I ask myself why one would use FindClass without the jnienv ? If you
use the jnienv then the classloader associated with the current native
method is used to load the class. This works fine even when running in an
eclipse/osgi context.
Now using the attach/detach thread and the resulting jnienv is of course
context-less (eg there is no native java stub that belongs to some class
and its source class-loader).
Your or other workarounds are possible. One workaround could be to call
java-code from JNI, which I do a lot, since it really ends up in better
maintainable software. I don't see anything fundamentally wrong with
calling java methods from jni code. You could call something like public
Class loadIt(ClassLoader cl, String klassname) or public Class
loadit(Class refKlass, String klassname). Of course in that case you need
a handle to a ref-class to call getClassLoader() on or the correct
classloader, which is sort of the same problem.
For JNI i believe the provided functionality is sufficient, as you always
have the option of calling back to java code or to call an init-method
that caches classes or method/field-ids. One possible point for calling
such an init method would be from the bundle-activation callback in OSGI
or from a static initializer. In my code I use constructs like
if(global_data.need_init) init(); which works fine as well.
So though your point is right that there is jni-code that runs outside of
eclipse just fine but will stop working when run in an eclipse/osgi
context, i want to point out that this is also the case for regular java
code when classloading is not done reasonably. Assuming that the
default-classloader can be used without breaking things is a bit too much
and breaks things inside eclipse/osgi or other frameworks using custom
classloading (probably jboss is another case of this).
I don't want to sound rude or smart, but in my eyes this is not an eclipse
bug or bug in any way.
Regards from cologne,
Stepan
Kenneth Evans wrote:
> I submitted this as a bug:
> https://bugs.eclipse.org/bugs/show_bug.cgi?id=125250. However, changing the
> Eclipse behavior is not feasible. I found a workaround, and this is a
> summary of the problem and solution. Thanks to those of you who helped.
> An added note to what is below: I have not determined if FindClass *always*
> uses the default class loader in situations, such as callbacks, where it is
> necessary to call AttachCurrentThread, or whether it is because my callback
> came back in a native thread that was independent of the Java VM. Class
> loaders are changed on a per thread basis (Thread.setContextCalssLoader) ,
> so the Eclipse class loader would not have been the default (after using
> AttachThread to make it a Java thread) on that thread in any event.
> -Ken
> JNI implementations that work outside of Eclipse, may not work under
> Eclipse.
> In particular, there is a problem using Eclipse with a JNI
> implementation that uses FindClass in a function where the JNIEnv
> pointer is not available, such as in a C callback. The reason is that
> FindClass, in this case, seems to use the classpath to find the class.
> If the desired function is in the classpath, as it would typically be
> in a stand-alone application, there is no problem. However, under
> Eclipse, it is not in the classpath, because Eclipse, by itself or as
> an RCP application, overwrites the classpath with startup.jar, which
> only includes generic launcher classes. Eclipse then uses its own
> class loader to find classes.
> The Eclipse class loader does appear to be used by FindClass in JNI
> functions which are passed the JNIEnv pointer, but not when you have
> to use AttachCurrentThread to get the JNIEnv pointer.
> Example:
> static JavaVM* jvm; // Global variable
> void myCallback(void) {
> JNIEnv* env;
> jvm->AttachCurrentThread((void**)&env, NULL);
> // Fails if "some/class" is not in the classpath:
> jclass cls = env->FindClass("some/class");
> jmethodID methodID = env->GetMethodID(cls, "methodName",
> "(Ljava/lang/String;)V or whatever signature");
> env->CallVoidMethod(callback, methodID, ...);
> jvm->DetachCurrentThread();
> }
> }
> A solution is to cache the method ID, for example:
> static jmethodID mid; // Global variable
> JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
> ....
> // Store the JavaVM pointer
> jvm = vm;
> // Find the class and store the method ID
> // Will use the class loader that loaded the JNI library
> jclass cls = env->FindClass(className"some/class");
> if(!cls) goto ERR;
> mid = env->GetMethodID(cls, "methodName",
> "(Ljava/lang/String;)V or whatever signature");
> if(!mid) goto ERR;
> ....
> }
> void myCallback(void) {
> JNIEnv* env;
> jvm->AttachCurrentThread((void**)&env, NULL);
> env->CallVoidMethod(callback, mid, ...);
> // Handle error ...
> jvm->DetachCurrentThread();
> }
> }Hi Ken
|
|
| | | |
Goto Forum:
Current Time: Sat Jan 18 06:10:59 GMT 2025
Powered by FUDForum. Page generated in 0.07126 seconds
|