当前位置:   article > 正文

ChatScript java调用接口封装

chatscript

ChatScript是一个很完整的对话框架,但是,对话系统往往并不是独立存在的,在我的应用场景下,它只是语音对话的一部分,被调用,生成完美的回复。我需要的是一个完整的语音对话APP,CS底层是C++实现的,而APP由java实现,因此,要将CS封装成一个java接口,供APP调用。

封装接口主要工作分三部分:(1). 看底层主调程序,锁定哪些函数是要被暴露出去的,需要做怎样的修改。(纯C部分)(2).定义java类中需要native化的函数,以及调用CS函数的方法。(JAVA部分)(3). 在C层面写native函数的实现。(JNI和C结合部分)

 

1.      JNI

JNI是支持java和C之间相互调用的工具。这里更关注java、JNI、C/C++之间的数据类型转换、数据编码格式等,JNI的基本概念,请google。

JNI所定义的数据类型和C的数据类型不同,数据类型间的映射关系请参考http://blog.csdn.net/conowen/article/details/7523145/

 

Java、JNI和C之间的数据的传递过程如下:

                                                                              

C/C++内部默认的字符串编码格式是ASCII,Java的String对象的编码格式默认为UTF-16,Java本身提供将String转为UTF-8的方法,JNI默认的编码格式是UTF-8。CS作者把系统读文本和处理文本的编码格式改为UTF-8,因此,从用户输入到获取回复文本,数据格式的转换过程为String(UTF-16) -> UTF-8 -> UTF-8 -> String。基本上只需要将Java给出的String(对应jni的jstring)转化为UTF-8的char*,将bot生成的UTF-8char*回复转化为String。用JNI提供的GetStringUTFChars()和NewStringUTF即可实现。

 

2.      锁定被java调用的函数

CS的主调文件是mainSystem.c,这个函数将整个底层组织成一个对话系统。mainSystem实现对话的流程如下:

                                                             

系统主要分四部分:系统初始化、用户输入读入、对话处理、退出系统。

无论是local模式还是server模式,CS的输入实际上主要是stdin,即命令行输入,底层把stdin当做文件来处理。但如果作为对话处理被调用,那么,输入最好不是从文件中读,用户直接传入输入字符指针效率更高。同时,系统的循环也不需要在接口中考虑,而是由APP调用者来处理。因此,要暴露出来的函数只有InitSystem、PerfomChat、CloseSystem,这是最简单的封装。

这三个函数的原定义为:

unsignedint InitSystem(int argc,char * argv[],char* unchangedPath =NULL,char*readonlyPath =NULL,char* writablePath =NULL,USERFILESYSTEM* userfiles =NULL,DEBUGAPI in =NULL,DEBUGAPI out =NULL);

int PerformChat(char* user,char*usee,char* incoming,char* ip,char* output);

void CloseSystem();

 

上面提到,JNI和C/C++的数据类型不同,为了保持原程序的原始完整性,以上三个函数保持不变,使他们依然可以通过自有的MainLoop系统调用,我们新增一个cpp文件,以上每个函数用新的函数调用,而这些新的函数采用JNI的数据结构,和JNI对接。

另外,用getOutput函数读取原系统中output中的回复内容。

 

3.  Java调用类

上面部分将C部分提供的函数确定后,新建一个java工程,定义需要native的函数(和C部分提供的向外暴露函数一致)。

  1. public class JniUse {
  2. // TODO Auto-generated method stub
  3. static
  4. {
  5. System.load("/home/sf/workspace/CSbot/ChatScript-7.42/LIBRARY/libChatScript.so");
  6. }
  7. public native static String getOutput();
  8. public native static int performChatJava(String user, String incoming);
  9. public native static int initSystemJava(String root_path);
  10. public native static void closeSystemJava();
  11. public JniUse(String user){
  12. try{
  13. Properties prop = new Properties();
  14. InputStream in = new FileInputStream("jniTest.properties");
  15. prop.load(in);
  16. String root_path = prop.getProperty("root_path");
  17. System.out.println(root_path);
  18. initSystemJava(root_path);
  19. //setUserName(user);
  20. }catch(Exception e){
  21. e.printStackTrace();
  22. }
  23. }
  24. public static void main(String[] args) {
  25. }
  26. }

生成以上类后,编译java文件:

javac JniUse.java

然后在JniUse的包目录下用class文件生成.h头文件:

javah JniTest.JniUse.h

生成的头文件为jniTest_JniUse.h。

将ChatScript整个文件夹放到工程的根目录下,为了保持CS内部的相对路径不受工程目录和机构改变的影响,文件夹一定要放在工程根目录下。

 

 

 

4.  C对接JNI

在封装原程序前需要注意两点:

