C#调用C++ DLL走过的坑

最近在研究中文分词中的歧义消除方案,项目是用C#编写的,我用C++写了个分词工具,想要整合进这个C#项目中,就打算用C#来调C++的dll,没成想这里面的坑竟然这么多。。。

ROUND 1 当C++接口的返回值是string类型时

C++对外接口的声明

extern "C" _declspec(dllexport) std::string __cdecl Segmentor(std::string);

这是一个分词的接口,可以看到参数和返回值类型都是string类型

C#的调用方式

[DllImport("xxxxx.dll", EntryPoint = "Segmentor", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern string Segmentor(string conten);

所以就想当然的用C#的string类型来接收C++的string结果,结果崩溃了。。。原来C#的string和C++的string是不一样的东西,因此得换种方法来调用

ROUND 2 用const char*代替string

好吧,既然string不能用那就换一种方式,由于传入的是窄字节字符串,因此C++可以用const char来代替,如果是宽字节字符串可以用*const wchat_t* **

C++对外接口的声明

extern "C" _declspec(dllexport) const char* __cdecl Segmentor(const char*);

C#的调用方式

C#在调用const char的时候要注意,最好不要用C#的string来接收,因为const char是个指针,所以最好也用指针来接收,所以就用C#的IntPtr**类型来接收

[DllImport("xxxxx.dll", EntryPoint = "Segmentor", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Segmentor(string conten);

此外,要想把获得的IntPtr结果转化为string类型,需要用Marshal.PtrToStringAnsi方法进行转化,

截至目前为止,一切都很美好,能够获得结果了,但是好景不长,又遇到了新的问题。。。

ROUND 3 用BSTR代替const char*

在返回值长度较小时(短文本),const char* 完全没有问题,但是文本过长偶尔会出现字符乱码的情况,那是因为C++中const char* 指针指向的时一个临时对象的地址,调用完之后这个临时对象就析构了,从而用C#的IntPtr类型来接收这个指针就会出现问题

字符串的长度可能互不相同,跨COM边界传输特定的字符串时,需要确定它的长度,而且字符串有时需要分配内存,因此需要找到一个所有COM兼容语言都可以使用的字符串,所以BSTR类型闪亮登场

C++对外接口的声明

extern "C" _declspec(dllexport) BSTR __cdecl Segmentor(const char*);

C#的调用方式

[DllImport("xxxxx.dll", EntryPoint = "Segmentor", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string Segmentor(string conten);

注意,C#调用BSTR类型时需要注明**[return: MarshalAs(UnmanagedType.BStr)]**特性

Author: Hongyi Guo
Link: https://guohongyi.com/2020/06/16/C-调用C-dll走过的坑/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.