views:

503

answers:

3

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.

+1  A: 

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)).

Software Monkey
A: 

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.

Stephen C
I am not trying to run an object-c applicaiton I am trying to incorporate this widget into my application: http://developer.apple.com/Mac/library/documentation/GraphicsImaging/Reference/IKImagePicker_Class/IKImagePicker_Reference.html
Mike2012
+1  A: 

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.

Matt Solnit
Thank you very much for this advice, I have actually been working with Rococoa because I could not get JNI going and I came up with something very close to your example but every time I run it I get this error:Exception in thread "main" java.lang.UnsatisfiedLinkError: Unable to load library 'rococoa': dlopen(librococoa.dylib, 9): image not foundIt seems that although I put all the rococoa jars and the librococoa.dylib in the location where java.library.path points it still is not happy. I believe I need to set the jna.library.path variable but I cannot find how to do this.
Mike2012
Correct, you need to use `-Djna.library.path=<directory that contains librococoa.dylib>`
Matt Solnit
ha ha I'm retarded. Thank You.
Mike2012
Did you code actually run? When ever I try to run your example or my example I get a java.lang.NullPointerException at the point where: public static final _Class CLASS = Rococoa.createClass("IKPictureTaker", _Class.class);is called. Do I need to add the IKPictureClass to some path?
Mike2012
I was getting the same thing until I added the `Quartz.instance.toString()` line.
Matt Solnit