Reputation: 2824
I'm really struggling to figure out how I can call a Java function from Objective-C using JNI.
I should start by saying I don't know anything much about Java but am very familiar with Obj-C. I have a single Java class with a single method that I need to call from my app bundle. The jar is inside the Resources folder in the bundle and I have NSJavaRoot
set to Content/Resources
, NSJavaNeeded
is checked and NSJavaPath
contains the names of 2 jar files (main one and one dependency).
I'm firing up the VM using a call to JNI_CreateJavaVM
and then attempting to find the class using NSClassFromString
which feels wrong but is the only method I have found in my search. I believe this method is correct when using the deprecated Java bridge but I cannot find any examples or references that use JNI.
My Java class looks like this:
package foo;
public class bar {
public function dostuff() {
}
}
I need to call dostuff()
once as part of the app flow. Does anyone have any ideas how to do this?
Thanks, J
Upvotes: 2
Views: 3546
Reputation: 29886
Since the JavaBridge has been deprecated, you are going to have to do this all with JNI. On MacOS, you'll want to add the JavaVM.framework to your project, and from your source files, the header you're looking for is:
#import <JavaVM/jni.h>
Next you need to set up the JavaVM. This can be non-trivial, depending on your classpath and other requirements, but you're on the right track as this is the function you use to create the JVM:
JNI_CreateJavaVM(JavaVM **pJVM, void **pJNIEnv, void *args);
Next you'll want to get a reference to your class foo.bar. This you can do using the pJNIEnv that was passed out from JNI_CreateJavaVM. You'll want to call:
jclass myClass = JNIEnv->FindClass(JNIEnv, "foo/bar"); // Note the slashes replacing the dots...
Assuming everything is set up right, you'll get back a reference to your class. Assuming for the moment that foo.bar has a default parameterless constructor, you can get an instance of it, like this:
jobject myFooBar = JNIEnv->NewObject(JNIEnv, myClass);
Now you need to get the methodID of your doStuff method. To do this, you'll need it's method signature, which you can get by calling javap, like this:
% javap -s foo.bar
which should produce output like this:
Compiled from "bar.java"
public class foo.bar extends java.lang.Object{
public foo.bar();
Signature: ()V
public void doStuff();
Signature: ()V
}
Then you can use that to get the methodID, which you'll need to call it. Like so:
jmethodID mid = JNIEnv->GetMethodID(JNIEnv, myClass, "doStuff", "()V");
Assuming all these things have gone right, then you can call the method like this:
JNIEnv->CallVoidMethod(JNIEnv, myFooBar, mid);
and the method should get called. After any of these stages, you probably want to check with the VM to see if there was an exception. You can check to see if an exception occurred by doing this:
if (JNIEnv->ExceptionCheck(JNIEnv))
{
// Handle the exception...
}
If you want the actual throwable, you can get it using the ExceptionOccurred JNI method.
So, there it is at it's most stripped down: how you call a Java method from Cocoa with JNI. You will want to read up on the JNI docs, espeically the parts about understanding the differences between global and local references. (i.e. making your Cocoa-side references last long enough to be called outside of the scope in which they were created.) In fact, there's a whole chapter on common mistakes and pitfalls, many of which you will hit.
http://java.sun.com/docs/books/jni/
Good luck!
Upvotes: 12