java调用c(c++)写dll文件

从网上找了这篇文档,做这个的时候感觉并不是特困难,感觉非常的新奇,毕竟自己做c语言的项目特别少.现在把这篇文章转载过来,加了自己的一些注意项。
JNI使用技巧点滴
本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。本文中的示例使用 Sun Microsystems 公司创建的 Java Development Kit (JDK) 版本 1.4.1。用 C 语言编写的本地代码是用 Microsoft Visual C++ 6.0编译器编译生成。
  简介
  近日,由于项目需要,要在WEB页面实现图像转换功能,而VC在图像转换方面有着得天独厚的优势。我们首先用VC封装出图像转换的DLL,然后用JAVA的本地化方法JNI调用用于图像转换的DLL,最后用JavaBean调用JNI生成的DLL。
  通过近几天在网上找资料和自己的摸索,收获很多,现总结如下,让以后做这方面的人少走弯路。
  一. JAVA部分
  1. 无包的情况:
  实例一:
  public class MyNative
  {
  static
  {
  System.loadLibrary( "MyNative" );
  }
  public native static void HelloWord();
  public native static String cToJava();
  }
   说明:
  1)在JAVA程序中,首先需要在类中声明所调用的库名称System.loadLibrary( String libname );,在库的搜寻路径中定位这个库。定位库的具体操作依赖于操作系统。在windows下,首先从当前目录查找,然后再搜寻”PATH”环境变量列出的目录。如果找不到该库,则会抛出UnsatisfiedLinkError。
2)这里加载的是JNI生成的DLL,而不是其他生成的DLL的名称。 在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。
3) 还需要对将要调用的方法做本地声明,关键字为native。并且只需要声明,而不需要具体实现。 实现放在C中实现,稍后将做说明。
  4)如果加了static,表明是静态方法。如果不加,表明是一般的方法。加与不加,生成的头文件中有一个参数不同。稍后将做说明。
  现在开始编译它:
  用javac MyNative.h编译它,生成对应的class文件。
  用javah MyNative ,就会生成对应的MyNative.h头文件。剩下的是就开始交给VC来完成了(我们用VC来实现对应的C实现部分)。
  2. 有包的情况:
  实例二:
  package com..myNative;
  public class MyNative
  {
  static
  {
  System.loadLibrary( "MyNative" );
  }
  public native static void HelloWord();
  public native static String cToJava();
  }
  其他与上面相同,就是在用javac和javah时有所不同。对于有包的情况一定要注意这一点,开始时我的程序始终运行都不成功,问题就出在这里。
  javac ./com/myNative/MyNative.java
  javah com.myNative.MyNative
  上面一句就不用解释了。对下面的一句解释一下:本类的前面均是包名。这样生成的头文件就是:com.myNative.MyNative.h。 开始时,在这种情况下我用javah MyNative生成的头文件始终是MyNative.h。在网上查资料时,看见别人的头文件名砸那长,我的那短。但不知道为什么,现在大家和我一样知道为什么了吧。:)。有时还需要带上路径。具体查看javah的语法。
二.C实现部分
  刚才用javah MyNative生成的MyNative.h头文件内容如下:
  /* DO NOT EDIT THIS FILE - it is machine generated */
  #include
  /* Header for class MyNative */
  #ifndef _Included_MyNative
  #define _Included_MyNative
  #ifdef __cplusplus
  extern "C" {
  #endif
  /*
  * Class:  MyNative
  * Method:  HelloWord
  * Signature: ()V
  */
  JNIEXPORT void JNICALL Java_MyNative_HelloWord (JNIEnv *, jclass);
  /*
  * Class:  MyNative
  * Method:  cToJava
  * Signature: ()Ljava/lang/String;
  */
  JNIEXPORT jstring JNICALL Java_MyNative_cToJava (JNIEnv *, jclass);
  #ifdef __cplusplus
  }
  #endif
  #endif
