user3148602
user3148602

Reputation: 63

Use C# to call C++ dll, memory violation sometimes

I have the definitions of the c++ code

#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) ;

For the first few times call in my C# code, it works well, but it cause the memory violation for the fifth time.

[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);

I think that's the problem of not to freeing the stack memory, but I do not know how to solve it.

Upvotes: 1

Views: 254

Answers (1)

Michael Geary
Michael Geary

Reputation: 28880

To start with, forget about C# and just think about how you would call your C++ function from another C/C++ function. With the export stuff stripped out, the result() function would be declared with:

void result( const char* sString, char* response );

Now what about an implementation for that function? Let's suppose it merely copies the input sString to the output response string. Maybe the code looks like this:

void result( const char* sString, char* response ) {
    strcpy( response, sString );
}

Sweet and simple, right?

OK, now let's call that function.

char buffer[10];
result( "This is my input string", buffer );

Do you see a possible problem here? buffer is only 10 characters long, but the input string is 24 characters including the terminating null.

So the result() function will copy 24 characters into a 10 character buffer. Oops.

In fact, how can the result() function ever know how much room is available in the response buffer? It doesn't have any way of knowing this.

To make the function usable in C/C++, you would have to also pass in the maximum buffer length, and then result() could use that length to limit the length of what it copies. It can do that by using strcpy_s() instead of strcpy():

void result( const char* sString, char* response, int cchResponse ) {
    strcpy_s( response, cchResponse, sString );
}

You could call it like this:

#define elementsof( array )  ( sizeof(array) / sizeof((array)[0]) )
// ...
char buffer[10];
result( "This is my input string", buffer, elementsof(buffer) );

And even though the input string is larger than the output buffer, you will be OK because only enough of the string will be copied as fits in the buffer.

So now you also have a function you could call from C# using StringBuilder, because you can allocate a StringBuilder and pass its length in:

[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 );

Like the C/C++ example above, even though the input string is larger than the buffer, the result() function will only copy the number of characters available in the buffer.

Bottom line: if you pass a StringBuilder into a C/C++ function to receive output from that function, you must preallocate it, and the C/C++ function must provide a way for you to tell it the maximum length.

(I'm writing this without testing the code; there could be errors here, but you should get the general idea.)

Upvotes: 2

Related Questions