OpenCC的編譯與多語言使用

OpenCC全稱Open Chinese Convert,是一個Github上面的開源項目,主要用於簡繁體漢字的轉換,支持語義級別的翻譯。本文就來簡單介紹一下該庫的編譯以及python、C++和JAVA分別如何調用DLL進行轉換。並記錄一些使用過程當中踩過的坑。html

1.編譯DLL

咱們首先編譯獲得opencc的dll動態庫。java

CMake Command line

當前工做目錄生成VS工程文件python

cmake -G "Visual Studio 14 2015" -D CMAKE_INSTALL_PREFIX="D:/Projects/Cnblogs/Alpha Panda/OpenCC" ../opencc-ver.1.0.5ios

編譯工程文件shell

cmake --build ./ --config RelWithDebInfo --target installjson

使用命令行build工程文件。windows

CMake - Gui

下載最新版本CMake,配置工程代碼generator,本文使用的Visual Studio 14 2015。ide

Configure操做過程當中須要正確的設置安裝路徑,這個安裝路徑決定了dll會去哪一個目錄下去讀取簡繁轉換的配置文件。函數

CMake中的變量CMAKE_INSTALL_PREFIX控制安裝路徑,其默認值工具

  • UNIX:/usr/local
  • Windows:c:/Program Files/${PROJECT_NAME}

這裏設置爲:D:/Projects/Cnblogs/Alpha Panda/OpenCC

接着通過generate生成VS工程文件。

Visual Studio

使用CMake command line或者cmake-gui獲得VS工程文件。

打開VS工程,這裏咱們只編譯工程libopencc獲得dll文件。爲了後續便於使用attach功能調試dll文件,最好將工程配置爲RelWithDebInfo。

工程libopencc的屬性配置尋找一個宏變量:PKGDATADIR(PKGDATADIR="D:/Projects/Cnblogs/Alpha Panda/OpenCC/share//opencc/")

這個宏變量是源代碼根目錄下面的CMakeLists.txt中設置的,感興趣的話能夠簡單瞭解一下這個變量的設置過程:

CMAKE_INSTALL_PREFIX = D:/Projects/Cnblogs/Alpha Panda/OpenCC set (DIR_PREFIX ${CMAKE_INSTALL_PREFIX}) set (DIR_SHARE ${DIR_PREFIX}/share/) set (DIR_SHARE_OPENCC ${DIR_SHARE}/opencc/) -DPKGDATADIR="${DIR_SHARE_OPENCC}"

簡繁轉換的配置文件必需要放到這個目錄下。

2.使用python

利用上面編譯獲得的libopencc的DLL文件,經過python調用來進行字體的轉換:(下面的代碼改編自 OpenCC 0.2)

# -*- coding:utf-8 -*-

import
os import sys from ctypes.util import find_library from ctypes import CDLL, cast, c_char_p, c_size_t, c_void_p __all__ = ['CONFIGS', 'convert'] if sys.version_info[0] == 3: text_type = str else: text_type = unicode _libcfile = find_library('c') or 'libc.so.6' libc = CDLL(_libcfile, use_errno=True) _libopenccfile = os.getenv('LIBOPENCC') or find_library('opencc') if _libopenccfile: libopencc = CDLL(_libopenccfile, use_errno=True) else: #libopencc = CDLL('libopencc.so.1', use_errno=True) # _libopenccfile = find_library(r'G:\opencc\build\src\Release\opencc') # 貌似不能使用相對路徑? cur_dir = os.getcwd() lib_path = os.path.join(cur_dir, 'T2S_translation_lib', 'opencc') lib_path = './share/opencc' libopencc = CDLL(lib_path, use_errno=True) libc.free.argtypes = [c_void_p] libopencc.opencc_open.restype = c_void_p libopencc.opencc_convert_utf8.argtypes = [c_void_p, c_char_p, c_size_t] libopencc.opencc_convert_utf8.restype = c_void_p libopencc.opencc_close.argtypes = [c_void_p]
libopencc.opencc_convert_utf8_free.argstypes = c_char_p CONFIGS
= [ 'hk2s.json', 's2hk.json', 's2t.json', 's2tw.json', 's2twp.json', 't2s.json', 'tw2s.json', 'tw2sp.json', 't2tw.json', 't2hk.json', ] class OpenCC(object): def __init__(self, config='t2s.json'): self._od = libopencc.opencc_open(c_char_p(config.encode('utf-8'))) def convert(self, text): if isinstance(text, text_type): # use bytes text = text.encode('utf-8') retv_i = libopencc.opencc_convert_utf8(self._od, text, len(text)) if retv_i == -1: raise Exception('OpenCC Convert Error') retv_c = cast(retv_i, c_char_p) value = retv_c.value # 此處有問題? # libc.free(retv_c) libopencc.opencc_convert_utf8_free(retv_i)
return value def __del__(self): libopencc.opencc_close(self._od) def convert(text, config='t2s.json'): cc = OpenCC(config) return cc.convert(text)

 上面的這段代碼能夠當作離線工具來進行文件的轉換,並無線上運行時被調用驗證過,可能存在內存泄露,僅供參考。

