I am having some trouble with heap corruption in a .NET application that makes use of native C code, C++/CLI, and C#. This is my first time really getting into the weeds here.
The structure of the application is C# for GUI and overall control flow, C++/CLI for wrapping native C functions, and native C functions for processing data. These native C functions typically accept as inputs native pointers to arrays (eg: int*) and a dimension. C++/CLI wraps up those low level functions into higher level combined processing functions, and C# calls the high level functions.
Occasionally, I do need to allocate unmanaged memory at the C# level, then pass the same parcel of memory to a few different C++/CLI functions.
In order to pass these arrays around freely through my C# and C++/CLI layers, I created a thin wrapper class around managed pointers. This wrapper, called ContiguousArray, defined at the C++/CLI layer, looks something like this:
template <typename T>
public ref class ContiguousArray
{
public:
ContiguousArray<T>(int size)
{
_size = size;
p = (T*) calloc(_size,sizeof(T));
}
T& operator[](int i)
{
return p[i];
}
int GetLength()
{
return _size;
}
~ContiguousArray<T>()
{
this->!ContiguousArray<T>();
}
!ContiguousArray<T>()
{
if (p != nullptr)
{
free(p);
p = nullptr;
}
}
T* p;
int _size;
};
// Some non-templated variants of ContiguousArray for passing out to other .NET languages
public ref class ContiguousArrayInt16 : public ContiguousArray<Int16>
{
ContiguousArrayInt16(int size) : ContiguousArray<Int16>(size) {}
};
I use this wrapper class in a few ways.
Use Case 1 (C++/CLI):
{
// Create an array for the low level code
ContiguousArray<float> unmanagedArray(1024);
// Call some native functions
someNativeCFunction(unmanagedArray.p, unmanagedArray.GetLength());
float* unmanagedArrayPointer = unmanagedArray.p;
anotherNativeCFunction(unmanagedArrayPointer, unmanagedArray.GetLength());
int returnCode = theLastNativeCFunction(unmanagedArray.p, unmanagedArray.GetLength());
return returnCode;
} // unmanagedArray goes out of scope, freeing the memory
Use Case 2 (C++/CLI):
{
// Create an array for the low level code
ContiguousArray<float>^ unmanagedArray = gcnew ContiguousArray<float>(1024);
cliFunction(unmanagedArray);
anotherCLIFunction(unmanagedArray);
float* unmanagedArrayPointer = unmanagedArray->p;
int returnCode = nativeFunction(unmanagedArrayPointer, unmanagedArray->GetLength());
return returnCode;
} // unmanagedArray goes out of scope, the garbage collector will take care of it at some point
Use Case 3 (C#):
{
ContiguousArrayInt16 unmanagedArray = new UnmanagedArray(1024);
cliFunction(unmanagedArray);
unmanagedArray = anotherCLIFunctionThatReplacesUnmanagedArray(unmanagedArray); // Unmanaged array is possibly replaced, original gets collected at some point
returnCode = finalCLIFunction(unmanagedArray);
// Do something with return code like show the user
} // Memory gets freed at some point
I thought that I was being pretty careful with handling the unmanaged memory by using this wrapper class, but I keep seeing heap corruption and access violation issues in my application. I never keep a native pointer to the unmanaged memory outside of the scope where the ContiguousArray object is valid.
Is there anything wrong with any of these three use cases that could, in theory, cause heap corruption? Am I missing something key in my ContiguousArray implementation? I am worried that perhaps the garbage collector is getting a little overzealous and cleaning up my managed objects before I'm really done with them.
Use case 1: Am I guaranteed that the finalizer won't be called until the closing brace? Is it possible that .NET has decided that object is no longer of use and it gets cleaned up while I still have a pointer to its internal memory? Does GC::KeepAlive need to be used for stack objects?
Use case 2: Do I need GC::KeepAlive at the end to guarantee the object isn't disposed before the third function call? Would I still need it if I instead wrote: nativeFunction(unmanagedArray->p, unmanagedArray->GetLength());
Use case 3: I can't see anything wrong here, but maybe I am missing something?
delete unmanagedArray;
or use stack semantics, in the C# code useusing
. – Hans Passant