对CRF++进行Java封装

(1)      使用JNI对CRF++进行封装

由于CRF++是由C++语言编写的条件随机场工具,为了在系统主体的Java程序中使用,需要使用JNI技术对Java进行扩展,并将CRF++编译为可被Java加载和调用的动态链接库。

JNI是Java Native Interface的缩写,中文为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互。在没有SWIG工具时,需要手工编写大量代码用于将C++代码封装为可供Java调用的接口函数,在SWIG工具的帮助下,能大量减少工作。

SWIG是个帮助使用C或者C++编写的软件能与其它各种高级编程语言进行嵌入联接的开发工具。在本系统中,我们使用SWIG将C++编写的CRF++程序与Java编写的系统主要部分联接。

下载SWIG Windows版,解压到某一安装目录下,将含有swig.exe的目录添加的环境变量Path中,供后续使用。

SWIG对程序的封装需要编写接口文件,在接口文件中导入“crfpp.h”文件,使得SWIG导出crfpp.h中定义的接口,将其封装为JNI。编写接口文件CRFPP.i如下:

[code]
%module CRFPP
%include exception.i
%{
#include "crfpp.h"
%}
%newobject surface;
%exception {
try { $action }
catch (char *e) { SWIG_exception (SWIG_RuntimeError, e); }
catch (const char *e) { SWIG_exception (SWIG_RuntimeError, (char*)e); }
}
%feature("notabstract") CRFPP::Tagger;
%ignore CRFPP::createTagger;
%ignore CRFPP::getTaggerError;
%extend CRFPP::Tagger { Tagger(const char *argc); }
%{
void delete_CRFPP_Tagger (CRFPP::Tagger *t) {
delete t;
t = 0;
}
CRFPP::Tagger* new_CRFPP_Tagger (const char *arg) {
CRFPP::Tagger *tagger = CRFPP::createTagger(arg);
if (! tagger) throw CRFPP::getTaggerError();
return tagger;
}
%}
%include crfpp.h
%include version.h
[/code]

其中crfpp.h与version.h文件都可以在CRF++源代码中找到,但需将crfpp.h替换为前文中提到的64位的头文件。

进入Windows命令行界面,切换至含有CRFPP.i接口文件的路径,输入命令:

[code]swig –c++ -java -package org.chasen.crfpp CRFPP.i[/code]

命令执行完后会生成如表生成的CRF++封装文件所示的6个文件。

生成的CRF++封装文件

CRFPP.java
CRFPPConstants.java
CRFPPJNI.java
SWIGTYPE_p_unsigned___int64.java
Tagger.java
CRFPP_wrap.cxx

其中,CRFPP_wrap.cxx文件是C++端的封装文件用于将CRF++封装为JNI接口。将其拷贝至CRF++编译工程目录。

将另外5个Java文件拷贝至本系统主体Java工程的org.chasen.crfpp包中,其中Tagger类是我们将要使用到的主要类。

(2)      JNI中文乱码的解决

由于在java内部是使用的16bit的Unicode编码(UTF-16)来表示字符串的,无论英文还是中文都是2字节;JNI内部是使用UTF -8编码来表示字符串的,UTF -8是变长编码的Unicode,一般ASCII字符是1字节,中文是3字节;C/C++使用的是原始数据,ASCII就是一个字节,在Windows操作系统中,中文一般是GB2312编码,用2个字节表示一个汉字。[21]如果直接在Java程序中传入中文字符串将会出现不可逆的中文乱码,对命名实体识别的效果有很大影响。

在swig生成的CRFPP_wrap.cxx文件中,在约270行的位置找到“#include “crfpp.h””,在这条语句下面加入两个函数:

l  JStringToWindows

[cpp]
//to use chinese never foget to free.
char * JStringToWindows(JNIEnv * pJNIEnv, jstring jstr)
{
jsize len = pJNIEnv->GetStringLength(jstr);
const jchar * jcstr = pJNIEnv->GetStringChars(jstr, NULL);
int size = 0;
char * str = (char *)malloc(len * 2 + 1);
if ((size = WideCharToMultiByte(CP_ACP, 0, LPCWSTR(jcstr), len, str, len * 2 + 1, NULL, NULL)) == 0)
return NULL;
pJNIEnv->ReleaseStringChars(jstr, jcstr);
str[size] = 0;
return str;
}
[/cpp]
l  WindowsTojstring

[cpp]
jstring WindowsTojstring( JNIEnv* env, char* str )
{
jstring rtn = 0;
int slen = strlen(str);
unsigned short * buffer = 0;
if( slen == 0 )
rtn = (env)->NewStringUTF(str );
else
{
int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
buffer = (unsigned short *)malloc( length*2 + 1 );
if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )
rtn = (env)->NewString(  (jchar*)buffer, length );
}
if( buffer )
free( buffer );
return rtn;
}
[/cpp]

在源文件中查找“GetStringUTFChars”函数,将其替换为“JStringToWindows”,查找“NewStringUTF”,将其替换为“WindowsTojstring”。并在“JStringToWindows”函数返回的字符串使用后,使用stdio标准库中的“free”函数将其释放,以防造成内存泄露。

在CRF++工程中,以动态链接库形式编译CRFPP_wrap.cxx为CRFPP.dll文件。将该文件拷贝到本系统Java主体的工程根目录下。

(3)      JNI封装后CRF++的使用

在Tagger类中加入如下代码:

[java]
static {
try {
System.loadLibrary("CRFPP");
} catch (UnsatisfiedLinkError e) {
System.err.println("Cannot load the example native code.\nMake sure your LD_LIBRARY_PATH contains \’.\’\n"+ e);
System.exit(1);
}
}
[/java]

在Tagger类被调用的时候,会自动在环境变量的Path路径下查找CRFPP.dll动态链接库,作为CRF++的核心载入主程序中使用。

1)        初始化Tagger类时使用“-m”参数指定模型文件地址。

2)        使用boolean clear()方法清除已有的数据。

3)        使用boolean add(String str)方法添加特征,每一个add方法中添加的特征形如CRF++的训练文件,只是少了标注列。

4)        使用boolean parse()方法出发CRF++的标注。

5)        使用String y2(long i)方法获取对应序号CRF++标注。

6 评论

  1. 你好!您文中有一句话“其中crfpp.h与version.h文件都可以在CRF++源代码中找到,但需将crfpp.h替换为前文中提到的64位的头文件。”请问“前文中提到的64位的头文件”指的是什么?我翻了一下您的博客也没有发现有这个文件,我现在也想以JNI的方式使用CRF++谢谢!!!另外,您的博客对于我这样不懂C++的人来说有点难,比如“以动态链接库形式编译CRFPP_wrap.cxx为CRFPP.dll文件”、“使用stdio标准库中的“free”函数将其释放,以防造成内存泄露”这些都需要再去查资料。还是表示感谢!

发表评论

电子邮件地址不会被公开。 必填项已用*标注