views:

265

answers:

3

Hi, I am using DllImport to call method in c wrapper library from my own .net class. This method in c dll creates a string variable and returns the pointer of the string.

Something like this;

_declspec(dllexport) int ReturnString()
{
 char* retval = (char *) malloc(125);
 strcat(retval, "SOMETEXT");
 strcat(retval, "SOMETEXT MORE");
 return (int)retval;
}

Then i read the string using Marshall.PtrToStringAnsi(ptr). After i get a copy of the string, i simply call another c method HeapDestroy which is in c wrapper library that calls free(ptr).

Here is the question; Recently while it is working like a charm, I started to get "Attempted to read or write protected memory area" exception. After a deeper analysis, i figured out, i beleive, although i call free method for this pointer, value of the pointer is not cleared, and this fills the heap unattended and makes my iis worker process to throw this exception. By the way, it is an web site project that calls this method in c library.

Would you kindly help me out on this issue?

Sure, here is C# code;

    [DllImport("MyCWrapper.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    private extern static int ReturnString();

    [DllImport("MyCWrapper.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    private extern static void HeapDestroy(int ptr);

    public static string GetString()
    {
        try
        {

            int i = ReturnString();
            string result = String.Empty;
            if (i > 0)
            {
                IntPtr ptr = new IntPtr(i);
                result = Marshal.PtrToStringAnsi(ptr);
                HeapDestroy(i);
            }

            return result;
        }
        catch (Exception e)
        {
            return String.Empty;
        }
    }
+7  A: 

What may be the problem is the underlying C code. You are not adding a NULL terminator to the string which strcat relies on (or checking for a NULL return from malloc). It's easy to get corrupted memory in that scenario. You can fix that by doing the following.

retval[0] = '\0';
strcat(retval, "SOMETEXT");

Also part of the problem is that you are playing tricks on the system. It's much better to write it correctly and let the system work on correctly functioning code. The first step is fixing up the native code to properly return the string. One thing you need to consider is that only certain types of memory can be natively freed by the CLR (HGlobal and CoTask allocations). So lets change the function signature to return a char* and use a different allocator.

_declspec(dllexport) char* ReturnString()
{
 char* retval = (char *) CoTaskMemAlloc(125);
 retval[0] = '\0';
 strcat(retval, "SOMETEXT");
 strcat(retval, "SOMETEXT MORE");
 return retval;
}

Then you can use the following C# signature and free the IntPtr with Marshal.FreeCoTaskMem.

[DllImport("SomeDll.dll")]
public static extern IntPtr ReturnString();

Even better though. When marshalling, if the CLR ever thinks it needs to free memory it will use FreeCoTaskMem to do so. This is typically relevant for string returns. Since you allocated the memory with CoTaskMemAlloc you can save yourself the marshalling + freeing steps and do the following

[DllImport("SomeDll.dll", CharSet=Ansi)]
public static extern String ReturnString();
JaredPar
I'll try that! Seems reasonable.
Emrah GOZCU
JaredPar, your answer is very helpfull. But another question for your example, what is equivalent of CoTaskMemAlloc in ansi-c, is there any in the standard library?
Emrah GOZCU
@Emrah, there is no equivalent in the standard ansi-c library. If you're limited to ansi-c you'll likely need to write up a quick PInvoke layer for free and pass the IntPtr there.
JaredPar
+1  A: 

Freeing memory doesn't clear it, it just frees it up so it can be re-used. Some debug builds will write over the memory for you to make it easier to find problems with values such as 0xBAADFOOD

Callers should allocate memory, never pass back allocated memory:

_declspec(dllexport) int ReturnString(char*buffer, int bufferSize)
{
    if (bufferSize < 125) {
        return 125;
    } else {
        strcat(buffer, "SOMETEXT");
        strcat(buffer, "SOMETEXT MORE");
        return 0;
    }
}
Tom
A: 

Although memory is allocated by the DLL in the same heap as your application, it MAY be using a different memory manager, depending on the library it was linked with. You need to either make sure you're using the same exact library, or add code to release the memory that the DLL allocates, in the DLL code itself.

justinhj
Yes, thats why added HeapDestroy method that contains a simple free(ptr) method to call.
Emrah GOZCU
Cool. I misread your question. I thought you'd written a wrapper around the DLL, so the DLL was doing the alloc whilst your wrapper code was doing the free.
justinhj
I'd recommend using safer operations that work on bounded strings. Put your buffersize in a constant, and pass that to the functions that operate on allocated string datat. Use strncat and so on.
justinhj
@justinhj, actually my sample is bounded string, my actual string varialbe is unbounded. Anyway, thanks for tips.
Emrah GOZCU