У меня возникли проблемы с повреждением кучи в приложении .NET, которое использует собственный C-код, C++/CLI и C#. Это мой первый раз, действительно попадая в сорняки здесь.Повреждение кучи при обертке неуправляемых указателей в C++/CLI
Структура приложения - это C# для GUI и общий поток управления, C++/CLI для упаковки собственных C-функций и собственные C-функции для обработки данных. Эти нативные функции C обычно принимают в качестве исходных указателей на массивы (например: int *) и измерение. C++/CLI завершает эти низкоуровневые функции в функции комбинированной обработки более высокого уровня, а C# вызывает функции высокого уровня.
Иногда мне нужно выделить неуправляемую память на уровне C#, а затем передать одну и ту же партию памяти нескольким другим функциям C++/CLI.
Чтобы свободно передавать эти массивы через мои слои C# и C++/CLI, я создал тонкий класс-оболочку вокруг управляемых указателей. Эта оболочка, называемая ContiguousArray, определяется на уровне C++/CLI, выглядит примерно так:
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) {}
};
Я использую этот класс-обертку несколько способов.
пример 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
Использования 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
Применение Случай 3 (С #):
{
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
Я думал, что я очень осторожен с обработкой неуправляемой памяти, используя этот класс-оболочку, но я продолжаю наблюдать за коррупцией кучи и проблемами нарушения доступа в моей заявке. Я никогда не сохраняю собственный указатель на неуправляемую память за пределами области действия, где объект ContiguousArray действителен.
Есть ли что-то неправильное в любом из этих трех вариантов использования, которые теоретически могут вызвать кучевое повреждение? Я что-то пропустил в своей реализации ContiguousArray? Я беспокоюсь, что, возможно, сборщик мусора становится немного переусердствующим и очищает мои управляемые объекты, прежде чем я действительно с ними справится.
Использование случая 1: Я уверен, что финализатор не будет вызываться до закрытия скобки? Возможно ли, что .NET решил, что объект больше не используется, и он очищается, а у меня все еще есть указатель на его внутреннюю память? Должен ли GC :: KeepAlive использоваться для объектов стека?
Вариант использования 2: Нужна ли мне GC :: KeepAlive в конце, чтобы гарантировать, что объект не будет удален до вызова третьей функции? Я бы все еще нуждался в этом, если бы я написал: nativeFunction (unmanagedArray-> p, unmanagedArray-> GetLength());
Случай использования 3: Я не вижу здесь ничего плохого, но, может быть, я чего-то не хватает?
Посмотрите на http://www.pinvoke.net. Дает образцы всех родных библиотек. – jdweng
Функции C, которые я вызываю, не являются встроенными библиотеками Windows, а кодом, который я сам компилирую. Я не хочу использовать pinvoke для вызова собственной функции, упражнение - это самообучение для высокой производительности. –
Это, безусловно, неправильный код, финализатор может работать во время выполнения собственного кода. Случается, когда другой поток в программе запускает сборку мусора. Использование GC :: KeepAlive() - это обходной путь, но у вас есть намного лучший вариант: уничтожить массив детерминированным образом. Добавьте «delete unmanagedArray;» или используйте семантику стека, в коде C# используйте 'using'. –