JNI编程注意事项

来源:互联网 发布:章鱼直播软件下载 编辑:程序博客网 时间:2024/05/16 06:08

整理项目文档时, 忽然发现当年的一篇以前公司里关于JNI编程 的标准化文档。做为收藏,就贴在这里吧。

注:关于JNI, 现在好像有一个OpenSource项目jace可以帮助进行JNI的开发。另外, 推荐IBM Developerworks网站上的一个教程《用jni进行java编程》

JNI编程注意事项:

1、  JNI的函数声明:

JNI函数声明方法为JAVA_PackageName_ClassName_FuncName,其中特别要注意的是如果native方法所在的java类位于某个package中时,必须在函数声明中加上packageName,否则在运行java程序时将出现:java.lang.UnsatisfiedLinkError错误。同时调用JNI方法:FindClass时需要使用PackageName/ClassName方式,否则会找不到相应Class

2、  编译后DLL存放的位置:

Windows平台下Dll必须可以存放在系统环境变量Path指示的目录中,或者当前目录中

3、  运行javah时的注意事项:

在调用javah时如果目标类处在一个包中,则必须加上包名,比如javah –jin package.ClassName,这样才能产生可以被vm正确识别的头文件

4、  签名

在调用jni函数GetMethodID或者GetFieldID时需要获得签名,签名可以通过javap –s classname来获得

5、  使用jni接口重新封装各服务器客户端api(组装代理api)时的注意事项:

a)         java中封装类实现时的注意事项:原apijava中必须用一个类进行封装,我们建议将所有这些apijava中定义为static方式,并创建一个private的类的构造函数以阻止引用该类的java代码中实例化该类的企图。通过这种方法我们可以使在一个进程中只有一个api集合在工作,当然我们也可以在该api封装类中提供一种机制创建多个api集合,并在类内部管理这些api集合的初始化和析构。这种方式将减少外部类使用该封装类时的复杂度,也给提高了系统资源的利用率。参考如下代码:

class EncapslateClass{

          private EncapslateClass() {}

          private static boolean bIsInit = false;

          public static long Init() {

if (!bIsInit){

return API_Init();

}

return ok;

                                    }

          public static long Destroy() {

if (bIsInit){

long lRet;

lRet = API_Destroy();

Thread.sleep(1000);                                 //很重要,java vm退出时不会等待API集合中的线程正常退出,我们必须保证在VM退出前自己的API集合全部正常退出,否则在退出时可能会出错。该逻辑也可以放在JNI代理API集合中实现。

}

return ok;

                                    }

          private static native long API_Init();

          private static native long API_Destroy();

         

          public static native long API_Func1();

          public static native long API_Func2();

          static{

                      System.loadLibrary(“xxx”);

}

protected void finalize(){

   Destroy();                     //永远不会执行到

}

                        }

上面的例子展示了我们的一种实现方式,外部类通过调用封装类的Init()Destroy()来初始化和析构,而不是直接调用本地方法API_Init()API_Destroy(),通过在Init()Destroy()中加入更多的逻辑,我们甚至可以初始化和析构多个API集合,但一般情况下以上用法已经够用了。采用该方法时需要注意,我们必须在进程退出前显式调用Destroy()来进行析构,原因是该类无法实例化,因此gc不会正确处理该类的析构,必须由我们自己来进行,一般可以在我们的控制类的finalize()函数中显式调用该Destroy()。同时请参考上例中Destroy()函数中的注释

b)        返回值的处理:在我公司大量api中使用返回值(一般为long)指出该api执行是否成功,如果失败则该返回值表示出错代码,而函数正常执行情况下的输出参数采用传引用(或者指针)方式返回。比如 long FuncA(long inparam1, long inparam2, long& outparam1)函数中函数返回值指出FuncA是否执行成功,如果执行成功则通过outparam1返回结果。这种做法在C++编程时是很常见的,但在jni中希望用同样方式与JVM进行交互是行不通。主要原因是函数参数中如果是简单类型(int,short,long,char,byte等非对象类型)jni不支持传引用方式,换句话说原来我们的api通过传引用方式返回简单类型的函数值在jni中是行不通的(虽然java函数参数都是采用传引用方式)。下面有一个例子:

---C++ APIlong FuncA(long inparam1, long inparam2, long& outparam1)

 

---Java中的封装:

   public class SampleNativeCall{

               public static long FuncA(long inparam1, long inparam2, long outparam1);

               static{

                           System.loadLibrary(“abc”);

               }

   }

 

---运行javah –jni SampleNativeCall后的结果

jlong JAVA_SampleNativeCall_FuncA(JNIEnv *jEnv, jobject thisObject, jlong inparam1, jlong inparam2, jlong outparam1)