接下来,就是如何实现它了。其实,用JNI作出的东西也是DLL,被JAVA所调用。
  在具体实现的时候,我们只关心两个函数原型:
  JNIEXPORT void JNICALL Java_MyNative_HelloWord(JNIEnv *, jclass);和JNIEXPORT jstring JNICALL Java_MyNative_cToJava(JNIEnv *, jclass);
  现在让我们开始激动人心的第一步吧 : ) 。在project里面选择win32 Dynamic-link Library,然后点击下一步,其余的取默认。如果不取默认的,将会有dllmain()函数。取空DLL工程的话,将无这个函数。我在这里取的是空。
  然后选择new->File->C++ Source File,生成一个空*.cpp文件。我们把他取名为MyNative。把JNIEXPORT void JNICALL Java_MyNative_HelloWord(JNIEnv *, jclass);和JNIEXPORT jstring JNICALL Java_MyNative_cToJava(JNIEnv *, jclass);拷贝到CPP文件中去。然后把头文件包含进来。
  生成的MyNative.cpp内容如下:
  #include
  #include "MyNative.h"
  JNIEXPORT void JNICALL Java_MyNative_HelloWord (JNIEnv *env, jclass jobject)
  {
  printf("hello word!\n");
  }
  JNIEXPORT jstring JNICALL Java_MyNative_cToJavaJNIEnv *env, jclass obj)
  {
  jstring jstr;
  char str[]="Hello,word!\n";
  jstr=env->NewStringUTF(str);
  return jstr;
  }
  在编译前一定要注意下列情况。
  注意:一定要把SDK中的include文件夹中(和它下面的win32文件夹下的头文件)的几个头文件拷贝到VC的include文件夹中。或者在VC的tools\options\directories中设置,把头文件给包含进来。
对程序的一点解释:
  1)前文不是说过,加了static和不加只是一个参数的区别吗。就是jclass的不同,不加static这里就是jobject。也就是JNIEXPORT void JNICALL Java_MyNative_HelloWord(JNIEnv *env, jobject obj)。
  2)这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jstring是以JNI为中介使JAVA的String类型与本地的string沟通的一种类型,我们可以视而不见,就当做String使用(具体对应见表一)。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的(参见有包的情况)。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。
  3)NewStringUTF()是JNI函数,从一个包含UTF格式编码字符的char类型数组中创建一个新的jstring对象。
  4) 以上程序片断jstr=env->NewStringUTF(str);是C++中的写法,不必使用env指针。因为JNIEnv函数的C++版本包含有直接插入成员函数,他们负责查找函数指针。而对于C的写法,应改为:jstr=(*env)->NewStringUTF(env,str);因为所有JNI函数的调用都使用env指针,它是任意一个本地方法的第一个参数。env指针是指向一个函数指针表的指针。因此在每个JNI函数访问前加前缀(*env)->,以确保间接引用函数指针。
  在C和Java编程语言之间传送值时,需要理解这些值类型在这两种语言间的对应关系。这些都在头文件jni.h中,用typedef语句声明了这些类在目标平台上的代价类。头文件也定义了常量如:JNI_FALSE=0 和JNI_TRUE=1;
表一说明了Java类型和C类型之间的对应关系。
  表一  Java类型和C类型
Java 编程语言
C 编程语言
字节
boolean
jboolean
1
byte
jbyte
1
char
jchar
2
short
jshort
2
int
jint
4
long
jlong
8
float
jfloat
4
double
jdouble
8
现在开始对所写的程序进行编译。选择build->rebuild all对所写的程序进行编译。点击build->build MyNative.DLL生成DLL文件。
  也可以用命令行cl来编译。具体参看其他书籍。
  再次强调(曾经为这个东西大伤脑筋):DLL放置地方
  1) 当前目录。
  2) 放在path所指的路径中
  3) 自己在path环境变量中设置一个路径,要注意所指引的路径应该到.dll文件的上一级,如果指到.dll,则会报错。
