一、概述
本人之前安卓开发通过JNI调用so,实现Java调用C函数接口。最近在写Java服务器程序,需要Java调用C实现一些功能。
本文是针对读者是:开发过安卓JNI调用,想了解非安卓和非NDK环境下Java调用C函数。
二、软件环境
c/c++编译器:gcc 7.5.0
c/c++编辑器:codeblocks16.01
java编译器:openjdk 17.0.3
java编辑器:eclipse
开发系统:ubuntu18.04
运行系统:centos7
三、开发不同点
本人研究了一下,发现开发略有不同:
1、头文件和库:安卓JNI,开发环境这些NDK给你提供好了。而服务器Java中JNI开发需要稍微配置。
2、编译和调用:安卓JNI需要写Android.mk告诉NDK怎么编译。服务器Java中JNI通过gcc编译好so库,放到指定位置即可调用。
四、查找头文件
JNI开发需要用到很多特定的定义,比如数据类型jstring等,需要找到这些定义所在的头文件。
这些头文件只能在所使用的jdk目录下找,我使用的是eclipse自带的jdk环境,所以在eclipse目录下找。
根据后来成功编译的经验,需要添加如下这些头文件:
classfile_constants.h
jawt.h
jawt_md.h
jdwpTransport.h
jni.h
jni_md.h
jvmticmlr.h
jvmti.h
将这些头文件复制到工程目录jni_h,并将目录加进工程的头文件搜索目录。
五、实现JNI函数接口
#define JAVA_CALL_FUN(fun) Java_Cer_CerCallFun_##fun
jstring JAVA_CALL_FUN(GenCer)(JNIEnv * env, jobject objCl, jstring cerPubE, jstring cerPubN, jstring cerId,
jint cerFromTimeYear, jint cerFromTimeMonth, jint cerFromTimeDay, jint cerFromTimeHour,
jint cerFromTimeMin, jint cerFromTimeSec,
jint cerToTimeYear, jint cerToTimeMonth, jint cerToTimeDay, jint cerToTimeHour,
jint cerToTimeMin, jint cerToTimeSec,
jstring issuerCN, jstring issuerOU, jstring issuerO, jstring issuerL, jstring issuerST, jstring issuerC,
jstring subjectCN, jstring subjectOU, jstring subjectO, jstring subjectL, jstring subjectST, jstring subjectC,
jstring cerSignE, jstring cerSignN, jstring cerSignD, jint shaType);
这是本人要调用到的上生成证书函数JAVA_CALL_FUN(GenCer),函数名前缀用宏JAVA_CALL_FUN定义的。JNI函数命名规则和NDK一致的,即 Java_报名_类名_函数名,包名中的符合.用_代替。
JAVA_CALL_FUN(GenCer)展开为Java_Cer_CerCallFun_GenCer,包名Cer,类名CerCallFun,Java中的函数GenCer。
其余的JNI变量如JNIEnv,jobject,jstring ,jint和安卓NDK中JNI也是一致的,不作详细描述。
实现完功能之后,gcc编译,加了需要的jni头文件之后,没有报错,生成so文件,文件名libpci_verify.so。
六、Java调用动态库
package Cer;
import com.sun.jna.Library;
import com.sun.jna.Native;
public class CerCallFun {
static { System.loadLibrary("pci_verify"); };
public static native String GenCer(String cerPubE, String cerPubN, String cerId,
int cerFromTimeYear, int cerFromTimeMonth, int cerFromTimeDay, int cerFromTimeHour,
int cerFromTimeMin, int cerFromTimeSec,
int cerToTimeYear, int cerToTimeMonth, int cerToTimeDay, int cerToTimeHour,
int cerToTimeMin, int cerToTimeSec,
String issuerCN, String issuerOU, String issuerO, String issuerL, String issuerST, String issuerC,
String subjectCN, String subjectOU, String subjectO, String subjectL, String subjectST, String subjectC,
String cerSignE, String cerSignN, String cerSignD, int shaType);
}
测试代码
import Cer.CerCallFun;
public class TestMain {
public static void main(String[]args) {
String pubE = "010001";
String pubN = "C950B92F22236BA7CD6DECFD8280917DCFD36FBECD1B2782728C1AD90E3986BEB262C2E7E5CB8145E6CFDF9518C57E0C40737E76E42368B4EBD89229CF294A56976D647E706F1CE1250354816BF7E77270FC969B77C41F0B1B18C954A8B6163AD8E4C057F629F4E9A0B2D7FBDBB0A0004A94657A41C345C605C399E5BF726F33CE7BF2FEDBEABA7A934858A49DF17AE3F08753495852AC0491D5951A24913E1CF645E1EC938BAB3634E335F92AF8F147319E716CB906B7D4CF58EDD33E3CD857CAB81F98697E0F8B7265C5F441B0C46F0A20635E14336D9B047701E1A4AB29742EB739631B3CE089C9A48456897C05C5EC58A44B6181CED01423366C924C83B5";
String signE = "010001";
String signN = "DA51CA066FF0C04959AEDBBEBF61B315017DE5CBC57B964A7B884064F41DE15F53B7F13A9139C7D3CB6120D072E20D23BEC8EA237DD3AE76E411FCF250B1F18E9F883C33FA0FD8A5E488F1A7C538DDE94EC45FA9A467EA96871910144D68A20F6B79C0C8195F0772EDCCC84343C9460746315FF47C3AB3FC2222E1F51AA41AF6DBA67951272C7E1337FA5AC124C8ADA84CF3A34CB9FC625CB6E3199BD4EB68E851422914465323D78CD92E0DCC06397D1419DD0EB7FB4D61B2B0D997BB7BA43C9E3B0B956F9D9FC64013B317D8FD6C243FCCD2EB1E59B857D25FB1BE66440EE90E07115CF94BAD18F6790E6FE00520FF8BBBCB02187A7F2BDDDA1D92D5D6BCA9";
String signD = "9B270B0B1E1C9B9AC54896546E1051C3B8E6AF1BECC1D2E225FE83BCFB1C0D21C10E753A29214619076D84737F6194F2F55035CD794BA9AE46915E111B360AB4503DF28301D0A727E64D4867F1AA6352BE2EA2C99DD4580C1800C0C39CEBA2589109DAEE99D1462AF042DB83AA35ED359835D8BA985AE3B468D21F812028C61B9EA12BBF194BCA5BA9A2433A1193CA9BD5B515AA2363F5E486BBA5D9C9B0DE1AC22984739E9831B8AD2D193C1161A5485C9907D4BD1F5F93B1B7ABC94FDD834CFAD135330F1E30468AFA6BFD0F0CEB7097D1111329600EFE5BC83493B4A5D9FB2B483B27BD232FD0E310B0AAE1998479B05E5E65D120C703D5E108A36E5F3201";
System.out.println(System.getProperty("java.library.path"));
cer = CerCallFun.GenCer(pubE, pubN, "1234567890123456",
2022, 6, 3, 18, 0, 0,
2023, 6, 5, 18, 0, 0,
"1", "2", "3", "4", "5", "AU",
"a", "b", "c", "d", "e", "DK",
signE, signN, signD, 5);
System.out.println(cer);
System.out.println("done");
}
}
出错提示找不到模块pci_verify,我把libpci_verify.so放到java运行搜索库目录下。可以通过调用System.out.println(System.getProperty(“java.library.path”));,查看java运行搜索库目录有哪些。我放到这个目录/usr/java/packages/lib。
再次运行能看到测试输出的数据,即成功调用。