OpenCC的编译与多语言使用
OpenCC全称Open Chinese Convert,是一个Github上面的开源项目,主要用于简繁体汉字的转换,支持语义级别的翻译。本文就来简单介绍一下该库的编译以及python、C++和JAVA分别如何调用DLL进行转换。并记录一些使用过程中踩过的坑。
1.编译DLL
我们首先编译得到opencc的dll动态库。
CMake Command line
当前工作目录生成VS工程文件
cmake -G "Visual Studio 14 2015" -D CMAKE_INSTALL_PREFIX="D:/Projects/Cnblogs/Alpha Panda/OpenCC" ../opencc-ver.1.0.5
编译工程文件
cmake --build ./ --config RelWithDebInfo --target install
使用命令行build工程文件。
CMake - Gui
下载最新版本CMake,配置工程代码generator,本文使用的Visual Studio 14 2015。
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, , strGBK, -, NULL, );
wchar_t* wstr = new wchar_t[len + ];
memset(wstr, , len + );
MultiByteToWideChar(CP_ACP, , strGBK, -, wstr, len);
len = WideCharToMultiByte(CP_UTF8, , wstr, -, NULL, , NULL, NULL);
char* str = new char[len + ];
memset(str, , len + );
WideCharToMultiByte(CP_UTF8, , wstr, -, 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, , strUTF8, -, NULL, );
wchar_t* wszGBK = new wchar_t[len + ];
memset(wszGBK, , len * + );
MultiByteToWideChar(CP_UTF8, , strUTF8, -, wszGBK, len);
len = WideCharToMultiByte(CP_ACP, , wszGBK, -, NULL, , NULL, NULL);
char* szGBK = new char[len + ];
memset(szGBK, , len + );
WideCharToMultiByte(CP_ACP, , wszGBK, -, 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 ;
}
上面的这段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'
下面列举出一些解决方案:
- 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.
- 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.
- 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.
- 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.
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文件中的部分汉字标点,竟然会有乱码,如下:
>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.\src\PhraseExtract.cpp(): error C3688: invalid literal suffix '銆'; literal operator or literal operator template 'operator ""銆' not found
>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.\src\PhraseExtract.cpp(): error C3688: invalid literal suffix '锛'; literal operator or literal operator template 'operator ""锛' not found
>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.\src\PhraseExtract.cpp(): error C3688: invalid literal suffix '鈥'; literal operator or literal operator template 'operator ""鈥' not found
>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.\src\PhraseExtract.cpp(): error C2001: newline in constant
>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.\src\PhraseExtract.cpp(): error C3688: invalid literal suffix '鈥'; literal operator or literal operator template 'operator ""鈥' not found
>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.\src\PhraseExtract.cpp(): error C3688: invalid literal suffix '锛'; literal operator or literal operator template 'operator ""锛' not found
>D:\Projects\Open_Source\OpwnCC\OpenCC-ver.1.0.\src\PhraseExtract.cpp(): error C3688: invalid literal suffix '銆'; literal operator or literal operator template 'operator ""銆' not found
文本编码对应关系(Visual Studio 2015 VS Notepad++):
file->Advance Save Options:
Chinese Simplified (GB2312) - Codepage <==> GBK
Unicode (UTF- with signature) - Codepage <==> Encoding in UTF- BOM
Unicode (UTF- without signature) - Codepage <==> Encoding in UTF-
将上面文件的编码方式从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中的断点了。
小结
如果能够耐心的浏览一遍,相信会发现这是一篇采坑复盘。能够从头开始独立的一步一步解决掉遇到的每一个问题,相信一定会别有一番滋味。希望本篇博文能在需要的时候对你有所帮助。
OpenCC的编译与多语言使用的更多相关文章
- 最短的可通过编译的C语言程序
要求:写一个最短的,能通过编译的C语言程序,但不要求能正确运行. 一般人首先想到的是除0.即 int main() { /; } 除0会引发SIGFPE信号(浮点异常). 我们也可以删掉return, ...
- Atitit.dart语言的特性 编译时js语言大总结
Atitit.dart语言的特性 编译时js语言大总结 1. 原型环境1 1.1. Dart可以编译js3 2. 第二个期待的理由是Dart的语言特性,没有什么特别特性好像,类似java c#一小时 ...
- 关于JAVA,特点,历史,编译式的语言&解释式的语言,什么是java?JDK?DOS?一次编译到处运行原理。
1.java语言的特点: 简单的:面向对象的:跨平台(操作系统)的(一次编译,到处运行):高性能的: 2.类名的首字母大写,方法小写: 3.历史: java2(即java),为什么加个2呢?1998年 ...
- x86汇编反编译到c语言之——(1)表达式求值及赋值语句
一. 反编译一种可能的实现方式 我们的目的是将多种平台的汇编如x86,ARM,6502反编译为c语言,所以实现时先将多种汇编转化为 特定虚拟机汇编语言,然后只需要将虚拟机汇编语言反编译为c语言.其中多 ...
- x86汇编反编译到c语言之——(2)if语句
一. 测试的C语句及编译后的x86汇编代码 int a; int b; int main(void) { int c; if (c) a = 4; else b = 5; return 0; } 1 ...
- Ubuntu的多文件编译以及c语言的数组、函数
闲言少叙,直接切入主题. 一.Linux下的多文件编译(Ubuntu系统). 对于小程序来说,往往一个.c文件就足够了,里面包含了各种主函数和功能函数,以及函数的声明等等.但是这样的弊端主要有两点,一 ...
- linux下怎么编译运行C语言程序?
linux下的C语言编译器是gcc,C++的编译器是g++. linux下编程可以使用编辑器vi或vim,建议使用vim,因为它有语法高亮显示.程序编写好后,假设你的程序名为test.c,可以使用gc ...
- apk反编译(2)smali语言及文件
Smali语言是Davlik的虚拟机使用的一种语言,用toolapk反编译apk后,可以见到大量的.smali文件. 可以按照smali语法对其修改,然后重新生成一个未签名的apk. 下面是一个示例: ...
- “Java是编译执行的语言”这句话对吗?
现在让你谈谈对Java平台的理解,你是否会感觉内容过于庞大?这个问题是比较宽泛的,Java发展到现在已经不仅仅是语言这么简单了,Java平台涉及的,包括但不仅限于下面提到的这些内容: Java语言本身 ...
随机推荐
- BZOJ_1712_[Usaco2007 China]Summing Sums 加密_矩阵乘法
BZOJ_1712_[Usaco2007 China]Summing Sums 加密_矩阵乘法 Description 那N只可爱的奶牛刚刚学习了有关密码的许多算法,终于,她们创造出了属于奶牛 ...
- 【Unity游戏开发】Lua中的os.date和os.time函数
一.简介 最近马三在工作中经常使用到了lua 中的 os.date( ) 和 os.time( )函数,不过使用的时候都是不得其解,一般都是看项目里面怎么用,然后我就模仿写一下.今天正好稍微有点空闲时 ...
- Java基础-方法重载和方法重写的区别
什么是java方法重载 (1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型. 重载Overloading是一个类中多态性的一种表现. (2) ...
- 【机器学习基础】对 softmax 和 cross-entropy 求导
目录 符号定义 对 softmax 求导 对 cross-entropy 求导 对 softmax 和 cross-entropy 一起求导 References 在论文中看到对 softmax 和 ...
- java一个大接口拆用多线程方式拆分成多个小接口
问题引入 目的:我们的接口A 分别调用了a1 a2 a3 三个接口,最终返回值是 a1的返回值+a2的返回值+a3的返回值 如果同步执行 a1 a2 a3 然后结果相加 很慢 . 如果异步执行 无法 ...
- 长沙4月21日开发者大会暨.NET社区成立大会活动纪实
活动总结 2019年4月21日是一个斜风细雨.微风和煦的美好日子,由长沙.NET技术社区.腾讯云云加社区.微软Azure云技术社区.中国.NET技术社区.长沙柳枝行动.长沙互联网活动基地(唐胡子俱乐部 ...
- ASP.NET Core 实现带认证功能的Web代理服务器
引言 最近在公司开发了一个项目,项目部署架构图如下: 思路 如图中文本所述,公司大数据集群不允许直接访问外网,需要一个网关服务器代理请求,本处服务器A就是边缘代理服务器的作用. 通常技术人员最快捷的思 ...
- Vue.js组件间通信方式总结
平时在使用Vue框架的业务开发中,组件不仅仅要把模板的内容进行复用,更重要的是组件之间要进行通信.组件之间通信分为三种:父-子:子-父:跨级组件通信.下面,就组件间如何通信做一些总结. 1.父组件到子 ...
- 已实现乐观锁功能,FreeSql.DbContext 准备起航
上回说到 FreeSql.DbContext 的规则,以及演示它的执行过程,可惜当时还不支持"乐观锁",对于更新数据来讲并不安全. FreeSql 核心库 v0.3.27 已提供乐 ...
- Caffe源码理解2:SyncedMemory CPU和GPU间的数据同步
目录 写在前面 成员变量的含义及作用 构造与析构 内存同步管理 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 在Caffe源码理解1中介绍了Blob类,其中的数据成 ...