下面就开始测试我们的所写的DLL吧(假设DLL已放置正确)。
  public class mytest
  {
  public static void main(String[] args)
  {
  MyNative a=new MyNative();
  a.HelloWord();
  System.out.println(a.cToJava());
  }
  }
  注意也要把MyNative.class放在与mytest.java同一个路径下。现在开始编译运行mytest,是不是在DOS窗口上输出:
  Hello word!
  Hello,world!
  以上是我们通过JNI方法调用的一个简单C程序。但在实际情况中要比这复杂的多。特别是在通过JNI调用其他DLL时,还有很多的地方需要注意。
  现在开始来讨论包含包的情况,步骤与上面的相同,只是有一点点不同。我们来看其中的一个函数。
  JNIEXPORT void JNICALL Java_com_MyNative_MyNative_HelloWord (JNIEnv *env, jclass jobject)
  {
  printf("hello word!\n");
  }
  我们来观察函数名称。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。现在这句话应该理解了吧。
  我们也写一个程序来测试包含包的情况。程序略。
  javac ./com/MyNative/mytest.java
  java mytest
  是不是在DOS窗口上也显示同样的内容:)。 
从   Java   程序调用   C   或   C   ++   代码的过程由六个步骤组成。我们将在下面几页中深入讨论每个步骤,但还是先让我们迅速地浏览一下它们。   
    
    
  编写   Java   代码。我们将从编写   Java   类开始,这些类执行三个任务:声明将要调用的本机方法;装入包含本机代码的共享库;然后调用该本机方法。   
    
    
  编译   Java   代码。在使用   Java   类之前,必须成功地将它们编译成字节码。   
    
    
  创建   C/C++   头文件。C/C++   头文件将声明想要调用的本机函数说明。然后,这个头文件与   C/C++   函数实现(请参阅步骤   4)一起来创建共享库(请参阅步骤   5)。   
    
    
  编写   C/C++   代码。这一步实现   C   或   C++   源代码文件中的函数。C/C++   源文件必须包含步骤   3   中创建的头文件。   
    
    
  创建共享库文件。从步骤   4   中创建的   C   源代码文件来创建共享库文件。   
    
    
  运行   Java   程序。运行该代码,并查看它是否有用。我们还将讨论一些用于解决常见错误的技巧。     
    
    
      
  步骤   1:编写   Java   代码   第   3   页(共16   页)     
    
    
    
    
  我们从编写   Java   源代码文件开始,它将声明本机方法(或方法),装入包含本机代码的共享库,然后实际调用本机方法。   
    
  这里是名为   Sample1.java   的   Java   源代码文件的示例:   
    
    
    1.   public   class   Sample1   
    2.   {   
    3.       public   native   int   intMethod(int   n);   
    4.       public   native   boolean   booleanMethod(boolean   bool);   
    5.       public   native   String   stringMethod(String   text);   
    6.       public   native   int   intArrayMethod(int[]   intArray);   
    7.   
    8.       public   static   void   main(String[]   args)   
    9.       {   
  10.           System.loadLibrary("Sample1");   
  11.           Sample1   sample   =   new   Sample1();   
  12.           int           square   =   sample.intMethod(5);   
  13.           boolean   bool       =   sample.booleanMethod(true);   
  14.           String     text       =   sample.stringMethod("JAVA");   
  15.           int           sum         =   sample.intArrayMethod(   
  16.                                                   new   int[]{1,1,2,3,5,8,13}   );   
  17.   
  18.           System.out.println("intMethod:   "   +   square);   
  19.           System.out.println("booleanMethod:   "   +   bool);   
  20.           System.out.println("stringMethod:   "   +   text);   
  21.           System.out.println("intArrayMethod:   "   +   sum);   
  22.       }   
  23.   }   
    
    
    
    第二步接下来,我们需要将   Java   代码编译成字节码。完成这一步的方法之一是使用随   SDK   一起提供的   Java   编译器   javac。用来将   Java   代码编译成字节码的命令是:   
    
            
  javac   Sample1.java   
    
  第三步是创建   C/C++   头文件,它定义本机函数说明。完成这一步的方法之一是使用   javah.exe,它是随   SDK   一起提供的本机方法   C   存根生成器工具。这个工具被设计成用来创建头文件,该头文件为在   Java   源代码文件中所找到的每个   native   方法定义   C   风格的函数。这里使用的命令是:   
    
      
  javah   Sample1     
  第四步当谈到编写   C/C++   函数实现时,有一点需要牢记:说明必须和   Sample1.h   的函数声明完全一样。我们将研究用于   C   实现和   C++   实现的完整代码,然后讨论两者之间的差异   
  以下是   Sample1.c,它是用   C   编写的实现:   
    
    
    1.   #include   "Sample1.h"   
    2.   #include   <string.h>   
    3.     
    4.   JNIEXPORT   jint   JNICALL   Java_Sample1_intMethod   
    5.       (JNIEnv   *env,   jobject   obj,   jint   num)   {   
    6.         return   num   *   num;   
    7.   }   
    8.     
    9.   JNIEXPORT   jboolean   JNICALL   Java_Sample1_booleanMethod   
  10.       (JNIEnv   *env,   jobject   obj,   jboolean   boolean)   {   
  11.       return   !boolean;   
  12.   }   
  13.   
  14.   JNIEXPORT   jstring   JNICALL   Java_Sample1_stringMethod   
  15.       (JNIEnv   *env,   jobject   obj,   jstring   string)   {   
  16.           const   char   *str   =   (*env)->GetStringUTFChars(env,   string,   0);   
  17.           char   cap[128];   
  18.           strcpy(cap,   str);   
  19.           (*env)->ReleaseStringUTFChars(env,   string,   str);   
  20.           return   (*env)->NewStringUTF(env,   strupr(cap));   
  21.   }   
  22.     
  23.   JNIEXPORT   jint   JNICALL   Java_Sample1_intArrayMethod   
  24.       (JNIEnv   *env,   jobject   obj,   jintArray   array)   {   
  25.           int   i,   sum   =   0;   
  26.           jsize   len   =   (*env)->GetArrayLength(env,   array);   
  27.           jint   *body   =   (*env)->GetIntArrayElements(env,   array,   0);   
  28.           for   (i=0;   i<len;   i++)   
  29.           { sum   +=   body[i];   
  30.           }   
  31.           (*env)->ReleaseIntArrayElements(env,   array,   body,   0);   
  32.           return   sum;   
  33.   }   
  34.     
  35.   void   main(){}   
    
    
    
      
  步骤   5:创建共享库文件   第   13   页(共16   页)     
    
    
    
    
  接下来,我们创建包含本机代码的共享库文件。大多数   C   和   C++   编译器除了可以创建机器代码可执行文件以外,也可以创建共享库文件。用来创建共享库文件的命令取决于您使用的编译器。下面是在   Windows   和   Solaris   系统上执行的命令。   
    
  Windows:   cl   -Ic:\jdk\include   -Ic:\jdk\include\win32   -LD   Sample1.c   -FeSample1.dll     
  Solaris:   cc   -G   -I/usr/local/jdk/include   -I/user/local/jdk/include/solaris   Sample1.c   -o   Sample1.so     
    
      
    
    
  最后一步是运行   Java   程序,并确保代码正确工作。因为必须在   Java   虚拟机中执行所有   Java   代码,所以需要使用   Java   运行时环境。完成这一步的方法之一是使用   java,它是随   SDK   一起提供的   Java   解释器。所使用的命令是:   
    
            
  java   Sample1   
    
    
  当运行   Sample1.class   程序时,应该获得下列结果:   
    
    
  PROMPT>java   Sample1   
  intMethod:   25   
  booleanMethod:   false   
  stringMethod:   JAVA   
  intArrayMethod:   33   
    
  PROMPT>   
    
  来自ibm的jni教程
