用c#调用c++ dll,有时会造成内存冲突
本文关键字:内存 冲突 调用 c++ dll | 更新日期: 2023-09-27 18:04:04
我有c++代码的定义
#define TEST_DLL_API extern "C" __declspec(dllexport)
TEST_DLL_API void __cdecl create(const char* sString, const char* sNext, int level, char* response ) ;
TEST_DLL_API void __cdecl result(const char* sString, char* response) ;
对于我的c#代码中的前几次调用,它工作得很好,但它导致第五次内存冲突。
[DllImport("TEST_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, SetLastError = true)]
public static extern void create(string sString, string sNext, int level, StringBuilder response);
[DllImport("TEST_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, SetLastError = true)]
public static extern void result(string sString, StringBuilder response);
我认为这是不释放堆栈内存的问题,但我不知道如何解决它
首先,忘记c#,只考虑如何从另一个C/c++函数调用c++函数。去掉导出部分后,result()
函数将被声明为:
void result( const char* sString, char* response );
那么这个函数的实现是什么呢?假设它只是将输入的sString
复制到输出的response
字符串。也许代码看起来像这样:
void result( const char* sString, char* response ) {
strcpy( response, sString );
}
又甜又简单,对吧?
好,现在我们调用这个函数。
char buffer[10];
result( "This is my input string", buffer );
你觉得这里可能有问题吗?buffer
只有10个字符长,但是输入的字符串是24个字符,包括结束的null。
所以result()
函数将复制24个字符到一个10个字符的缓冲区。哎呀。
result()
函数如何能够知道response
缓冲区中有多少可用空间?它没有办法知道这个
为了使该函数在C/c++中可用,您还必须传入最大缓冲区长度,然后result()
可以使用该长度来限制它复制的内容的长度。它可以通过使用strcpy_s()
而不是strcpy()
来做到这一点:
void result( const char* sString, char* response, int cchResponse ) {
strcpy_s( response, cchResponse, sString );
}
你可以这样称呼它:
#define elementsof( array ) ( sizeof(array) / sizeof((array)[0]) )
// ...
char buffer[10];
result( "This is my input string", buffer, elementsof(buffer) );
即使输入字符串比输出缓冲区大,也不会有问题,因为只有足够的字符串会被复制到缓冲区中。
所以现在你也有一个函数,你可以从c#调用StringBuilder
,因为你可以分配一个StringBuilder
,并传递它的长度:
[DllImport(
"TEST_DLL.dll",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
SetLastError = true
)]
public static extern void result(
string sString,
StringBuilder response,
int cchResponse
);
StringBuilder buffer( 10 );
result( "This is my input string", buffer, buffer.Capacity );
与上面的C/c++示例一样,即使输入字符串大于缓冲区,result()
函数也只会复制缓冲区中可用的字符数。
底线:如果你将一个StringBuilder
传递给C/c++函数来接收该函数的输出,你必须预先分配它,并且C/c++函数必须提供一种方法让你告诉它最大长度。
(我写这个没有测试代码;这里可能会有错误,但您应该了解总体思路。)