零、题外话

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包和动态库文件。

更多推荐

Java与C++混合编程之Java调用C++