零、题外话
Java和C++都是我所热爱的语言,但是众所周知,C++学习是一件长期艰苦的事情,C++适用于需要“硬件级别”操作的软件,其语法复杂,内存也需要我们程序员来自动管理等,而Java向程序员隐藏了指针,相对于C++来说更加安全,而且Java具有内置的垃圾回收机制和多线程等机制,而且Java网络编程也是对程序员来说比C++要友好,从而Java也是深受许多程序员的喜爱。
下面一段话,送给还在纠结选C++ or Java 的同学们:
总体来说,C++几乎可以实现任何功能,但除非拥有明显的特殊理由,否则我不会将C++作为首选。Java则是一切都刚刚好——虽然并非最佳,但确实完全足够。
一、目标
- 使用Java声明类方法而将其实现交给C++来做(譬如要求实时性非常强的一些操作就交给C++来做);
- 向别的客户(Java程序)提供jar文件和动态库文件(当然最好提供一组文档接口,告诉你的客户应该如何调用你写的模块),从而实现隐藏你的源代码;此时,jar文件中是一些class的集合,但是仅仅只有声明,其真正的实现是在动态库中。
以下是举例说明:
1、Project1
用Java写好一个类声明(或者说函数),其具体功能是实现两个整数的加减法。
最终这个工程会产生Math.jar和Math.dll以供后面的Project2中的Java程序调用。
2、Project2
前提条件:
- 将Project1中产生的Math.jar和Math.dll拷贝到当前工程中来
Projecte2的目标:
是一个Java程序,用于测试Project1中产生的Math.jar和Math.dll。
二、具体实现
题外话:需要说明的是,在实验过程中我并没有使用任何的集成开发环境,因为集成开发环境往往会让我不知所措,it always make me confused. 所以我完全是使用命令行进行实验。另外,请读者关注引用框中的内容,因为那往往是容易出错的地方。
1、Project1
1.1 创建Project1文件夹,并在其中创建一个Math.java文件
其中,Math.java中的文件内容如下(注意使用native关键字):
package com.cholen.math;
public class Math{
public static native int add(int x,int y);
public static native int sub(int x,int y);
}
1.2 使用javac编译该Java文件
首先进入到Math.java目录中(后续操作都在该目录中进行),然后执行如下编译命令
javac -d . Math.java
- javac的参数 -d <目录> 用于指定放置生成的类文件的位置
- 使用-d选项,编译器会自动给我们生成对应包名的目录结构
- .
效果如下图:
1.3 使用javah命令生成对应的c/c++头文件
- javah 操作的一定是class文件
- 注意类名要写为类的全名
具体命令为:
javah -cp . com.cholen.math.Math
执行完成后,会自动生成一个c/c++头文件,如下图:
请不要修改这个头文件,这个头文件的内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_cholen_math_Math */
#ifndef _Included_com_cholen_math_Math
#define _Included_com_cholen_math_Math
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_cholen_math_Math
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_cholen_math_Math_add
(JNIEnv *, jclass, jint, jint);
/*
* Class: com_cholen_math_Math
* Method: sub
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_cholen_math_Math_sub
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
1.4 在当前文件夹下建立一个com_cholen_math_Math.cpp文件
就是包含以下刚刚生成的头文件,然后把头文件中声明的函数拷贝过来,并添加实现体。
其内容如下:
#include "com_cholen_math_Math.h"
/*
* Class: com_cholen_math_Math
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_cholen_math_Math_add
(JNIEnv *, jclass, jint x, jint y) {
return x + y;
}
/*
* Class: com_cholen_math_Math
* Method: sub
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_cholen_math_Math_sub
(JNIEnv *, jclass, jint x, jint y){
return x -y;
}
int main(int argc, const char* argv[]){
}
- 请务必写上一个空的main函数,否则在后面一步编译时会产生 undefined reference to `WinMain’错误
1.5 使用g++命令进行编译
具体编译命令如下:
g++ -ID:\SDK\Java\jdk1.8.0_231\include -ID:\SDK\Java\jdk1.8.0_231\include\win32 com_cholen_math_Math.cpp -o Math.dll
- g++ -I参数后面接的是一个路径,-I参数告诉g++去那个路径下寻找它所需要的头文件
- g++ -I参数与路径名之间请不要添加任何空格
- g++ -o 参数后面接的是你想让g++生成的文件名
- 请不要照抄上述命令,请务必包含你自己电脑jdk下的那两个路径
经过编译,会在当前目录下生成Math.dll文件,注意,我们此时的文件结构如下图:
1.6 使用jar命令生成jar文件
- jar 命令作用的对象是class文件,请一定要带上.class后缀名,请一定要写类的路径,不要写类的全名
- jar 命令类似于Linux中的tar命令,读者可以自行学习
- jar 命令相当于把一大堆class文件打了一个包
- jar -c 参数 创建新档案
- jar -v 参数 在标准输出中生成详细输出
- jar -f 参数 指定档案文件名
具体的命令为:
- jar -cvf Math.jar ./com/cholen/math/Math.class
- 后面那一部分千万不可写成com.cholen.math.Math.class
经过打包,会在当前目录下生成一个Math.jar包,注意,我们此时的文件结构如下图:
至此,我们的Project1工程算是完成了,剩下的便是向别人提供我们写好的Math.jar和Math.dll文件了。
2、Project2
2.1 创建Project1文件夹,并在其中创建一个Main.java文件,并将Project1中的Math.jar和Math.dll文件拷贝进来
其中,Main.java的内容如下:
package org.ch;
public class Main{
public static void main(String[] args){
int a = 10;
int b = 20;
int c = com.cholen.math.Math.add(a,b);
int d = com.cholen.math.Math.sub(a,b);
System.out.println("c ="+c);
System.out.println("d ="+d);
}
static {
System.loadLibrary("Math");
}
}
- System.loadLibrary()用来加载某一个动态库文件
- 注意System.loadLibrary()的参数为Math,不是Math.dll
2.1 使用javac命令编译Main.java文件
具体编译命令为:
javac -cp .;./Math.jar -d . Main.java
- -cp <路径> 指定查找用户类文件和注释处理程序的位置
-cp 后面接的是一个(或多个)第三方jar包的路径,多个路径之间用英文分号;隔开,它告诉Java编译器除了jdk提供的标准库外,你还应该去这些地方找所需要的类。 (后面一步的java命令相同)
经过编译,此时,我们的文件结构如下图:
2.3 使用java命令运行
具体命令为:
java -cp .;./Math.jar org.ch.Main
- 注意为类的全名,且不带.class后缀名
如果上述步骤都正确的话,你应该会得到正确的结果,如下图:
至此,我们便完成了:
- 一方面,我们提供了由Java声明并由C/C++实现的类方法,从而生成了动态库文件,并将编译好的class文件打包成一个jar文件,将动态库文件和jar文件发给别人以供别人使用,与此同时,最好写一份文档说明你所提供的接口(这个功能由javadoc命令可以完成,读者可以自行学习)。
- 另一方面,我们学习了如何在我们的Java程序中如何使用第三方提供的jar包和动态库文件。