1. 开发环境
- MyEclipse 2016 CI
- Visual Studio 2010
- JDK 1.8_45 x64
2. 编写Java类,将需要在C/C++中实现的部分方法使用native关键字修饰
FCClient.java
package com.mlight.floorcontrol.client;
/**
* @author ddl007
*/
public class FCClient {
/**
* 设置服务器信息
*
* @param ip
* @param port
* 以秒为单位
*/
public native void init(String ip, int port);
/**
* 发送消息
*
* @param jsonMsg
*/
public native void sendMsg(String jsonMsg);
/**
* 响应通知
*
* @param listener
* 消息通知
*/
public native void onMsg(FCListener listener);
static {
System.loadLibrary("JniSdk");
}
public static void main(String[] args) throws InterruptedException {
FCListener listener = new FCListener() {
@Override
public void onMessage(String json) {
System.out.println(json);
}
};
FCClient client = new FCClient();
client.init("192.168.2.190", 6980);
client.sendMsg("{\"msg\":\"jsonMsg\"}");
client.onMsg(listener);
}
}
MessageListener.java
package com.mlight.floorcontrol.client;
/**
* 处理通知信息
*
* @author ddl007
*/
public interface FCListener {
public void onMessage(String json);
}
2. 使用javac编译,当然此处为集成开发环境,可跳过。如果使用javac编译,命令如下
javac -classpath . -encoding utf-8 com\mlight\floorcontrol\client\FCClient.java
3. 使用javah生成jni头文件,com_mlight_floorcontrol_client_FCClient.h
javah com.mlight.floorcontrol.client.FCClient
4.在VS2010下新建dll工程clientSDK,并将com_mlight_floorcontrol_client_FCClient.h拷贝到头文件夹下
5.设置工程的“包含目录”(右键工程>属性>VC++目录>包含目录),增加如下两个路径
C:\Program Files\Java\jdk1.8.0_45\include\win32;C:\Program Files\Java\jdk1.8.0_45\include
6.设置工程的“库目录”(右键工程>属性>VC++目录>库目录),增加如下路径:
C:\Program Files\Java\jdk1.8.0_45\lib
7.在clientSDK.cpp中实现必要的逻辑,样例代码如下:
#include "stdafx.h"
#include "com_mlight_floorcontrol_client_FCClient.h"
#include <jni.h>
#include <iostream>
using namespace std;
char* jstringTostring(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
jstring stoJstring(JNIEnv* env, const char* pat)
{
jclass strClass = env->FindClass("Ljava/lang/String;");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}
/*
* Class: com_mlight_floorcontrol_client_FCClient
* Method: init
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_mlight_floorcontrol_client_FCClient_init
(JNIEnv *env, jobject obj, jstring server, jint port){
cout << "Java_com_mlight_floorcontrol_client_FCClient_init" << endl;
cout << "server:" << jstringTostring(env,server) << endl;
cout << "port:" << port << endl;
}
/*
* Class: com_mlight_floorcontrol_client_FCClient
* Method: sendMsg
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_mlight_floorcontrol_client_FCClient_sendMsg
(JNIEnv *env, jobject, jstring msg){
cout << "Java_com_mlight_floorcontrol_client_FCClient_sendMsg" << endl;
cout << "msg:" << jstringTostring(env,msg) << endl;
}
/*
* Class: com_mlight_floorcontrol_client_FCClient
* Method: onMsg
* Signature: (Lcom/mlight/floorcontrol/client/FCListener;)V
*/
JNIEXPORT void JNICALL Java_com_mlight_floorcontrol_client_FCClient_onMsg
(JNIEnv *env, jobject, jobject listener){
jclass cls = env->GetObjectClass(listener);
jmethodID method = env->GetMethodID(cls,"onMessage","(Ljava/lang/String;)V");
char paramStr[] = "abcdeasdfasdfasf";
env->CallVoidMethod(listener,method,stoJstring(env,paramStr));
}
其中有2个地方是需要注意
- jni生成的头文件中字符串类型为jstring,C++中字符串使用char *表示,两者需要转换后才能使用。转换过程参见jstringTostring,sttoJstring两个函数,其中也说明字节在C++和Java中的表示方式是一致的。
- 在函数Java_com_mlight_floorcontrol_client_FCClient_onMsg
(JNIEnv *, jobject, jobject);中调用onMessage函数,需要用到FCListener中onMessage方法的签名获取,可通过如下命令获得javap -s -p com.mlight.floorcontrol.client.FCListener
运行结果如下
Compiled from "FCListener.java" public interface com.mlight.floorcontrol.client.FCListener { public abstract void onMessage(java.lang.String); Signature: (Ljava/lang/String;)V }
- 在clientSDK.cpp文件中调用FCListener的onMessage函数时,参数直接使用C++中双引号包围的字符串在编译时是没有问题的,但在运行时会出现进行崩溃。曾听说在使用JNI时,从C++中回调Java的方法会导致进程崩溃,本人也一直没有办法解决。但是回到强类型语言的基本要求,严格遵守类型约定。于是虚拟机进程不再崩溃。
8. 总结
- 强类型语言要严格遵守类型要求;
- 跨语言编程时,一定要清楚当前被操作对象的语言环境,以及语言环境本身给我们的制约;
- 魔鬼藏在细节里,如果不该错的错了,那么一定有魔鬼藏在了我们的视线外;