1.  在第2节提到,尽量不改变原程序,但是,由于原系统多处读和写文件,读写文件的目录都是相对路径,而将ChatScript放在java里面,程序运行时,当前目录不再是CS的目录,而是工程目录,因此,将原程序中所有读写文件路径全部添加“ChatScript/”,不然系统找不到相应的文件。

2.  UTF-8的编码将一个汉字编码成3个字节,每个字节表示汉字时,第7位(最高位)为1, 原对话系统通过读文件的方式获取用户文本,在读文本时,实际将char转换成(unsigned char),然后在performChat中,通过(at<31)的方式排除特殊符号。

  1. while (*++at)
  2. {
  3. if (*at < 31) *at = ' ';
  4. if (*at == '"' && *(at-1) != '\\') quote = !quote;
  5. if (*at == ' ' && !quote) // proper token separator
  6. {
  7. if ((at-startx) > (MAX_WORD_SIZE-1)) break; // trouble
  8. startx = at + 1;
  9. }
  10. }

封装后舍弃读文件的环节,通过参数的方式将用户文本传入,在判断字节是否小于31前,将字节转换成(unsigned char),不然所有汉字将会被排除掉,无法得到合理输出。

 

接下来可以写接口文件了。

  1. #include "common.h"
  2. #include "jniTest_JniUse.h"
  3. #define LOCAL_PATH_MAX 300
  4. char* argvx1[1];
  5. JNIEXPORT jstring JNICALL Java_jniTest_JniUse_getOutput(JNIEnv *env, jclass jc){
  6. return env->NewStringUTF(ourMainOutputBuffer);
  7. }
  8. JNIEXPORT jint JNICALL Java_jniTest_JniUse_performChatJava(JNIEnv *env, jclass jc, jstring user, jstring incoming){
  9. ReadComputerID();
  10. PerformChat((char*)env->GetStringUTFChars(user, 0), computerID, (char*)env->GetStringUTFChars(incoming,0), NULL, ourMainOutputBuffer);
  11. return 0;
  12. }
  13. JNIEXPORT jint JNICALL Java_jniTest_JniUse_initSystemJava(JNIEnv *env, jclass jc, const jstring root_path){
  14. char* root;
  15. root = (char*)env->GetStringUTFChars(root_path, 0);
  16. argvx1[0] = (char*) malloc(10);
  17. strcpy(argvx1[0], (char*)"local");
  18. printf("%s\n", argvx1[0]);
  19. // int i = InitSystem(1, argvx1);
  20. if (InitSystem((int)1, argvx1)) myexit((char*)"failed to load memory\r\n");
  21. return 0;
  22. }
  23. JNIEXPORT void JNICALL Java_jniTest_JniUse_closeSystemJava(JNIEnv *env, jclass jc){
  24. CloseSystem();
  25. }

include jniTest_JniUse.h和CS的头文件。

 

然后,修改SRC中的makefile,主要修改点如下

 

server: DEFINES+= -DLOCKUSERFILE=1  -DEVSERVER=1 -DEVSERVER_FORK=1  -DDISCARDPOSTGRES=1 -DDISCARDMONGO=1 -DDISCARDMYSQL=1 
server: PGLOAD= -pthread
server: INCLUDEDIRS=-Ievserver -I../MYCODE/cppjieba-5.0.0/include/ -I../MYCODE/cppjieba-5.0.0/deps/ -I/usr/java/jdk1.8.0_101/include -I/usr/java/jdk1.8.0_101/include/linux
server: all
server: EXECUTABLE=../BINARIES/ChatScript
server: SHARE_LIB=../BINARIES/libChatScript.so
server: CFLAGS=-c  -std=c++0x -Wall  -funsigned-char  -Wno-write-strings -Wno-char-subscripts -Wno-strict-aliasing 

  1. library: $(OBJECTS)
  2. $(CC) $(LDFLAGS) $(DEFINES) $(INCLUDEDIRS) -fPIC -c $(SOURCES)
  3. $(CC) -fPIC -shared $(OBJECTS) -o $(SHARE_LIB)

编译时引入jni.h所在路径 /usr/java/jdk1.8.0_101/include
和jni_md.h所在路径 /usr/java/jdk1.8.0_101/include/linux

要生成动态库,最好再生成.o文件时变使用-fPIC。

Make后得到libChatScript.so。

然后,通过JniUse.java即可实现通过java调用CS,生成自定义的对话系统了。

 

如果修改了脚本,需要重新加载脚本,则将可执行文件ChatScript复制一份放到java工程根目录下,在根目录下执行

./ChatScript localBuild0=ChatScript/RAWDATA/files0.txt

./ChatScript localBuild0=ChatScript/RAWDATA/filesYouFolder.txt

即可重新编译脚本,将脚本内容写入ChatScript/TOPIC中。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/834776
推荐阅读
相关标签
  

闽ICP备14008679号