由于在项目中用到了java调用c++的SDK动态库的例子,博主之前没有接触过,所以一直在网上百度,千篇一律,经过几天的努力终于成功了。所以博主想把它记录下来,方便其他人参考。
以下是要调用的接口方法:
/************************************************
方法一:(上传图片)1、fileName:输入参数。
×************************************************ */
M2CCGKEYER_API BOOL CALL_TYPE M2CLogoCgKeyerUpLoadFile(const char *fileName);
/************************************************
方法二:(图片上线叠加)1、fileName:输入参数。
×************************************************ */
M2CCGKEYER_API BOOL CALL_TYPE M2CLogoCgKeyerSetupLogo(const char *fileName, int x, int y, BOOL bEnabled);
其中M2CLogoCgKeyerUpLoadFile和M2CLogoCgKeyerSetupLogo是方法名称,此方法在第三方提供的.h文件里面,我采用的是jni,之前也查过很多资料,考虑到效率问题我还是选择了我有难度的jni,至少效率比不用写c++的jna要高很多。因为这个函数是需要将图片叠加在视频上,必须要做到毫秒级别的。
第一步:(非必须的,根据项目实际需要)
下载 dom4j-1.6.1.jar和log4j-1.2.17.jar的jar包,右键选中项目创建文件夹lib,将jar包copy进去,最后build path.(在此说明jni是java自带的所以不需要jar包的,前面两个jar包,是因为我要解析XML文件,并做一个日志输出,我会在后面的文章中逐一讲解的)
第二步:
还是直接上代码吧,其中MyDLL7是用vs2015生成的dll文件,会在下面详细说明的,而M2CCGKeyer是第三方提供的dll动态库,注意这里加载顺序不能错,必须先加载MyDLL7,其实在这里有个误解,第三方的M2CCGKeyer.dll文件可以不用System.loadLibrary("M2CCGKeyer"),直接右键选中项目把这个文件copy在项目路径下就可以了,到时候会自己找到加载的。但是如果你写了System.loadLibrary("M2CCGKeyer"),那也必须将M2CCGKeyer.dll放在项目路径下。
public class Task1 {
package cn.com.plustv;
public class SmartCard {
static {
System.loadLibrary("MyDLL7");//用c++自己编写的dll库文件
System.loadLibrary("M2CCGKeyer");//第三方提供的库文件
}
/*方法一:
* ***********************************************
* 上载logo到设备,支持文件格式有:BMP和TGA。
* 支持BMP和TGA 24bit或32bit像素深度; 支持TGA RLE压缩格式
* 1、fileName:输入参数。 表示本地存储的Logo文件名,例如C://test.tga
* ×************************************************
*/
private native boolean M2CLogoCgKeyerUpLoadFile(String fileName);
//使用jni必须要用native修饰,不是我说的哦,因为就这么规定的,好了,接下来该头疼的参数问题了,对于一个没接触过c++的人,可能比较吃力,比如小编我就是,其实java中的String对应c++中的char 但是在编写c++的过程中需要转换,还有释放内存,我会在后面讲解的。而fileName就是输入参数。
private native boolean M2CLogoCgKeyerSetupLogo(String fileName, int x, int y, boolean bEnabled);//M2CLogoCgKeyerSetupLogo这种方法名称必须和第三方提供的接口方法名称保持一致,参数也要保持一一对应。
// 外部类能调用的方法一
public boolean tjSDK(String fileName) {
return this.M2CLogoCgKeyerUpLoadFile(fileName);
}
//方法三:
public boolean tjSDK03(String fileName, int x, int y, boolean bEnabled) {
return this.M2CLogoCgKeyerSetupLogo(fileName, x , y , bEnabled);
}
}
第三步:
需要编写一个测试方法,等到dll文件生成后,测试。
public class Task1 {
package cn.com.plustv;
import org.junit.Test;
public class TestSdk {
SmartCard sc = new SmartCard();
@Test
public void test01() {
//测试方法一:通过
boolean tjSDK = sc.tjSDK("D:/200311000100101.tga");
System.out.println(tjSDK);
}
@Test
public void test03() {
// 测试方法三:通过
boolean tjSDK03 = sc.tjSDK03("/logo/200311000100103.tga", 0, 0, true);
System.out.println(tjSDK03);
} }
第四步:
然后使用javac cn/com/plustv/SmartCard.java回车,再使用javah cn.com.plustv.SmartCard 回车,你会发现在src下会生成一个.h的文件,如下图所示:
第五步:(编写dll文件)
1、 打开visual Studio2015,新建一个项目,选择Win32—>win32项目,项目名称为:MyDLL5。如下图所示:
2、 点击确定,然后点击下一步,进入应用程序设置的页面,如下图所示:在应用程序类型下选择”DLL”,在附加选项中选中”导出符号”,点击完成。如下图所示:
生成的项目的目录结构如下图所示:
3、 在VS工程中,添加c/c++工程中外部头文件及库的基本步骤:(重要)
a、添加工程的头文件目录:工程---属性---配置属性---c/c++---常规---附加包含目录:加上头文件存放目录。(此处需加上自己用javah生成的.h文件目录和第三方提供的.h文件目录)
b、添加文件引用的lib静态库路径:工程---属性---配置属性---链接器---常规---附加库目录:加上lib文件存放目录。(此处加上第三方提供的lib文件目录)然后添加工程引用的lib文件名:工程---属性---配置属性---链接器---输入---附加依赖项:加上lib文件名。(此处加上第三方提供的lib文件名)
4、在MyDLL7.cpp中加入以下代码:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
// MyDll.cpp : 定义 DLL 应用程序的导出函数。
//
#include "jni.h"
#include "stdafx.h"
#include "cn_com_plustv_SmartCard.h"
#include "M2CCGKeyerDll.h"
#include "stdafx.h"
#include <tchar.h>
#include <iostream>
using namespace std;
#ifdef __cplusplus
extern "C" {
#endif
//方法一:
JNIEXPORT jboolean JNICALL Java_cn_com_plustv_SmartCard_M2CLogoCgKeyerUpLoadFile
(JNIEnv * env, jobject obj, jstring fileName)
{
//char strText[256] = { 0 };
//char buf[128];
//测试
// MessageBox(0, (LPCWSTR)"OK", 0, 0);
BOOL result = 0;
jboolean jb = JNI_TRUE;
const char* strDeviceInfo = (*env).GetStringUTFChars(fileName, &jb);
//printf("String1 = [%s]\n", strDeviceInfo);
//执行商家提供的方法
//sprintf_s(strText, 128, "fileName: %s", strDeviceInfo);
//MessageBox(NULL, strText, "UpLoadFile", 0);
if (strDeviceInfo == NULL) {
return NULL;
}
result = M2CLogoCgKeyerUpLoadFile(strDeviceInfo);
//释放资源
//在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,
//让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出
env->ReleaseStringUTFChars(fileName, strDeviceInfo);
return (jboolean)result;
}
//JNIEXPORT jboolean JNICALL Java_cn_com_plustv_SmartCard_M2CLogoCgKeyerSetupLogo
//(JNIEnv *, jobject, jstring, jint, jint, jboolean);
//M2CCGKEYER_API BOOL CALL_TYPE M2CLogoCgKeyerSetupLogo(const char *fileName, int x, int y, BOOL bEnabled);
//方法三:jint 和 c或c++的 signed int 是等同的
JNIEXPORT jboolean JNICALL Java_cn_com_plustv_SmartCard_M2CLogoCgKeyerSetupLogo
(JNIEnv * env, jobject obj, jstring fileName, jint x, jint y, jboolean bEnabled)
{
// MessageBox(0, (LPCWSTR)"OK", 0, 0);
BOOL result = 0;
jboolean jb = JNI_TRUE;
const char* strDeviceInfo = (*env).GetStringUTFChars(fileName, &jb);
//执行商家提供的方法
result = M2CLogoCgKeyerSetupLogo(strDeviceInfo, x, y, bEnabled);
//释放资源
//在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,
//让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,
//JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出
(*env).ReleaseStringUTFChars(fileName, strDeviceInfo);
return (jboolean)result;
}
#ifdef __cplusplus
}
#endif
说明:代码具体的实现涉及到jni中的类型和vc++中类型的对应转换的操作,第三方提供的dll中的函数参数是c/c++中的类型,而通过java方法传进来的参数通过jni的处理转变为java和c/c++之间的一种过渡类型,比如jstring等,此时,就需要我们调用jni中的对应方法来转换该类型,具体需参照jni文档。
第六步:(生成dll文件)
如下图所示,在解决方案配置中选择”Dubug”,点击”启动调试”按钮,在这里我选择32位,最好跟第三方用的操作系统保持一致。
这时,就会在工程下的Dubug文件夹下生成MyDLL5.dll。我的是在:C:\Users\java\Documents\Visual Studio2015\Projects\MyDLL5\Debug路径下。注:在解决方案配置中如果选择Release,则会在工程下的Release文件夹下生成MyDLL5.dll
第七步:(测试)
将dll文件放到java项目中将生成的MyDLL5.dll和商家提供的M2CCGKeyer.dll文件拷贝到项目的目录下,如下图:
说明:在我的项目中是通过网络连接第三方那个设备,所以我需要将netconfig.ini(里面是一个ip地址)文件放在自己的jre的bin目录下。
最后运行测试方法会在控制台打印一个true.
我不能保证写的每个地方都是对的,但是至少是我仔细思考过的,希望大家多多指教,面对压力,我可以挑灯夜战、不眠不休;面对困难,我愿意迎难而上、永不退缩。