Bootstrap

在JNI中自C++回调Java

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. 总结

  • 强类型语言要严格遵守类型要求;
  • 跨语言编程时,一定要清楚当前被操作对象的语言环境,以及语言环境本身给我们的制约;
  • 魔鬼藏在细节里,如果不该错的错了,那么一定有魔鬼藏在了我们的视线外;

转载于:https://my.oschina.net/ddl007/blog/908457

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;