1 java
注意事项,如果c中引用的动态库太多,则java无法加载。此时应将C程序改成Socket Udp服务,然后java通过网络与c程序进行交互。
1.1 KcConfig
类名的第一个C表示此类和C共享。
package cn.kuncb;
public class CKcConfig {
int httpTimeout;
boolean isSeed;
String name;
public CKcConfig(int httpTimeout, boolean isSeed, String name) {
this.httpTimeout = httpTimeout;
this.isSeed = isSeed;
this.name = name;
}
public int getHttpTimeout() {
return httpTimeout;
}
public void setHttpTimeout(int httpTimeout) {
this.httpTimeout = httpTimeout;
}
public boolean isSeed() {
return isSeed;
}
public void setSeed(boolean seed) {
isSeed = seed;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1.2 KcUseConfig
类名的第一个C表示此类和C共享,第二个C表示调用C方法。
package cn.kuncb;
public class CCKcConfig {
static {
//名称要和dll或so文件名称一至
System.loadLibrary("kc_config");
}
//region 初始化
public native static int registered();
public native static void unregistered();
//endregion
public native static String getName();
public native static int getHttpTimeout();
public native static boolean isSeed();
public native static CKcConfig get();
public native static void set(CKcConfig config);
}
1.3 java 编译
#先到源码目录D:\MyWork\202109\qinterpolation\src\main\java\cn\kuncb运行
cd D:\MyWork\202109\qinterpolation_java\src\main\java\cn\kuncb
del /Q /F D:\MyWork\202109\qinterpolation_java\src\main\java\cn\kuncb\CCKcConfig.class
javac -encoding UTF-8 CKcConfig.java CCKcConfig.java
1.3 生成头文件
#回退至包目录
cd D:\MyWork\202109\qinterpolation_java\src\main\java
del /Q /F D:\MyWork\202109\qinterpolation_java\src\main\java\cn\kuncb\cn_kuncb_CCKcConfig.h
javah -encoding UTF-8 cn.kuncb.CCKcConfig
- 生成的头文件完整路径“D:\MyWork\202109\qinterpolation_java\src\main\java\cn_kuncb_CCKcConfig.h”。
- 复制cn_kuncb_CCKcConfig.h、%JDK_HOME%\include\jni.h和%JDK_HOME%\include\win32\jni_md.h至C项目目录
copy /Y D:\MyWork\202109\qinterpolation_java\src\main\java\cn_kuncb_CCKcConfig.h D:\MyWork\202109\kc_config
copy /Y %JAVA_HOME%\include\jni.h D:\MyWork\202109\kc_config
copy /Y %JAVA_HOME%\include\win32\jni_md.h D:\MyWork\202109\kc_config
1.4 查看java方法对应的签名
javap -classpath D:\MyWork\202109\qinterpolation_java\src\main\java -s cn.kuncb.CKcConfig
#本例输出
public class cn.kuncb.CKcConfig {
public int test;
descriptor: I
public cn.kuncb.CKcConfig(int, boolean, java.lang.String);
descriptor: (IZLjava/lang/String;)V
public int getHttpTimeout();
descriptor: ()I
public void setHttpTimeout(int);
descriptor: (I)V
public boolean isSeed();
descriptor: ()Z
public void setSeed(boolean);
descriptor: (Z)V
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
public void setName(java.lang.String);
descriptor: (Ljava/lang/String;)V
}
在输出结果中 descriptor:后面的内容即为方法签名。当写C时GetMethodID函数最后一个参数为方法签名。
例如:“getName"为方法名,”()Ljava/lang/String;"为方法签名,此时GetMethodID的参数如下设置:
jmetGetHttpTime = (*env)->GetMethodID(env, exClass, "getName", "()Ljava/lang/String;");
1.5 在idea中添加java.library.path
点击“Edit Configurations->Modify options->Add VM options”中填入以下内容,指明dll或so库所在目录
-Djava.library.path=D:/MyWork/202109/x64/Release
2 C文件
2.1 cn_kuncb_CCKcConfig.h
cn_kuncb_CCKcConfig.h头文件是由javah生成的,复制至项目即可。头文件名及函数定义必须符合java标准,头文件名:包_类.h,函数名:包_类_函数名,其中包名中的.用_代替。
修改头文件第2行
#include <jni.h>
修改为
#include "jni.h"
修改后的源文件“cn_kuncb_KcConfig.h”
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class cn_kuncb_CCKcConfig */
#ifndef _Included_cn_kuncb_CCKcConfig
#define _Included_cn_kuncb_CCKcConfig
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: cn_kuncb_CCKcConfig
* Method: registered
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_cn_kuncb_CCKcConfig_registered
(JNIEnv *, jclass);
/*
* Class: cn_kuncb_CCKcConfig
* Method: unregistered
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_cn_kuncb_CCKcConfig_unregistered
(JNIEnv *, jclass);
/*
* Class: cn_kuncb_CCKcConfig
* Method: getName
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_cn_kuncb_CCKcConfig_getName
(JNIEnv *, jclass);
/*
* Class: cn_kuncb_CCKcConfig
* Method: getHttpTimeout
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_cn_kuncb_CCKcConfig_getHttpTimeout
(JNIEnv *, jclass);
/*
* Class: cn_kuncb_CCKcConfig
* Method: isSeed
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_cn_kuncb_CCKcConfig_isSeed
(JNIEnv *, jclass);
/*
* Class: cn_kuncb_CCKcConfig
* Method: get
* Signature: ()Lcn/kuncb/CKcConfig;
*/
JNIEXPORT jobject JNICALL Java_cn_kuncb_CCKcConfig_get
(JNIEnv *, jclass);
/*
* Class: cn_kuncb_CCKcConfig
* Method: set
* Signature: (Lcn/kuncb/CKcConfig;)V
*/
JNIEXPORT void JNICALL Java_cn_kuncb_CCKcConfig_set
(JNIEnv *, jclass, jobject);
#ifdef __cplusplus
}
#endif
#endif
2.2 KcConfig.h
字段保持和java类CCKcConfig一至
#ifndef KC_CEE1D479_955D_624E_8B78_039F9D777D8D
#define KC_CEE1D479_955D_624E_8B78_039F9D777D8D
#include <stdint.h>
#include <stdbool.h>
/*
* 对应java中的cn.kunc.CKcConfig类
*/
struct KcConfig {
int32_t test;
int32_t http_timeout;
bool isseed;
//可伸缩字符串
char name[0];
};
#endif /* KC_CEE1D479_955D_624E_8B78_039F9D777D8D */
2.2 cn_kuncb_CCKcConfig.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cn_kuncb_CCKcConfig.h"
#include "KcConfig.h"
#define KC_SAFE_FREE(ptr) do {\
if(NULL !=(ptr) ) { \
free((ptr)); (ptr) = NULL; \
} \
} while (0)
static struct KcConfig* global_config = NULL;
//抛出NoClassDefFoundError异常
jint throwNoClassDefError(JNIEnv* env, char* message) {
jclass exClass;
const char* className = "java/lang/NoClassDefFoundError";
exClass = (*env)->FindClass(env, className);
if (NULL == exClass)
return throwNoClassDefError(env, message);
return (*env)->ThrowNew(env, exClass, message);
}
//抛出NullPointerException异常
jint throwNullPointerError(JNIEnv* jenv, char* message) {
jclass exClass;
const char* className = "java/lang/NullPointerException";
exClass = (*jenv)->FindClass(jenv, className);
if (NULL == exClass)
return throwNoClassDefError(jenv, message);
return (*jenv)->ThrowNew(jenv, exClass, message);
}
//抛出NoSuchMethod异常
jint throwNoSuchMethodError(JNIEnv* jenv, char* message) {
jclass exClass;
const char* className = "java/lang/NoSuchMethodError";
exClass = (*jenv)->FindClass(jenv, className);
if (NULL == exClass)
return throwNoClassDefError(jenv, message);
return (*jenv)->ThrowNew(jenv, exClass, message);
}
/// <summary>
/// 初始化
/// </summary>
jint JNICALL Java_cn_kuncb_CCKcConfig_registered(JNIEnv* env, jclass obj) {
struct KcConfig* config = NULL;
if (NULL != global_config)
return 0;
config = (struct KcConfig*)calloc(1, sizeof(struct KcConfig) + 25); //字符串可以保存24个字符
if (NULL == config) {
printf("out of memory.\n");
goto KC_ERROR_CLEAR;
}
memcpy(config->name, "ABCDEFGHIJKLMNOPQRSTUVWZ", 24);
config->test = 5;
config->http_timeout = 10;
config->isseed = false;
global_config = config;
return 0;
KC_ERROR_CLEAR:
KC_SAFE_FREE(config);
return 1;
}
/// <summary>
/// 释放资源
/// </summary>
void JNICALL Java_cn_kuncb_CCKcConfig_unregistered(JNIEnv* env, jclass obj) {
if (NULL == global_config)
throwNullPointerError(env, ("kcconfig not initialized."));
KC_SAFE_FREE(global_config);
}
jstring JNICALL Java_cn_kuncb_CCKcConfig_getName(JNIEnv* env, jclass obj) {
if (NULL == global_config)
throwNullPointerError(env, ("kcconfig not initialized."));
//C风格函数
return (*env)->NewStringUTF(env, global_config->name);
//cpp风格函数
//return jenv->NewStringUTF(global_config->appname);
}
jint JNICALL Java_cn_kuncb_CCKcConfig_getHttpTimeout(JNIEnv* env, jclass obj) {
if (NULL == global_config)
throwNullPointerError(env, ("kcconfig not initialized."));
return global_config->http_timeout;
}
jboolean JNICALL Java_cn_kuncb_CCKcConfig_isSeed(JNIEnv* env, jclass obj) {
if (NULL == global_config)
throwNullPointerError(env, ("kcconfig not initialized."));
return global_config->isseed;
//return '\0'; //false
//return '\1'; //true
}
jobject JNICALL Java_cn_kuncb_CCKcConfig_get(JNIEnv* env, jclass obj) {
jclass exClass;
jmethodID jmethondConstruct;
jfieldID jFieTest;
const char* CLASSNAME = "cn/kuncb/CKcConfig";
if (NULL == global_config)
throwNullPointerError(env, ("kcconfig not initialized."));
exClass = (*env)->FindClass(env, CLASSNAME);
if (NULL == exClass)
throwNoClassDefError(env, "java class cn.kuncb.CKcConfig not foud.");
jmethondConstruct = (*env)->GetMethodID(env, exClass, "<init>", "(IZLjava/lang/String;)V");
if (NULL == jmethondConstruct)
throwNoSuchMethodError(env, "java class cn.kuncb.CKcConfig Construct method not foud.");
jFieTest = (*env)->GetFieldID(env, exClass, "test", "I");
if (NULL == jFieTest)
throwNoSuchMethodError(env, "java class cn.kuncb.CKcConfig field test not foud.");
jobject jobj = (*env)->NewObject(env, exClass, jmethondConstruct,
global_config->http_timeout,
global_config->isseed,
(*env)->NewStringUTF(env, global_config->name)
);
(*env)->CallVoidMethod(env, exClass, jmethondConstruct);
global_config->test = 1;
(*env)->SetIntField(env, exClass, jFieTest, global_config->test);
return jobj;
}
void Java_cn_kuncb_CCKcConfig_set(JNIEnv* env, jclass obj, jobject objArg) {
jclass exClass;
jfieldID jFieTest;
jmethodID jmetGetHttpTime, jmetIsSeed, jmetGetName;
jint test = 0, http_timeout = 0;
jboolean isseed = true;
jobject name = NULL;
const char* strName = NULL;
jboolean isCopy;
jsize strNameLen = 0;
struct KcConfig* newConfig;
if (NULL == global_config)
throwNullPointerError(env, "kcconfig not initialized11.");
exClass = (*env)->GetObjectClass(env, objArg);
if (NULL == exClass)
throwNoClassDefError(env, "java class cn.kuncb.CKcConfig not foud.");
jFieTest = (*env)->GetFieldID(env, exClass, "test", "I");
if (NULL == jFieTest)
throwNoSuchMethodError(env, "java class cn.kuncb.CKcConfig field test not foud.");
test = (*env)->GetIntField(env, exClass, jFieTest);
jmetGetHttpTime = (*env)->GetMethodID(env, exClass, "getHttpTimeout", "()I");
if (NULL == jmetGetHttpTime)
throwNoSuchMethodError(env, "java class cn.kuncb.CKcConfig method getHttpTimeout not foud.");
jmetIsSeed = (*env)->GetMethodID(env, exClass, "isSeed", "()Z");
if (NULL == jmetIsSeed)
throwNoSuchMethodError(env, "java class cn.kuncb.CKcConfig method isSeed not foud.");
jmetGetName = (*env)->GetMethodID(env, exClass, "getName", "()Ljava/lang/String;");
if (NULL == jmetGetName)
throwNoSuchMethodError(env, "java class cn.kuncb.CKcConfig method getName not foud.");
//在这里会抛出异常,name or signature does not match,不知道是啥原因,而且接收的test值也和java传递过来时的不一至
http_timeout = (*env)->CallIntMethod(env, exClass, jmetGetHttpTime);
isseed = (*env)->CallBooleanMethod(env, exClass, jmetIsSeed);
name = (*env)->CallObjectMethod(env, exClass, jmetGetName);
if (NULL != name) {
strName = (*env)->GetStringUTFChars(env, name, &isCopy);
if (NULL != strName)
strNameLen = (*env)->GetStringUTFLength(env, name); //strNameLen=0表示空字符串==""
}
newConfig = (struct KcConfig*)realloc(global_config, sizeof(struct KcConfig) + strNameLen + sizeof(char));
if (NULL == newConfig) { //修改除name之外的字段
global_config->test = test;
global_config->http_timeout = http_timeout;
global_config->isseed = isseed;
} else {
if (strNameLen < 1) //空字符串不复制
memcpy(newConfig->name, strName, strNameLen);
newConfig->name[strNameLen] = '\0';
newConfig->http_timeout = http_timeout;
newConfig->isseed = isseed;
newConfig->test = test;
global_config = newConfig;
}
(*env)->ReleaseStringUTFChars(env, name, strName);
}
2.3 查看导出的dll
#查看依赖项
dumpbin /DEPENDENTS D:\MyWork\202109\x64\Debug\kc_config.dll
#查看导出的函数
dumpbin /EXPORTS D:\MyWork\202109\x64\Debug\kc_config.dll
3总结
在写C时如果依赖的模块比较少,则可以使用jin,但是如果依赖的模块比较多,jin大概率会加载失败(可能因为jdk版本或系统原因,不确定),因此建议采用以下两种方式实现:
- 直接将C程序制作为exe然后通过管道调用,此种方法实现简单,但是性能低下,还不如直接java实现;
- 用C写后台服务,此服务专供java调用,java与C之间通过网络通信来传递消息。