關於python如何調用DLL文件,能夠參考個人另外一篇文章:Python使用Ctypes與C/C++ DLL文件通訊過程介紹及實例分析

使用示例:

origin_text = u'(理髮 vs 發財),(鬧鐘 vs 一見傾心),後來'.encode('utf-8') s2t_1 = convert(origin_text, 's2t.json') t2s_1 = convert(s2t_1, 't2s.json') print t2s_1.decode('utf-8') print s2t_1.decode('utf-8') print origin_text == t2s_1 ============================================
>>>(理髮 vs 發財),(鬧鐘 vs 一見傾心),後來 >>>(理髮 vs 發財),(鬧鐘 vs 一見鍾情),後來 >>>True

3.使用C++

 下面咱們來使用C++來演示一下如何使用OpenCC進行繁簡字體的轉換。

因爲opencc傳入的翻譯文本編髮方式爲utf-8。所以須要對待翻譯文本進行編碼轉換。

string GBKToUTF8(const char* strGBK) { int len = MultiByteToWideChar(CP_ACP, 0, strGBK, -1, NULL, 0); wchar_t* wstr = new wchar_t[len + 1]; memset(wstr, 0, len + 1); MultiByteToWideChar(CP_ACP, 0, strGBK, -1, wstr, len); len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); char* str = new char[len + 1]; memset(str, 0, len + 1); WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL); string strTemp = str; if (wstr) delete[] wstr; if (str) delete[] str; return strTemp; } string UTF8ToGBK(const char* strUTF8) { int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0); wchar_t* wszGBK = new wchar_t[len + 1]; memset(wszGBK, 0, len * 2 + 2); MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len); len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL); char* szGBK = new char[len + 1]; memset(szGBK, 0, len + 1); WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL); string strTemp(szGBK); if (wszGBK) delete[] wszGBK; if (szGBK) delete[] szGBK; return strTemp; }

這是在windows平臺上兩個很是有用的UTF8和GBK編碼互轉函數。

方便起見咱們直接在opencc中添加一個新的工程,命名爲Translation。

#include <cstdio>
#include <cstdlib>
#include
<iostream> #include <string> #include <windows.h> #include <fstream> #include "../OpenCC-ver.1.0.5/src/opencc.h" //using namespace std; using std::cout; using std::endl; using std::string; #define OPENCC_API_EXPORT __declspec(dllimport) OPENCC_API_EXPORT char* opencc_convert_utf8(opencc_t opencc, const char* input, size_t length); OPENCC_API_EXPORT int opencc_close(opencc_t opencc); OPENCC_API_EXPORT opencc_t opencc_open(const char* configfilename);
OPENCC_API_EXPORT void opencc_convert_utf8_free(char* str);
#pragma comment(lib, "../Build/src/RelWithDebInfo/opencc.lib")
string GBKToUTF8(const char* strGBK); string UTF8ToGBK(const char* strUTF8); int main() { char* trans_conf = "s2t.json"; char* trans_res = nullptr; string gbk_str, utf8_str, res; // read from file and write translation results to file std::ifstream infile; std::ofstream outfile; infile.open("infile.txt", std::ifstream::in); outfile.open("outfile.txt", std::ifstream::out); // open the config file opencc_t conf_file = opencc_open(trans_conf); while (infile.good()) { infile >> gbk_str; utf8_str = GBKToUTF8(gbk_str.c_str()); std::cout << gbk_str << "\n"; trans_res = opencc_convert_utf8(conf_file, utf8_str.c_str(), utf8_str.length()); cout << UTF8ToGBK(trans_res) << endl; outfile << trans_res << endl;
opencc_convert_utf8_free(trans_res); //
delete[] trans_res; trans_res = nullptr; } infile.close(); outfile.close(); opencc_close(conf_file); conf_file = nullptr; system("pause"); return 0; }

 上面的這段C++代碼能夠從infile.txt中讀取簡體中文,而後將翻譯結果寫入到outfile.txt文件中。

3.使用JAVA

這裏給出一個使用JNA調用DLL的方案:

package com.tvjody; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.nio.charset.StandardCharsets; import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Platform; import com.sun.jna.Pointer; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.io.FileOutputStream; public class JNA_CALL { public interface openccDLL extends Library{ openccDLL Instance = (openccDLL) Native.load( (Platform.isWindows() ? "opencc" : "libc.so.6"), openccDLL.class); // void* opencc_open(const char* configfilename);
 Pointer opencc_open(String configfilename); // int opencc_close(void* opencc);
        int opencc_close(Pointer opencc); // void opencc_convert_utf8_free(char* str);
        void opencc_convert_utf8_free(String str); // char* opencc_convert_utf8(opencc_t opencc, const char* input, size_t length)
        String opencc_convert_utf8(Pointer opencc, String input, int length); } public static void writeToFile(String utf8_str) throws IOException { Writer out = new OutputStreamWriter(new FileOutputStream("out.txt"), StandardCharsets.UTF_8); out.write(utf8_str); out.close(); } public static String readFromFile() throws IOException { String res = ""; Reader in = new InputStreamReader(new FileInputStream("in.txt"), StandardCharsets.UTF_8); try(BufferedReader read_buf = new BufferedReader(in)){ String line; while((line = read_buf.readLine()) != null) { res += line; } read_buf.close(); } return res; } public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException { System.setProperty("jna.library.path", "D:\\Projects\\Open_Source\\OpwnCC\\Build(x64)\\src\\RelWithDebInfo"); Pointer conf_file = openccDLL.Instance.opencc_open("s2t.json"); try { String res_utf8 = readFromFile(); System.out.println("From: " + res_utf8); byte[] ptext = res_utf8.getBytes("UTF-8"); // String utf8_str = new String(res_utf8.getBytes("GBK"), "UTF-8");
            String trans_res = openccDLL.Instance.opencc_convert_utf8(conf_file, res_utf8, ptext.length); System.out.println("To:" + trans_res); // String trans_gbk = new String(trans_res.getBytes("UTF-8"), "GBK");
 writeToFile(trans_res); openccDLL.Instance.opencc_convert_utf8_free(trans_res); } catch (IOException e) { // TODO Auto-generated catch block
 e.printStackTrace(); } openccDLL.Instance.opencc_close(conf_file); } }

json配置文件的路徑有DLL決定,除了上面手動設置dll文件的路徑以外,還能夠將dll文件放置到bin目錄下。上面使用的是jna-5.2.0。

4.填坑指南

實際上使用時會遇到N多的問題,這裏僅列出一些注意事項,其實下面的有些問題具備一些廣泛性,較爲有價值。

DLL讀取配置文件路徑 

工程中讀取json配置文件的路徑是用宏變量定義,而Cmake的變量MAKE_INSTALL_PREFIX決定了工程中配置文件的宏變量,也決定了DLL被調用時讀取配置文件的路徑。路徑中最好使用‘/’,而不是‘\’。

OCD文件的生成

進行簡繁體文字轉換的過程須要讀取json和對應的ocd文件,ocd文件是由工程Dictionaries生成的,該工程又依賴與opencc_dict的opencc.exe程序。

實際使用時發現最新的1.0.5版本好像有一個錯誤,須要將上面的一個函數聲明,改成下面的函數聲明,否者會有一個連接錯誤。

void ConvertDictionary(const string inputFileName, const string outputFileName, const string formatFrom, const string formatTo); OPENCC_EXPORT void ConvertDictionary(const string inputFileName, const string outputFileName, const string formatFrom, const string formatTo);

此外,data目錄下生成的全部ocd文件須要和json配置文件放到同一個目錄下,32位和64位的ocd文件也不要混用。

32位or64位

在使用java調用dll的時候要特別的注意,若是是64位的JDK,必定要編譯64位的dll和全部的ocd文件。否者下面的這個錯誤會一直纏着你:

java.lang.UnsatisfiedLinkError: %1 不是有效的 Win32 應用程序

從兩方面簡述一下如何正確的生成64位的opencc工程文件。

使用cmake-gui configure直接指定64位的編譯器,選擇Visual Studio 14 2015 Win64,而不是Visual Studio 14 2015。

若是當前的工程爲32位的工程,能夠在VS中經過configuration manager來手動配置爲x64位。將32位工程手動改成64位工程可能會有許多的坑,好比:

fatal error LNK1112: module machine type 'x64' conflicts with target machine type 'X86'

下面列舉出一些解決方案:

  1. Check your properties options in your linker settings at: Properties > Configuration Properties > Linker > Advanced > Target Machine. Select MachineX64 if you are targeting a 64 bit build, or MachineX86 if you are making a 32 bit build.
  2. Select Build > Configuration Manager from the main menu in visual studio. Make sure your project has the correct platform specified. It is possible for the IDE to be set to build x64 but an individual project in the solution can be set to target win32. So yeah, visual studio leaves a lot of rope to hang yourself, but that's life.
  3. Check your library files that they really are of the type of platform are targeting. This can be used by using dumpbin.exe which is in your visual studio VC\bin directory. use the -headers option to dump all your functions. Look for the machine entry for each function. it should include x64 if it's a 64 bit build.
  4. In visual studio, select Tools > Options from the main menu. select Projects and Solutions > VC++ Directories. Select x64 from the Platform dropdown. Make sure that the first entry is: $(VCInstallDir)\bin\x86_amd64 followed by $(VCInstallDir)\bin.
  5. Check in Visual Studio:Project Properties -> Configuration Properties -> Linker -> Command line."Additional Options" should NOT contain /machine:X86.I have such key, generated by CMake output: CMake generated x86 project, then I added x64 platform via Configuration Manager in Visual Studio 2010 - everything was create fine for new platform except linker command line, specified /machine:X86 separately.