{

  

   if  ((lRet = FuncA(inparam1 , inparam2, outparam1)) != OK_VALUE){

               return lRet

}

return lRet;

}

 

上例中希望javaFuncAC++FuncA保持一致的接口简化代理api实现的过程,但是由于jlong指示简单的传值过程,我们无法通过outparam1将结果返回给jvm

                        可以有两种方式解决以上问题:

1、  将返回的简单类型封装成类,并在类中提供Set方式来设置该简单变量(或是提供pulic成员变量直接赋值),jni代理api函数还是通过返回值方式返回错误,如下:

---C++ APIlong FuncA(long inparam1, long inparam2, long& outparam1)

 

---Java中的封装:

   public class SampleNativeCall{

               public static class FuncAOutput{

                           private long outparam;

                           public long Get() { return outparam; }

                           public void Set(long inOutparam) {outparam = inOutparam; }

               }

public static long FuncA(long inparam1, long inparam2, FuncAOutput outparam1);

               static{

                           System.loadLibrary(“abc”);

               }

   }

 

---运行javah –jni SampleNativeCall后的结果

jlong JAVA_SampleNativeCall_FuncA(JNIEnv *jEnv, jobject thisObject, jlong inparam1, jlong inparam2, jobject outparam1)

{

  

   if  ((lRet = FuncA(inparam1 , inparam2, out1)) != OK_VALUE){

               return lRet

}

if (outparam1 == NULL) {…}

   jclass cls = env->GetObjectClass(outparam1);

   if (cls ==NULL)

   { … }

   jmethodID mid = env->GetMethodID(cls, “Set”, “J”);

   if (mid == NULL)

   {…}

   env->CallVoidMethod(outparam1, mid, out1);

 

  

}

---java中调用方式

public class SampleClass{

   static public main(String[] argvs){

   {

              

SampleNativeCall.FuncAOutput output = new SampleNativeCall.FuncAOutput(); //很重要,必须先创建对象

SampleNativeCall.FuncA(inparam1, inparam2, output);

System.out.println(“output = ” + output.Get());

}

}

以上方式中在返回值的处理上,以及返回值的取值可以与原api保持一致,接口参数个数也保持一致,但返回参数如果是简单类型则需封装成类(在例子中采用内部类方式,正式实现时也建议采用这种方式,简化代码)。如果原api中返回值通过一个结构返回,则可以用类似的方式用一个类封装该结构,并通过调用jni方式设置该类中成员变量的值。

 

2、  将结果封装成类通过函数返回值返回,函数的出错处理采用“异常”处理机制,即在jni中将错误throw出来而不是通过函数返回值返回,如下例

---C++ APIlong FuncA(long inparam1, long inparam2, long& outparam1)

 

---Java中的封装:

   public class SampleNativeCall{

               public class FuncAOutput{

                           private long outparam;

                           public long Get() { return outparam; }

                           public void Set(long inOutparam) {outparam = inOutparam; }

               }

public FuncAOutput FuncA(long inparam1, long inparam2) throws MyException;

               static{

                           System.loadLibrary(“abc”);

               }

   }

 

---运行javah –jni SampleNativeCall后的结果

jobject JAVA_SampleNativeCall_FuncA(JNIEnv *jEnv, jobject thisObject, jlong inparam1, jlong inparam2)

{

  

   if  ((lRet = FuncA(inparam1 , inparam2, out1)) != OK_VALUE){

               return lRet

}

jclass cls = env->FindClass(“SampleNativeCall.FuncAOutput”);

if (cls == NULL)

{

    

throw;

}

jobject obj = env->AllocObject(cls);

if (obj == NULL)

{

    

throw;

}

jmethodID mid = env->GetMethodID(cls, “Set”, “J”);

   if (mid == NULL)

{

    

throw;

}

   env->CallVoidMethod(outparam1, mid, out1);

  

}

---java中调用方式

public class SampleClass{

   static public main(String[] argvs){

   {

              

//很重要,必须先创建对象

try{

SampleNativeCall.FuncAOutput output = SampleNativeCall.FuncA(inparam1, inparam2);

}

                                                            catch ( xxx ){

                                                                       

                                                                        return;

                                                            }

System.out.println(“output = ” + output.Get());

}

}

我们推荐采用第二种方式,此种方式在输入输出参数的安排上更为清晰合理,而且采用的异常处理机制更符合java的思路。在此种方式里jni通过AllocObject分配的对象同样接受JVMgc的管理,在没有引用时将被正确释放(已经经过验正)

c)        api中大量预定义宏和常量处理方式:通过一个类来封装这些东西,这些宏和常量全部设置为static final方式。