Most of the documentation that details how to get started with JNI described how to build a new JNI application using X-Code. Can anyone link me to a description of how to use JNI to interface with Objective-C in an existing application.
You will still need to write a JNI library of some sort to wrap your access to the existing code (aka, shared object, DLL, service program, etc). This is because JNI requires a rather obtuse (but sensible) naming convention for the native functions invoked, because you need to move data in and out of Java memory space and because you need to have conceptual "bridging" code between Java and your native function.
For example, I wrote a JNI library to provide access to existing C functions on the iSeries. One such function to read from a data area looks as follows:
JNIEXPORT void JNICALL Java_com_xxx_jni400_DataArea_jniGetDataArea(JNIEnv *jep, jobject thsObj, jbyteArray qulnam, jint str, jint len, jbyteArray rtndta, jint rtnlen) {
jbyte *qn,*rd;
Qwc_Rdtaa_Data_Returned_t *drt;
QFBK2_T fbk;
byte nam[11],lib[11];
byte *ptr;
// SETUP
thsObj=thsObj;
qn=(*jep)->GetByteArrayElements(jep,qulnam,0);
rd=(*jep)->GetByteArrayElements(jep,rtndta,0);
fbk.pro=sizeof(fbk); fbk.avl=0;
// INVOKE
QWCRDTAA(rd,rtnlen,(byte*)qn,str,len,&fbk);
// HANDLE SUCCESSFUL INVOCATION
if(fbk.avl==0) {
drt=(Qwc_Rdtaa_Data_Returned_t*)rd;
if(drt->Length_Value_Returned>0) { /* pad with spaces until the length requested */
ptr=(byte*)(rd+sizeof(*drt)+drt->Length_Value_Returned);
for(; drt->Length_Value_Returned<len; drt->Length_Value_Returned++,ptr++) { *ptr=' '; }
}
}
// RELEASE JAVA MEMORY LOCKS
(*jep)->ReleaseByteArrayElements(jep,qulnam,qn,JNI_ABORT); /* discard array changes */
(*jep)->ReleaseByteArrayElements(jep,rtndta,rd,0 ); /* copy back changes */
// TRANSFORM NATIVE ERROR INTO AN EXCEPTION AND THROW
if(fbk.avl!=0) {
byte eid[8],dta[201];
word dtalen;
f2s(nam,sizeof(nam),(byte*)qn ,10);
f2s(lib,sizeof(lib),(byte*)(qn+10),10);
dtalen=(word)mMin( sizeof(fbk.dta),(fbk.avl-(sizeof(fbk)-sizeof(fbk.dta))) );
f2s(eid,sizeof(eid),fbk.eid,sizeof(fbk.eid));
f2s(dta,sizeof(dta),fbk.dta,dtalen);
if(mStrEquI(eid,"CPF1015") || mStrEquI(eid,"CPF1021")) {
throwEscape(jep,90301,"Could not find data area %s in library %s",nam,lib);
}
else if(mStrEquI(eid,"CPF1016") || mStrEquI(eid,"CPF1022")) {
throwEscape(jep,90301,"Not authorized to data area %s in library %s",nam,lib);
}
else if(mStrEquI(eid,"CPF1063") || mStrEquI(eid,"CPF1067")) {
throwEscape(jep,90301,"Cannot allocate data area %s in library %s",nam,lib);
}
else if(mStrEquI(eid,"CPF1088") || mStrEquI(eid,"CPF1089")) {
throwEscape(jep,90301,"Substring %i,%i for data area %s in library %s are not valid",str,len,nam,lib);
}
else {
if(strlen(dta)>0) { throwEscape(jep,90001,"System API QWCRDTAA returned error message ID %s (%s)",eid,dta);}
else { throwEscape(jep,90001,"System API QWCRDTAA returned error message ID %s",eid); }
}
}
}
Note the one-line invocation for underlying existing API, QWCRDTAA, which is provided by IBM; the rest is Java-centric wrapping which is necessary to make the call and deal with the results.
Also, be very careful that what you invoke is thread-safe, or that you protect the code from concurrent invocations globally in the Java layer, or that you protect the code with a mutex in the O/S layer.
PS: Note that non-threadsafe native code is globally non-threadsafe; you must prevent concurrent invocation with all other non-threadsafe native code, not just the one method you are invoking. This is because it might be unsafe due to an underlying call to some other function which other unsafe methods call (like strerror(), (if my C memory serves well)).
Assuming that the Object-C application can be run via the command line, a simpler (and less problematic) approach would be to launch it using one of the java.lang.Runtime.exec(...)
methods.
JNI is fraught with complexity and stability issues, and it is best to avoid it if you can.
EDIT: The OP has explained that this is a "widget" not a command line application. That makes it harder to avoid using JNI. But I still think that you ought to try. For example, you could consider wrapping the Objective-C widget in an Objective-C application, that runs the widget in a new window.
NOTE: I have completely re-written this answer from scratch, now that I know for sure it works ;-).
Use Rococoa instead of JNI.
Here is a brief sample I was able to whip up that displays the picture taker dialog (based on your comment to Stephen C's answer).
/***
* INCOMPLETE: Doesn't have imports or anything like that.
***/
public interface Quartz extends Library
{
public static Quartz instance = (Quartz)Native.loadLibrary("Quartz", Quartz.class);
}
public interface IKPictureTaker extends NSObject
{
public static final _Class CLASS = Rococoa.createClass("IKPictureTaker", _Class.class);
public interface _Class extends NSClass
{
/**
* Returns a shared {@code IKPictureTaker} instance, creating it if necessary.
* @return an {@code IKPictureTaker} object.
*/
IKPictureTaker pictureTaker();
}
NSInteger runModal();
}
public class IKPictureTakerTest extends JFrame
{
public static void main(String[] args) throws Exception
{
// You need a GUI before this will work.
new IKPictureTakerTest().setVisible(true);
NSAutoreleasePool pool = NSAutoreleasePool.new_();
// Initialize the Quartz framework.
Quartz.instance.toString();
// Display the dialog.
IKPictureTaker pictureTaker = IKPictureTaker.CLASS.pictureTaker();
NSInteger result = pictureTaker.runModal();
if (result.intValue() == 0) // NSCancelButton
{
System.out.println("User cancelled.");
}
else
{
assert result.intValue() == 1; // NSOKButton
System.out.println("User chose an image.");
}
System.out.println(pictureTaker.inputImage()); // null if the user cancelled
pool.release();
}
}
If you get lost, try the Rococoa mailing lists. The developers are very helpful.