ChatScript java调用接口封装
来源:互联网 发布:男士护肤品牌 知乎 编辑:程序博客网 时间:2024/05/16 16:08
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部分提供的向外暴露函数一致)。
public class JniUse {// TODO Auto-generated method stubstatic{ System.load("/home/sf/workspace/CSbot/ChatScript-7.42/LIBRARY/libChatScript.so");}public native static String getOutput();public native static int performChatJava(String user, String incoming);public native static int initSystemJava(String root_path);public native static void closeSystemJava();public JniUse(String user){try{Properties prop = new Properties();InputStream in = new FileInputStream("jniTest.properties");prop.load(in);String root_path = prop.getProperty("root_path");System.out.println(root_path);initSystemJava(root_path);//setUserName(user);}catch(Exception e){e.printStackTrace();}}public static void main(String[] args) {}}
生成以上类后,编译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)的方式排除特殊符号。
while (*++at){if (*at < 31) *at = ' ';if (*at == '"' && *(at-1) != '\\') quote = !quote;if (*at == ' ' && !quote) // proper token separator{if ((at-startx) > (MAX_WORD_SIZE-1)) break; // troublestartx = at + 1;}}
封装后舍弃读文件的环节,通过参数的方式将用户文本传入,在判断字节是否小于31前,将字节转换成(unsigned char),不然所有汉字将会被排除掉,无法得到合理输出。
接下来可以写接口文件了。
#include "common.h"#include "jniTest_JniUse.h"#define LOCAL_PATH_MAX 300char* argvx1[1];JNIEXPORT jstring JNICALL Java_jniTest_JniUse_getOutput(JNIEnv *env, jclass jc){ return env->NewStringUTF(ourMainOutputBuffer);}JNIEXPORT jint JNICALL Java_jniTest_JniUse_performChatJava(JNIEnv *env, jclass jc, jstring user, jstring incoming){ ReadComputerID(); PerformChat((char*)env->GetStringUTFChars(user, 0), computerID, (char*)env->GetStringUTFChars(incoming,0), NULL, ourMainOutputBuffer); return 0;}JNIEXPORT jint JNICALL Java_jniTest_JniUse_initSystemJava(JNIEnv *env, jclass jc, const jstring root_path){ char* root; root = (char*)env->GetStringUTFChars(root_path, 0); argvx1[0] = (char*) malloc(10); strcpy(argvx1[0], (char*)"local"); printf("%s\n", argvx1[0]); // int i = InitSystem(1, argvx1); if (InitSystem((int)1, argvx1)) myexit((char*)"failed to load memory\r\n"); return 0;}JNIEXPORT void JNICALL Java_jniTest_JniUse_closeSystemJava(JNIEnv *env, jclass jc){ CloseSystem();}
include jniTest_JniUse.h和CS的头文件。
然后,修改SRC中的makefile,主要修改点如下
server: DEFINES+= -DLOCKUSERFILE=1 -DEVSERVER=1 -DEVSERVER_FORK=1 -DDISCARDPOSTGRES=1 -DDISCARDMONGO=1 -DDISCARDMYSQL=1 server: PGLOAD= -pthreadserver: 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/linuxserver: allserver: EXECUTABLE=../BINARIES/ChatScriptserver: SHARE_LIB=../BINARIES/libChatScript.soserver: CFLAGS=-c -std=c++0x -Wall -funsigned-char -Wno-write-strings -Wno-char-subscripts -Wno-strict-aliasing
library: $(OBJECTS)$(CC) $(LDFLAGS) $(DEFINES) $(INCLUDEDIRS) -fPIC -c $(SOURCES)$(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中。
- ChatScript java调用接口封装
- 接口封装及调用
- 封装调用接口类
- c++调用python封装接口
- php调用webservice接口封装方法
- 封装调用OEM7F7.EXE的接口
- 微信开发接口封装调用
- java(继承,封装,实现接口)
- redis接口的java封装
- Java 调用底层接口
- Java调用股票接口
- Java WebService 接口调用
- Java调用硬件接口
- java调用webservice 接口
- Java接口调用
- java短信接口调用
- java调用webservice接口
- Java 调用底层接口
- 串口termios函数
- POJ 2322 PLANKS 笔记
- 51单片机自己动手写一个printf函数
- 深入hibernate的三种状态
- CTypePtrList<BASE_CLASS, TYPE>的结构应用说明
- ChatScript java调用接口封装
- 玲珑杯 1137
- DraggableView
- Android 多层fragment 嵌套时,viewPager不显示的问题
- 互联网的十大圈子
- Java条形码生成技术-barcode4j-light-2.1.jar
- 唱歌气沉丹田怎么做 气沉丹田的口诀
- Linux 文件操作
- 字典dict