交叉编译总结笔记
在项目的流程中,我们涉及到使用交叉编译的部分,关于这一块,我将研究后的结果总结如下。
DLL是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。
整体环境:
64位 Win7系统
jdk1.8.0_45
Eclipse-Mars
Visual Studio 2013
首先,JAVA是无法直接调用C#的dll的,需要通过经过桥接的方式,我才用的方法是通过管理性的c++桥接的方式,帮助JAVA调用C#的dll。整体流程如下。
整体的大概流程是:
Java -> JNI -> C++ dll <== Managed C++ ==> C# dll
依照这个顺序来执行的话,第一步需要在JAVA中写好一个类,我们将其命名为TestJNI,这个类里面声明的函数最终将会由C#来实现:
public class TestJNI {
public native void denoiseWord(String path,int trd);
public native void cutwords(int trd, String analyzer);
public native void key(String Path,int trd);
public native void translate(int trd);
static {
System.loadLibrary("c++dll");
}
public static void main(String[] args) {
TestJNI t = new TestJNI();
System.out.println(System.getProperty("java.library.path"));
}
}
在这里我声明了函数translate,这个函数在JAVA中不会被实现,他真正被实现的地方是在C#所写的dll中。
在完成了JAVA部分的代码后,需要用javah命令产生.h文件,我是用的JAVA编译软件是eclipse,在工程目录下的bin文件夹内可以看到.class文件,打开命令行,输入
javah -classpath . -jni TestJNI
正确执行指令之后,在当前文件夹内生成了这个类的.h文件。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJNI */
#ifndef _Included_TestJNI
#define _Included_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: TestJNI
* Method: denoiseWord
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_TestJNI_denoiseWord
(JNIEnv *, jobject, jstring, jint);
/*
* Class: TestJNI
* Method: cutwords
* Signature: (ILjava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_TestJNI_cutwords
(JNIEnv *, jobject, jint, jstring);
/*
* Class: TestJNI
* Method: key
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_TestJNI_key
(JNIEnv *, jobject, jstring, jint);
/*
* Class: TestJNI
* Method: translate
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_TestJNI_translate
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
在这个.h头文件中,可以清楚地看到,在JAVA中声明的函数在这个头文件中被声明了。暂时跳过c++的部分,因为c++的作用是起到中间的桥梁的作用,真正的两边是JAVA和C#,所以我们先完成C#的部分,最后再来搭建中间的桥梁。
创建一个C#控制台程序,在开始编程之前,在菜单栏中选择【项目】-》【设置】-》【应用】中将输出类型改为类库。
完成C#部分对于函数的实现,编译成动态链接库。
下一步就是C++部分。首先用VS2013创建一个C++编写类库的工程,首先承建一个C++的win32工程,在设置页面注意勾选:
在编译C++DLL之前,需要做以下配置,在项目属性对话框中选择"C/C++"|"Advanced",将Compile AS 选项的值改为"C++"。在运行目录下,需要引入刚才C#编译完成的动态链接库,还有三个头文件分别是jni.h、jni_md.h和刚才通过javah指令生成的头文件,前两个文件可以从网上下载,在C++程序中引入这几样头文件,使用c#动态链接库的命名空间。
#include "stdafx.h"
#include "c++dll.h"
#include "jni.h"
#include "jni_md.h"
#include "TestJNI.h"
#include <string>
#include "string.h"
#include <malloc.h>
#include <stdlib.h>
#include <vcclr.h>
//引入c#的库和命名空间
#using "ClassToDll.dll"
using namespace ClassToDll;
#using "mscorlib.dll"
#using "System.dll"
using namespace System;
特别需要注意,选择《项目》-> 《属性页》->《配置属性》->《常规》->《公共语言运行库支持》,选择公共语言运行库支持(/clr)。
由于可能会用到jstring和string之间的转换问题,所以需要自己实现以下几个函数,用来做到字符串格式之间的转换。
jstring stringTojstring(JNIEnv* env, const char* pat)
{
jclass strClass = env->FindClass("Ljava/lang/String;");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}
char* jstringTostring(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
String^ jstringToStr(JNIEnv* env, jstring jstr)
{
char* str = jstringTostring(env, jstr);
String^ value = gcnew String(str);
free(str);
return value;
}
jstring strTojstring(JNIEnv* env, String^ rtn)
{
pin_ptr<const wchar_t> wch = PtrToStringChars(rtn);
size_t convertedChars = 0;
size_t sizeInBytes = ((rtn->Length + 1) * 2);
char *ch = (char *)malloc(sizeInBytes);
errno_t err = wcstombs_s(&convertedChars,
ch, sizeInBytes,
wch, sizeInBytes);
jstring js = stringTojstring(env, ch);
free(ch);
return js;
}
之后,根据javah生成头文件中的函数声明,依次实现各个函数。
JNIEXPORT void JNICALL Java_TestJNI_denoiseWord
(JNIEnv *env, jobject obj, jstring path, jint trd)
{
//c#中的对象
Denoise ^d = gcnew Denoise();
d->denoiseWord(jstringToStr(env, path),(int)trd);
}
JNIEXPORT void JNICALL Java_TestJNI_cutwords
(JNIEnv *env, jobject obj, jint trd ,jstring)
{
Denoise ^d = gcnew Denoise();
d->cutwords((int)trd, "Lucene.China.ChineseAnalyzer");
}
JNIEXPORT void JNICALL Java_TestJNI_key
(JNIEnv *env, jobject, jstring Path ,jint trd)
{
Denoise ^d = gcnew Denoise();
d->key( jstringToStr(env, Path),(int)trd);
}
JNIEXPORT void JNICALL Java_TestJNI_translate
(JNIEnv *env, jobject, jint trd)
{
Denoise ^d = gcnew Denoise();
d->translate((int)trd);
}
将c++工程进行编译,得到c++的动态链接库。
回到JAVA程序,我特意在最开始留了一句
System.out.println(System.getProperty("java.library.path"));
用来输出library.path的地址,在这个地址下,将刚才编译好的C++,C#和其他用到的动态链接库放在这个文件夹下,运行JAVA程序,就可以使用JAVA调用c#编写的动态链接库了。
报错处理:
如果出现如下报错
一般是因为没有把库放在正确的路径下面,或者缺少依赖的库,补上后可以消除这个问题。
如果出现如下报错
这个是JAVA虚拟机内部错误,一般问题是出在了C#运行中出现的错误,由于C#和JAVA中string的编码格式不同,所以使用命令行输出会出现输出乱码的问题,推荐将错误信息输出到文件,这样可以看到C#里面出错的部分在哪里。