用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++ dll,有时会造成内存冲突

首先,忘记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++函数必须提供一种方法让你告诉它最大长度。

(我写这个没有测试代码;这里可能会有错误,但您应该了解总体思路。)