关键字:C++创建Java虚拟机 C函数调用java方法 java调用C\C++函数 JNI机制 参数传递 

毕业设计到了最后阶段,没想到遇到了技术上的难题。java里调用C函数的方法比较简单,就是JNI机制。但是要在C\C++里面调用java类,觉得这里有一些有关java底层实现即java虚拟机的有趣问题,比较难。现在就把java与C++的相互调用的方法,实现过程详细写下来。 

首先,java调用C函数这个过程,《JNI机制》已有讲述,这里不再详述。现在说说参数传递的方法。一个类型的参数在JNI过程中要经过两次映射。比如在java程序里是int类型,JNI里对应就是jint类型,到了C++程序里相应转为本地类型int,如下表。 

Java类型 本地类型 描述 
boolean jboolean C/C++8位整型 
byte jbyte C/C++带符号的8位整型 
char jchar C/C++无符号的16位整型 
short jshort C/C++带符号的16位整型 
int jint C/C++带符号的32位整型 
long jlong C/C++带符号的64位整型e 
float jfloat C/C++32位浮点型 
double jdouble C/C++64位浮点型 
> String jstring 字符串对象 
Object[] jobjectArray 任何对象的数组 
boolean[] jbooleanArray 布尔型数组 
byte[] jbyteArray 比特型数组 
char[] jcharArray 字符型数组 
short[] jshortArray 短整型数组 
int[] jintArray 整型数组 
long[] jlongArray 长整型数组 
float[] jfloatArray 浮点型数组 
double[] jdoubleArray 双浮点型数组 
表一 JNI类型映射 


