概述
本文章主要介绍如何通过jni实现java调用C语言生成的动态库。 测试调用Virbox Runtime版本的java库 。
JNI
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。
实现步骤
以下实现步骤以RuntimeEasy API为例,描述如何通过java 调用Runtime接口
编写带有native方法的Java类
参考C代码接口,编写带有native方法的Java类,所有方法和C接口进行对应,方便调用实现。
例如C接口如下
C/C++代码段
/*!
* @brief SS反黑引擎初始化,验证开发商合法身份
* @param[in] szdeveloper_password 开发商密码HEX字符串
* @return 成功返回SS_OK,失败返回相应的错误码
* @see slm_init_easy slm_cleanup_easy slm_is_debug_easy
* @remark 为保护APP检测调试器做准备
*/
SS_UINT32 SSAPI slm_init_easy(const char* szdeveloper_password);
转换为Java接口如下
package com.senseyun.openapi.SSRuntimeEasyJava;
public class SSRuntimeEasy {
static{
try{
System.loadLibrary("SSRuntimeEasyJava");
}catch(UnsatisfiedLinkError e){
System.err.println("Native code library failed to load.\n" + e);
System.exit(1);
}
}
/**
* SS反黑引擎初始化,验证开发商合法身份
* @param APIPsd 开发商API密码HEX字符串
* @return 成功返回SS_OK,失败返回相应的错误码
*/
public final static native long SlmInitEasy(String APIPsd);
}
使用javac工具编译Java类
上述Java类编写完成后,调用 javac命令进行编译。
javac Runtime/*.java -d .
javac是java语言编程编译器。全称javacompilation.javac工具读由java语言编写的类和接口的定义,并将它们编译成字节代码的class文件。
-d 参数是指将编译结果放在指定的目录下。
“.”表示目录为java文件中包含的包目录结构层级,本例中将生产的目录结构为:./com/senseyun/openapi/SSRuntimeEasyJava。
使用javah来生成与native方法对应的头文件
javah -cp . -jni com.csenseyun.openapi.SSRuntimeEasyJava.SSRuntimeEasy
javah 生成实现本地方法所需的 C 头文件和源文件。C 程序用生成的头文件和源文件在本地源代码中引用某一对象的实例变量。详细的命令参数可自行查询。
实现C功能
生成的*.h文件可创建一个VS工程,添加对应的.c文件实现所有函数功能。
//头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_senseyun_openapi_SSRuntimeEasyJava_SSRuntimeEasy */
#ifndef _Included_com_senseyun_openapi_SSRuntimeEasyJava_SSRuntimeEasy
#define _Included_com_senseyun_openapi_SSRuntimeEasyJava_SSRuntimeEasy
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_senseyun_openapi_SSRuntimeEasyJava_SSRuntimeEasy
* Method: SlmInitEasy
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL Java_com_senseyun_openapi_SSRuntimeEasyJava_SSRuntimeEasy_SlmInitEasy
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
//c文件
#include "SSRuntimeEasyJava.h"
#include "slm_runtime_easy.h"
#include <memory.h>
#pragma comment(lib, "slm_runtime_easy.lib")
jlong JNICALL Java_com_senseyun_openapi_SSRuntimeEasyJava_SSRuntimeEasy_SlmInitEasy(JNIEnv *jenv, jclass jcls, jstring japi_psd)
{
jlong jresult = 0;
SS_UINT32 result = 0;
char *szapi_psd = 0;
if (japi_psd)
{
szapi_psd = (char *)(*jenv)->GetStringUTFChars(jenv, japi_psd, 0); // 这里是有内存分配的,下面所有调用相同
if (szapi_psd == 0)
return SS_ERROR_MEMORY_FAIELD;
}
result = slm_init_easy(szapi_psd);
jresult = (jlong)result;
if (szapi_psd)
(*jenv)->ReleaseStringUTFChars(jenv, japi_psd, (const char *)szapi_psd); // 所以需要在这里释放,下面所有调用相同
return jresult;
}
其中实现中用于字节数组转换的函数有
GetStringUTFChars
GetCharArrayElements
等,可详细了解其使用方法后使用。
将C代码编译成动态库,在Java工程中包含此动态库,加载之前写好的java类即可试用。至此通过Java调用RuntimeEasy API的功能已经完成。
Javadoc的生成
我们使用eclipse创建Java工程师,每个用到的每个Java包中的方法,都有函数说明,被称之为Javadoc。要生成Javadoc,只需以下三步
第一步,写好java类注释
第二步,使用javadoc命令生成
第三步,在eclipse工程中导入javadoc的目录
扩展内容
Java接口和C接口中的结构体转换
java中只有类,没有结构体,要想传递结构体参数,必须用类来替换,替换方法依然是在java中创建类,通过Object类型参数将结构传递给jni,jni通过jni提供的方法转换为结构体结构
下面以RuntimeEasy接口的slm_login_easy接口为例,解释如何传递结构体参数。
ST_LOGIN_PARAM对应的Java类
package com.senseyun.openapi.SSRuntimeEasyJava;
public class ST_LOGIN_PARAM {
public int size; ///** 结构体大小(必填)*/
public int license_id; ///** 要登录的许可ID*/
public int timeout; ///** 许可会话的超时时间(单位:秒),填0则使用默认值:600秒 */
public int login_mode; ///** 许可登录的模式:本地,远程,云(见LOGIN_MODE_XXX),如果填0,则使用SLM_LOGIN_MODE_AUTO*/
public int login_flag; ///** 许可登录的标志:见SLM_LOGIN_FLAG_XXX */
public byte[] sn = new byte[SSDefines.SLM_LOCK_SN_LENGTH]; ///** 许可登录指定的锁唯一序列号(可选)*/
public char[] server = new char[SSDefines.SLM_MAX_SERVER_NAME]; ///** 网络锁服务器地址(可选),仅识别IP地址 */
public char[] access_token = new char[SSDefines.SLM_MAX_ACCESS_TOKEN_LENGTH]; ///** 云锁用户token(可选)*/
public char[] cloud_server = new char[SSDefines.SLM_MAX_CLOUD_SERVER_LENGTH]; ///** 云锁服务器地址(可选)*/
public byte[] snippet_seed = new byte[SSDefines.SLM_SNIPPET_SEED_LENGTH];///** 碎片代码种子(可选),如果要支持碎片代码,login_flag需要指定为SLM_LOGIN_FLAG_SNIPPET*/
public byte[] user_guid = new byte[SSDefines.SLM_CLOUD_MAX_USER_GUID_SIZE];///** 已登录用户的唯一ID(可选) */
public int getSize()
{
return (4 * 5 + SSDefines.SLM_LOCK_SN_LENGTH + SSDefines.SLM_MAX_SERVER_NAME + SSDefines.SLM_MAX_ACCESS_TOKEN_LENGTH +
SSDefines.SLM_MAX_CLOUD_SERVER_LENGTH + SSDefines.SLM_SNIPPET_SEED_LENGTH + SSDefines.SLM_CLOUD_MAX_USER_GUID_SIZE);
}
}
在jni中对Java传入的object参数进行解析
/* @brief 使用GetFieldID获取FieldID的符号参数对照表
* @remark Java类型 符号
* -boolean "Z"
* -byte "B"
* -char "C"
* -short "S"
* -int "I"
* -long "L"
* -float "F"
* -double "D"
* -void "V"
* -objects对象 Lfully-qualified-class-name; "L类名"
* -Arrays数组 "[array-type ;例如 byte[] 则"[B"
* -methods方法 (argument-types)return-type(参数类型)返回类型
*/
#define JCLASS_ST_LOGIN_PARAM ("com/senseyun/openapi/SSRuntimeEasyJava/ST_LOGIN_PARAM")
#define JFIELD_NAME_SIZE ("size")
#define JFIELD_NAME_LICENSE_ID ("license_id")
#define JFIELD_NAME_TIMEOUT ("timeout")
#define JFIELD_NAME_LOGIN_MODE ("login_mode")
#define JFIELD_NAME_LOGIN_FLAG ("login_flag")
#define JFIELD_NAME_SN ("sn")
#define JFIELD_NAME_SERVER ("server")
#define JFIELD_NAME_ACCESS_TOKEN ("access_token")
#define JFIELD_NAME_CLOUD_SERVER ("cloud_server")
#define JFIELD_NAME_SNIPPET_SEED ("snippet_seed")
#define JFIELD_NAME_USER_GUID ("user_guid")
void jcls_to_struct(IN JNIEnv *jenv, IN jobject jparam, OUT ST_LOGIN_PARAM *login)
{
jfieldID jsize = 0;
jfieldID jlicense_id = 0;
jfieldID jtimeout = 0;
jfieldID jlogin_mode = 0;
jfieldID jlogin_flag = 0;
jfieldID jsn = 0;
jfieldID jserver = 0;
jfieldID jaccess_token = 0;
jfieldID jcloud_server = 0;
jfieldID jsnippet_seed = 0;
jfieldID juser_guid = 0;
jclass jcls = 0;
// 使用FindClass接口获取到Java调用此方法的类名称。
jcls = (*jenv)->FindClass(jenv, JCLASS_ST_LOGIN_PARAM);
if (jcls == 0)
return;
jsize = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_SIZE, "I");
if (jsize)
login->size = (*jenv)->GetIntField(jenv, jparam, jsize);
jlicense_id = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_LICENSE_ID, "I");
if (jlicense_id)
login->license_id = (*jenv)->GetIntField(jenv, jparam, jlicense_id);
jtimeout = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_TIMEOUT, "I");
if (jtimeout)
login->timeout = (*jenv)->GetIntField(jenv, jparam, jtimeout);
jlogin_mode = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_LOGIN_MODE, "I");
if (jlogin_mode)
login->login_mode = (*jenv)->GetIntField(jenv, jparam, jlogin_mode);
jlogin_flag = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_LOGIN_FLAG, "I");
if (jlogin_flag)
login->login_flag = (*jenv)->GetIntField(jenv, jparam, jlogin_flag);
jsn = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_SN, "[B");
if (jsn)
{
jbyteArray jarry = 0;
char *arry = 0;
jarry = (*jenv)->GetObjectField(jenv, jparam, jsn);
arry = (*jenv)->GetByteArrayElements(jenv, jarry, 0);
memcpy(login->sn, (const void*)arry, SLM_LOCK_SN_LENGTH);
(*jenv)->ReleaseByteArrayElements(jenv, jarry, arry, 0);
}
jserver = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_SERVER, "[C");
if (jserver)
{
jcharArray jarry = 0;
char *arry = 0;
jarry = (*jenv)->GetObjectField(jenv, jparam, jserver);
arry = (*jenv)->GetCharArrayElements(jenv, jarry, 0);
memcpy(login->server, (const void*)arry, SLM_MAX_SERVER_NAME);
(*jenv)->ReleaseCharArrayElements(jenv, jarry, arry, 0);
}
jaccess_token = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_ACCESS_TOKEN, "[C");
if (jaccess_token)
{
jcharArray jarry = 0;
char *arry = 0;
jarry = (*jenv)->GetObjectField(jenv, jparam, jaccess_token);
arry = (*jenv)->GetCharArrayElements(jenv, jarry, 0);
memcpy(login->access_token, (const void*)arry, SLM_MAX_ACCESS_TOKEN_LENGTH);
(*jenv)->ReleaseCharArrayElements(jenv, jarry, arry, 0);
}
jcloud_server = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_CLOUD_SERVER, "[C");
if (jcloud_server)
{
jcharArray jarry = 0;
char *arry = 0;
jarry = (*jenv)->GetObjectField(jenv, jparam, jcloud_server);
arry = (*jenv)->GetCharArrayElements(jenv, jarry, 0);
memcpy(login->cloud_server, (const void*)arry, SLM_MAX_CLOUD_SERVER_LENGTH);
(*jenv)->ReleaseCharArrayElements(jenv, jarry, arry, 0);
}
jsnippet_seed = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_SNIPPET_SEED, "[B");
if (jsnippet_seed)
{
jbyteArray jarry = 0;
char *arry = 0;
jarry = (*jenv)->GetObjectField(jenv, jparam, jsnippet_seed);
arry = (*jenv)->GetByteArrayElements(jenv, jarry, 0);
memcpy(login->snippet_seed, (const void*)arry, SLM_SNIPPET_SEED_LENGTH);
(*jenv)->ReleaseByteArrayElements(jenv, jarry, arry, 0);
}
juser_guid = (*jenv)->GetFieldID(jenv, jcls, JFIELD_NAME_USER_GUID, "[B");
if (juser_guid)
{
jbyteArray jarry = 0;
char *arry = 0;
jarry = (*jenv)->GetObjectField(jenv, jparam, juser_guid);
arry = (*jenv)->GetByteArrayElements(jenv, jarry, 0);
memcpy(login->user_guid, (const void*)arry, SLM_CLOUD_MAX_USER_GUID_SIZE);
(*jenv)->ReleaseByteArrayElements(jenv, jarry, arry, 0);
}
}
jlong JNICALL Java_com_senseyun_openapi_SSRuntimeEasyJava_SSRuntimeEasy_SlmLoginEasy(JNIEnv *jenv, jclass jcls, jobject jparam, jlong jtype)
{
jlong jresult = 0;
char *param = 0;
INFO_FORMAT_TYPE fmt_type;
SLM_HANDLE_INDEX result = 0;
ST_LOGIN_PARAM login = { 0 };
fmt_type = (INFO_FORMAT_TYPE)jtype;
if (jparam && fmt_type == JSON)
{
param = (char *)(*jenv)->GetStringUTFChars(jenv, jparam, 0);
}
else if (jparam && fmt_type == STRUCT)
{
jcls_to_struct(jenv, jparam, &login);
param = (char *)&login;
}
result = slm_login_easy(param, fmt_type);
jresult = (jlong)result;
if (param && fmt_type == JSON)
(*jenv)->ReleaseStringUTFChars(jenv, jparam, (const char *)param);
return jresult;
}