终于讲完了后端代码,要涉及到前端的内容了。作为C++写的后台,而在Unity中是用C#中作为脚本语言。因此后台的代码想在前端进行调用,需要讲C++部分打包成Dll的动态链接库,约定好对应的格式,即可在C#中调用。
首先需要了解C++/C#之前的主要区别,两种语言在语法上和关键字上很相似。这里体现的最主要的区别C#是托管堆代码,C++是非托管堆代码。在数据分配上C++每当用new等关键字申请空间时,编译器会在堆上为其分配内存,在使用完成后需要delete等回收空间,尤其注意在局部函数中申请的空间,一旦丢失了指向申请内存的指针,那么就会造成内存泄漏,即申请的内存空间和数据依然放在堆中,但是再也没办法使用和回收它(因为失去了地址去定位),当程序一直运行时,无效内存会越来越多,最终导致内存溢出崩溃(这就是我不怎么习惯用C++的一种原因,虽然现在已经有了auto_ptr等) 。
而C#中的托管堆代码即将内存管理交给了OS和编译器,当一段内存没有被任何指针或变量引用后,就会由system.GC()自动回收 。基于这两种语言的特性,在C++中设计函数参数类型和数据交换时要考虑兼容性。
这里列举一些常用的数据类型对应:
Windows Data Type | .NET Data Type |
BOOL, BOOLEAN | Boolean or Int32 |
BSTR | String |
BYTE | Byte |
CHAR | Char |
DOUBLE | Double |
DWORD | Int32 or UInt32 |
FLOAT | Single |
HANDLE (and all other handle types, such as HFONT and HMENU) | IntPtr, UintPtr or HandleRef |
HRESULT | Int32 or UInt32 |
INT | Int32 |
LANGID | Int16 or UInt16 |
LCID | Int32 or UInt32 |
LONG | Int32 |
LPARAM | IntPtr, UintPtr or Object |
LPCSTR | String |
LPCTSTR | String |
LPCWSTR | String |
LPSTR | String or StringBuilder* |
LPTSTR | String or StringBuilder |
LPWSTR | String or StringBuilder |
LPVOID | IntPtr, UintPtr or Object |
LRESULT | IntPtr |
SAFEARRAY | .NET array type |
SHORT | Int16 |
TCHAR | Char |
UCHAR | SByte |
UINT | Int32 or UInt32 |
ULONG | Int32 or UInt32 |
VARIANT | Object |
VARIANT_BOOL | Boolean |
WCHAR | Char |
WORD | Int16 or UInt16 |
WPARAM | IntPtr, UintPtr or Object |
这里只用了一些比较常用的。比如整数类型DWORD(C++)对应uint(C#),字符串传值时使用char*(C++)对应string/stringBuilder(C#)。
其次是在C++中导出Dll,用vs可以很简单的创建一个C++Dll工程。
然后在.cpp文件写好函数,这里拿项目中需要得到一个string的哈希值做例子
DWORD Hash4(const string& tar) //计算哈希值函数
{
DWORD64 ans = 0;
for (int i = 0; i < tar.length(); i++) {
ans = ((ans >> 8) & 0xf) ^ ((ans << 4) ^ tar[i]);
}
return ans & 0xfff;
}
DWORD GetHash(char* tar)
{
string temp = tar;
return Hash4(temp);
}
在.h中补充函数原型,添加上extern "C" _declspec(dllimport)语句
#ifdef DL1_API
#else
#define DL1_API extern "C" _declspec(dllimport) //交给客户端使用时
#endif
DL1_API DWORD GetHash(char* tar);
最后在C#中使用[DllImport]导入, static extern 并对上函数原型参数列表,中间的名称为打包出来的Dll文件名,注意如果是在unity中想使用dll,需要放在对应的assert/plugin文件夹中,即可加载dll数据库。只是普通的C#控制台程序和执行文件同目录即可
注意制力用了char*与c#中的string交换,c++中的string不论是传出还是传入数据都是和C#对不上的,所以需要用到char*,中间还会涉及到一些编码问题。而c#中string和stringBuilder都可以在C++中被接受到。
[DllImport("Dll2")]
public static extern uint GetHash(string tar); //需要对应上参数类型和函数名
如果需要C#传入一个字符串,在C++中接收并进行修改,然后再由C#中得到结果,则需要用到stringBuilder,stringBuilder与string最大的不同在于stringBuilder可以预先分配空间,能够被多次的修改,并且不产生新的未使用对象(而string拷贝多了都是创建一个新的对象,非常耗时间)。在传值的时候建议使用stringBuilder,这里再拿程序中用到的搜索功能样例。
C++中函数原型:
void articleInfo(char* 文章名, char* 数据库地址, char* 结果);//输入文章名,返回文章的所有相关信息
/// <summary>
/// 输入文章名,返回文章的所有相关信息
/// </summary>
/// <param name="_articleName">文章名</param>
/// <param name="_saveUrl">文件保存路径</param>
/// <param name="pt">对外输出结果</param>
void articleInfo(char* _articleName, char* _saveUrl, char* pt)
{
string ret;
... //文件计算过程
//将结果与c#对接
if (ret.length() > 0)
{
strcpy_s(pt, ret.length() + 1, ret.c_str()); //拷贝数据输出
}
}
C#中:
using System.Text;
StringBuilder stringBuilder = new StringBuilder(50000);
[DllImport("Dll1")]
public static extern void articleInfo(String articleName, String saveUrl, StringBuilder pt);
不过如果需要传输的字符串内容过多,可以考虑c++先写入磁盘文件,在通过C#中打开文件再IO解决(实际上本程序中的模糊搜索实现方式采用的就是这样,面对一些单词几百万的结果,C++会存储在文件中再通过C# IO读取)
至此,一个简单的Dll程序就这样设计完成,这里展示下程序中实际用到的所有接口。
class DLL
{
public Thread thread;
StringBuilder stringBuilder = new StringBuilder(50000);
[DllImport("Dll1")]
public static extern void articleInfo(String articleName, String saveUrl, StringBuilder pt);
[DllImport("Dll1")]
public static extern void main_Reader(String authorName, String saveUrl, StringBuilder pt);
[DllImport("Dll1")]
public static extern bool CheckFilePathCorrect_Compatible(string filePath, StringBuilder refS);
[DllImport("Dll1")]
public static extern bool CheckFilePathCorrect(string filePath);
[DllImport("Dll1")]
public static extern void CoAuthor(string search_name, string _saveUrl, StringBuilder pt);
[DllImport("Dll1")]
public static extern void initial_fuzzy(string filePath);
[DllImport("Dll1")]
public static extern int fuzzy_search(string filePath, uint threadNum, string content);
[DllImport("Dll1")]
public static extern void release_fuzzy_searcher();
[DllImport("Dll1")]
public static extern void Keyword_Sort(uint year, string filePath); //年份排序
[DllImport("Dll2")]
public static extern uint GetHash(string tar); //需要对应上参数类型和函数名
[DllImport("Dll2")]
public static extern bool ChechFilePathCorrect_dblp(string saveUrl);
[DllImport("Dll1")]
public static extern bool initial_readers(uint totalThread, uint maxThread, bool debugText, string saveUrl);//建库
[DllImport("Dll1")]
public static extern bool Author_Sort(string saveUrl); //作者排序
[DllImport("Dll1")]
public static extern int Full_word_match_fuzzy_search(string filePath, uint threadNum, string content);
[DllImport("Dll1")]
public static extern uint Format_Hash4(string tar); //哈希值
//C++中的原型
/*
using data_initial::Hash4;//通用字符串Hash函数 DWORD Hash4(const string& 需要hash的字符)
using data_initial::initial_readers;//数据库初始化建库函数 bool initial_readers(const DWORD 总线程数(建议值:16), DWORD 最多同时运行线程数(建议值:4), bool 是否显示文本(建议值:False), char* 数据库地址)
using Keyword_Sorting::Keyword_Sort;//年份热搜排序 void Keyword_Sort(DWORD 年份, char* 数据库地址)
using Author_Sorting::Author_Sort;//作者发文量排序 bool Author_Sort(char* 数据库地址)
using Elements_Searcher::articleInfo;//输入文章名,返回文章的所有相关信息 void articleInfo(char* 文章名, char* 数据库地址, char* 结果)
using Elements_Searcher::CheckFilePathCorrect;//检查文件路径是否正确 bool CheckFilePathCorrect(char* 数据库地址)
using Elements_Searcher::CheckFilePathCorrect_Compatible;//检查文件路径是否正确(兼容模式) bool CheckFilePathCorrect_Compatible(char* 数据库地址)
using Elements_Searcher::main_Reader;//输入作者名字,返回作者发布的所有文章 void main_Reader(char* 作者名字, char* 数据库地址, char* 结果)
using Elements_Searcher::CoAuthor;//输入作者名字,返回所有合作作者 void CoAuthor(char* 作者名字, char* 数据库地址, char* 结果)
using Fuzzy_Searcher::initial_fuzzy;//初始化模糊搜索 void initial_fuzzy(char* 数据库地址)
using Fuzzy_Searcher::fuzzy_search;//模糊搜索 bool fuzzy_search(char* 数据库地址, DWORD 线程数, char* 目标内容)
using Fuzzy_Searcher::release_fuzzy_searcher;//释放模糊搜索的资源(需要重新初始化) void release_fuzzy_searcher()
*/
}