此外,我们还可以把数组作为参数进行传递。如函数: 
JNIEXPORT void JNICALL Java_pakage_class(JNIEnv *env, jobject, jintArray i){ 
//class在java中定义为native void class(int[] i); 
jint* pi = (env)->GetIntArrayElements(i, 0 );//相当于获取数组首地址 
jsize len = (env)->GetArrayLength(i);//获取数组长度 
… 
(env)->ReleaseIntArrayElements(i, pi, 0 );//用完后释放指针 


函数 Java数组类型 本地类型 
GetBooleanArrayElements jbooleanArray jboolean 
GetByteArrayElements jbyteArray jbyte 
GetCharArrayElements jcharArray jchar 
GetShortArrayElements jshortArray jshort 
GetIntArrayElements jintArray jint 
GetLongArrayElements jlongArray jlong 
GetFloatArrayElements jfloatArray jfloat 
GetDoubleArrayElements jdoubleArray jdouble 
表二 JNI数组存取函数 


其次,我们来看看在C++中如何调用访问Java的属性和方法。Java程序必须运行在java虚拟机上。Java程序启动时会加载java虚拟机。在jdk1.4\jre\bin目录下,大家可以看到client和server两个文件夹,里面都是jvm.dll。它们都是加载虚拟机的库函数。两者有区别,但只是性能上的区别。如果我们自己加载的话,任何一个都可以。 

C++中加载虚拟机: 
添加设置以下两个include路径, JDK\INCLUDE; JDK\INCLUDE\WIN32 

#define JNI_VERSION_1_4 0x00010004 
#pragma comment (lib,"jvm.lib") 
#i nclude 
#i nclude 
typedef jint (JNICALL *JNICREATEPROC)(JavaVM **, void **, void *); 
main(){ 
JavaVM *jvm; 
JNIEnv *env; 
JavaVMInitArgs vm_args; 
JavaVMOption options[3]; 
vm_args.version=JNI_VERSION_1_4; 
/*设置初始化参数*/ 
options[0].optionString = "-Djava.compiler=NONE"; 
/*类路径,相对或绝对,可设多个,分号隔开*/ 
options[1].optionString = "-Djava.class.path=C:\\dijk\\JNI_Workplace\\MyCap\\MyCap\\Debug;.;..;"; 
options[2].optionString = " "; 
vm_args.nOptions =3; 
vm_args.options = options; 
vm_args.ignoreUnrecognized = JNI_TRUE; 
//------------------------------加载jvm: 
HINSTANCE jvmDll = LoadLibrary(“jdk1.4\\jre\\bin\\server\\jvm.dll”); 
if (jvmDll == NULL) printf("加载JVM动态库错误。%l", ::GetLastError()); 
//查找JNI_CreateJavaVM过程。 
JNICREATEPROC jvmCreateProc = (JNICREATEPROC)GetProcAddress(jvmDll, "JNI_CreateJavaVM"); 
if (jvmCreateProc == NULL) 

FreeLibrary(jvmDll); 
printf("查找JNI_CreateJavaVM过程错误。%l", ::GetLastError()); 

//创建JVM: 

int res = (jvmCreateProc)(&jvm, (void **)&env, &vm_args); 
if (res < 0 || jvm == NULL || env == NULL) 

FreeLibrary(jvmDll); 
printf( "创建JVM发生错误。"); 

//--------------------------------------------------------------------------------- 
j>FindClass(“pakage/class”);//加载启动类 
if (env->ExceptionCheck() == JNI_TRUE || jcl == NULL) 

FreeLibrary(jvmDll); 
printf("加载启动类失败。"); 

jmethodID mid = env->GetStaticMethodID(jcl, “methodname” , "([Ljava/lang/String;"V);//此处的参数意义见注解1 
if (env->ExceptionCheck() == JNI_TRUE || mid == NULL) 

FreeLibrary(jvmDll); 
printf("查找启动方法失败。"); 

//-------------------------调用方法: 
env-> CallStaticObjectMethod( jcl, mid); //第3,4…参数就是被调方法的参数 
FreeLibrary(jvmDll); 


注解1:methodname是java方法名,第三个参数可以这样得到:命令行下: 
javap –p –s class> 双引号里头的就可以作为相应的参数。 

注解2:GetStaticMethodID,CallStaticObjectMethod都是对静态成员而言,非静态成员对应GetMethodID,CallObjectMethod 


到此为止,Java与C++的相互调用就实现了。朋友们请想一想,如果java本地方法的C++函数实现中,又调用java的方法,那该怎么办呢?这里面关系好象比较复杂,绕来绕去很容易让人糊涂。先写出源码: 
Java程序: 

import javax.swing.*; 
public > static { 
System.loadLibrary("mydll"); 

public native static int get(); 
public native static void set(int i);//有待c实现的方法 
public static void fuction(int i){ // static 
System.out.println("I am java fuction "+i); 
JFrame j=new JFrame(); 
j.setVisible(true); 

public static void main(String[] args) 

myjava my=new myjava(); 
my.set(10); 
System.out.println(my.get());//调用c实现的方法 
my.fuction(1); 



javac生成myjava.class,javah myjava生成myjava.h。myjava.h内容大致如下: 
JNIEXPORT jint JNICALL Java_myjava_get 
(JNIEnv *, jclass); 
JNIEXPORT void JNICALL Java_myjava_set 
(JNIEnv *, jclass, jint); 

新建动态链接库工程mydll,添加myjava头文件,添加mydll.cpp。mydll.cpp源码如下: 

#i nclude "myjava.h" 
int i=0; 
JNIEXPORT jint JNICALL Java_myjava_get(JNIEnv *, jclass) 

return i; 

JNIEXPORT void JNICALL Java_myjava_set(JNIEnv *env, jclass, jint j) 

i=j; 
//启动类 
const char StartClass[] = "myjava"; 
//启动方法 
const char StartMethod[] = "fuction"; 
j>FindClass(StartClass); 
if (env->ExceptionCheck() == JNI_TRUE || jcl == NULL) { 
printf("加载启动类失败。"); 
return; 

//启动方法 
jmethodID mid = env->GetStaticMethodID(jcl, StartMethod , "(I)V"); 
if (env->ExceptionCheck() == JNI_TRUE || mid == NULL) { 
printf("查找启动方法失败。"); 
return ; 

env-> CallStaticObjectMethod( jcl, mid,9); 
return ; 


运行myjava试试看,什么结果?大家会看到屏幕左上角出现两个JFrame!!神气吧,我们在一个程序组中同时实现了C++与Java的相互调用! 


我花了两天才比较地搞懂里面的机制,画一张图,觉得蛮得意,哈哈哈 
  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值