使用C#调用C ++ DLL,有时会出现内存冲突

时间:2015-07-23 03:34:23

标签: c#

我有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);

我认为这是不释放堆栈内存的问题,但我不知道如何解决它。

1 个答案:

答案 0 :(得分:2)

首先,忘记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 个字符,包括终止空字符。

因此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) );

即使输入字符串大于输出缓冲区,您也可以,因为只有足够的字符串将被复制为缓冲区中的匹配。

所以现在你也可以使用StringBuilder从C#调用一个函数,因为你可以分配一个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 ++函数必须提供一种方法让您告诉它最大值长度。

(我写这篇文章时没有测试代码;这里可能会有错误,但你应该得到一般的想法。)