編碼問題

因爲opencc內部處理字符串均使用的是utf-8編碼,所以須要進行編解碼的處理才能正確的調用DLL中的接口。

廣義上來講,所謂亂碼問題就是解碼方式和編碼方式不一樣致使的。這是一個很大的話題,這裏不深刻討論,有興趣能夠參考我另外一篇博文python編碼問題分析,應該能對你有所啓發。

在win10上使用cmake生成VS工程。編譯的時候會遇到一個有趣的問題就是中文環境下utf-8文件中的部分漢字標點,居然會有亂碼,以下:

1>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.5\src\PhraseExtract.cpp(32): error C3688: invalid literal suffix ''; literal operator or literal operator template 'operator ""銆' not found 1>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.5\src\PhraseExtract.cpp(32): error C3688: invalid literal suffix ''; literal operator or literal operator template 'operator ""錛' not found 1>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.5\src\PhraseExtract.cpp(32): error C3688: invalid literal suffix ''; literal operator or literal operator template 'operator ""鈥' not found 1>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.5\src\PhraseExtract.cpp(32): error C2001: newline in constant 1>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.5\src\PhraseExtract.cpp(33): error C3688: invalid literal suffix ''; literal operator or literal operator template 'operator ""鈥' not found 1>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.5\src\PhraseExtract.cpp(33): error C3688: invalid literal suffix ''; literal operator or literal operator template 'operator ""錛' not found 1>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.5\src\PhraseExtract.cpp(33): error C3688: invalid literal suffix ''; literal operator or literal operator template 'operator ""銆' not found
View Code

文本編碼對應關係(Visual Studio 2015 VS Notepad++):
file->Advance Save Options:

Chinese Simplified (GB2312) - Codepage 936 <==> GBK Unicode (UTF-8 with signature) - Codepage 65001 <==> Encoding in UTF-8 BOM Unicode (UTF-8 without signature) - Codepage 65001 <==> Encoding in UTF-8

將上面文件的編碼方式從Unicode (UTF-8 without signature) - Codepage 65001改成 Chinese Simplified (GB2312) - Codepage 936便可。

python的編碼轉換比較簡單,C++的轉換接口上面已經列出,至於java,建議將java文件和數據文件的編碼方式均改成utf-8,使用String utf8_str = new String(gbk_str.getBytes("UTF-8"), "UTF-8")這種轉碼方式可能帶來一些奇怪的問題。

DLL與EXE局部堆問題

有一點須要注意,要確保正確釋放DLL中使用new在堆中分配的內存空間,這裏必需要使用DLL中提供的釋放堆空間的函數,而不要在主程序中直接使用delete或者delete[].

簡單的解釋就是EXE和DLL分別有各自的局部堆,new和delete分別用於分配和釋放各自局部堆上的空間,使用EXE中的delete來釋放DLL中new的局部堆內存可能會致使錯誤,這個和具體的編譯器有關。

上面的C++代碼在EXE中delete DLL分配的空間,是一種未定義行爲。

DLL調試技巧

實際使用尤爲是使用不一樣語言對opencc.dll進行調用的時候會碰到不少問題,這時最好的辦法就是使用VS的Attach To Process對DLL進行斷點跟進。

對於python調用DLL,能夠先打開一個python shell或者IDLE環境並在其中調用一下DLL,以後在VS中attach到對應的python進程,不要直接attach到sublime等IDE程序,由於IDE中運行的python程序而不是IDE自己直接調用DLL文件。

對於java而言,一樣不能使用vs直接attach到Eclipse等IDE上。這裏有一個技巧,就是在調用到DLL接口前的java代碼加上一個斷點,而後會在VS進程列表中看到一個javaw.exe程序,attach到這個程序後,接着運行java程序就會進入DLL中的斷點了。

小結

若是可以耐心的瀏覽一遍,相信會發現這是一篇採坑覆盤。可以從頭開始獨立的一步一步解決掉遇到的每個問題,相信必定會別有一番滋味。但願本篇博文能在須要的時候對你有所幫助。

原文出處:https://www.cnblogs.com/yssjun/p/10447017.html

相關文章
相關標籤/搜索