使用JNI包装现有应用程序

时间:2009-11-03 23:37:33

标签: java objective-c java-native-interface

详细介绍如何开始使用JNI的大多数文档描述了如何使用X-Code构建新的JNI应用程序。任何人都可以链接到我在如何使用JNI与现有应用程序中的Objective-C接口的描述。

3 个答案:

答案 0 :(得分:1)

您仍然需要编写某种类型的JNI库来包装您对现有代码(即共享对象,DLL,服务程序等)的访问。这是因为JNI需要对调用的本机函数采用相当迟钝(但合理)的命名约定,因为您需要将数据移入和移出Java内存空间,因为您需要在Java和本机函数之间使用概念性的“桥接”代码

例如,我编写了一个JNI库来提供对iSeries上现有C函数的访问。从数据区读取的一个这样的功能如下:

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);         }
            }
        }
    }

请注意由IBM提供的底层现有API QWCRDTAA的单行调用;其余的是以Java为中心的包装,这是进行调用和处理结果所必需的。

另外,要非常小心,您调用的是线程安全的,或者保护代码不受Java层中全局的并发调用的影响,或者使用O / S层中的互斥锁保护代码。

PS:请注意,非线程安全的本机代码全局非线程安全;您必须阻止与所有其他非线程安全的本机代码并发调用,而不仅仅是您正在调用的一个方法。这是因为它可能是不安全的,因为对其他不安全方法调用的其他函数的底层调用(如strerror(),(如果我的C内存很好))。

答案 1 :(得分:1)

注意:我已经从头开始重写了这个答案,现在我知道它确实有效; - )。

使用Rococoa代替JNI。

以下是我能够掀起的简短示例,其中显示了图片获取者对话框(基于您对Stephen C的回答的评论)。

/***
 * 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();
  }
}

如果你迷路了,试试Rococoa mailing lists。开发人员非常乐于助人。

答案 2 :(得分:0)

假设可以通过命令行运行Object-C应用程序,那么使用java.lang.Runtime.exec(...)方法之一启动它就会更简单(也更少问题)。

JNI充满了复杂性和稳定性问题,如果可以的话,最好避免使用它。

编辑:OP已经解释说这是一个“小部件”而不是命令行应用程序。这使得避免使用JNI变得更加困难。但我仍然认为你应该尝试。例如,您可以考虑将Objective-C小部件包装在Objective-C应用程序中,该应用程序在新窗口中运行小部件。