第三方给了so文件和一个.c和.h调用例子,还有一个.a的库
但是通过nm查看so的方法,发现想要调用的方法返回值是void ,返回值是定义在入参中的(不懂c语言),于是只能通过生成一个中间库的方式去调用第三方库,然后通过中间库暴露有返回值的方法供jna调用
通过nm命令查看so的方法
nm XXX.so
其中为 U 的方法是未被定义的,不可以直接调用
1. 编译中间库
给的例子中调用了一个.a库中的方法(我没找到引入的地方),头文件简单如下
#ifdef __cplusplus
extern "C"{
#endif
void Encode(unsigned char *passwd,unsigned char *result);
#ifdef __cplusplus
}
#endif
.c例子源代码中只有一个man方法调用.a中的方法打印一下
int main()
{
unsigned char str2[12];
Encode("990818",str2);
printf("%s\n",str2);
}
在这个文件中新建一个方法
char* getEncode(char* a)
{
unsigned char str2[12];
printf("before encode %s\n",a);
Encode(a,str2);
//char temp[256] = "456";
//memcpy(a, temp, strlen(temp)) ;
printf("after encode %s\n",str2);
char *s = malloc(100);
strcpy(s, str2);
//char* aa=str2;
//printf("%s\n",aa);
//jstring encodePas=(*env)->NewStringUTF(aa);
return s;
}
注意下返回值是不是你java代码中需要的
有一点,我不是通过jni的那种先根据class生成.h文件编辑的,所以不需要在c语言中引入jni之类的模块,所以我这样的方法也不会在java中调用时抛出jvm相关的错误
2. 将中间库编译成新的so文件
在linux中安装gcc后编译
gcc demo.c -fPIC -I/usr/local/mysoft/jdk/jdk1.8.0_231/include -I/usr/local/mysoft/jdk/jdk1.8.0_231/include/linux -shared -o liencode.so libgtjautil64.a
有几点很重要
-I 是指定jdk对应的库,不指定的话可能会报错
因为我的这个项目引用了.a库文件,所以一定一定要在编译的时候把.a文件也编译上,不然运行会找不到方法
3.编写java代码(这应该是最简单的了)
String os = System.getProperty("os.name"); // 获取当前操作系统的类型
int beginIndex = os != null && os.startsWith("Windows") ? 1 : 0;// windows操作系统为1 否则为0
// [Native.synchronizedLibrary] 阻止多线程同时访问本地代码
Clibrary INSTANTCE = (Clibrary) Native.synchronizedLibrary(
(Clibrary) Native.loadLibrary(
EncodePasUtils.filePath
// .getPath()
.substring(beginIndex)
, Clibrary.class
)
);
String getEncode(String password);
然后去调用就大功告成了(需要在linux环境下)
4.打包发布
因为这个项目我是准备打成一个jar供其它项目调用的,我又不想定义绝对路径在每个服务器都要放个so文件,所以需要通过流去将so文件读取生成到服务器的某个目录,然后再去调用它
static {
logger.info("begin write so to temp ");
String systemType = System.getProperty("os.name");
// String libExtension = (systemType.toLowerCase().indexOf("win")!=-1) ? ".dll" : ".so";
String libExtension = ".so";
String libFullName = "liencode" + libExtension;
//动态库的输出目录 可自行设置
String nativeTempDir = System.getProperty("java.io.tmpdir");
logger.info("write so path "+nativeTempDir);
InputStream in = null;
BufferedInputStream reader = null;
FileOutputStream writer = null;
File extractedLibFile = new File(nativeTempDir+File.separator+libFullName);
if(!extractedLibFile.exists()){
try {
// “/”代表Jar包的根目录
in = Clibrary.class.getResourceAsStream("/" + libFullName);
if(in==null){
in = Clibrary.class.getResourceAsStream(libFullName);
}
Clibrary.class.getResource(libFullName);
reader = new BufferedInputStream(in);
writer = new FileOutputStream(extractedLibFile);
byte[] buffer = new byte[1024];
while (reader.read(buffer) > 0){
writer.write(buffer);
buffer = new byte[1024];
}
} catch (IOException e){
logger.info("read so fail-----------------------");
e.printStackTrace();
} finally {
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
然后打包的时候指定maven打包插件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<!-- 必须要指定 1.7 不然项目不兼容 -->
<!-- <source>1.7</source>-->
<!-- <target>1.7</target>-->
<encoding>UTF-8</encoding>
<archive>
<manifest>
<!-- <mainClass>com.gd.net.NetMain</mainClass>-->
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<!--下面是为了使用 mvn package命令,如果不加则使用mvn assembly-->
<executions>
<execution>
<id>make-assemble</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
因为服务器代码使用jdk1.7编译,并且spring都是3.几的代码,所以不能用1.8去编译,不然运行报错
打包还要注意,因为我本地为了方便测试用的springboot并且打包是可执行的包,但是当作依赖的话springboot依赖的包太多了,把它去掉并打成依赖包即可
通过下面的命令将jar安装到本地maven库
mvn install:install-file -Dfile=XXXX.jar -DgroupId=com.linkstec -DartifactId=XXXX -Dversion=0.0.1-SNAPSHOT -Dpackaging=jar
大功告成!