Access Violation Exception / Crash из C ++ обратного вызова для функции C #
-
05-07-2019 - |
Вопрос
Итак, у меня есть собственная сторонняя кодовая база C ++, с которой я работаю (файлы .lib и .hpp), которую я использовал для создания оболочки в C ++ / CLI для последующего использования в C #. Р>
Я столкнулся с определенной проблемой при переключении из режима отладки в режим выпуска, в которой я получаю исключение нарушения прав доступа при возврате кода обратного вызова. Р>
Код из исходных hpp-файлов для формата функции обратного вызова:
typedef int (*CallbackFunction) (void *inst, const void *data);
Код из оболочки C ++ / CLI для формата функции обратного вызова: (Я объясню, почему я объявил два сразу)
public delegate int ManagedCallbackFunction (IntPtr oInst, const IntPtr oData);
public delegate int UnManagedCallbackFunction (void* inst, const void* data);
- Быстро, причина, по которой я объявил вторую " UnManagedCallbackFunction " в том, что я пытался создать «посредника» обратный вызов в оболочке, поэтому цепочка изменилась с Native C ++ > C # для версии Native C ++ > C ++ / CLI Wrapper > C # ... Полное раскрытие, проблема все еще жива, она только что была перенесена в C ++ / CLI Wrapper теперь в той же строке (возврат).
И, наконец, аварийный код из C #:
public static int hReceiveLogEvent(IntPtr pInstance, IntPtr pData)
{
Console.WriteLine("in hReceiveLogEvent...");
Console.WriteLine("pInstance: {0}", pInstance);
Console.WriteLine("pData: {0}", pData);
// provide object context for static member function
helloworld hw = (helloworld)GCHandle.FromIntPtr(pInstance).Target;
if (hw == null || pData == null)
{
Console.WriteLine("hReceiveLogEvent: received null instance pointer or null data\n");
return 0;
}
// typecast data to DataLogger object ptr
IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData)));
DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target;
//Do Logging Stuff
Console.WriteLine("exiting hReceiveLogEvent...");
Console.WriteLine("pInstance: {0}", pInstance);
Console.WriteLine("pData: {0}", pData);
Console.WriteLine("Setting pData to zero...");
pData = IntPtr.Zero;
pInstance = IntPtr.Zero;
Console.WriteLine("pData: {0}", pData);
Console.WriteLine("pInstance: {0}", pInstance);
return 1;
}
Все записи в консоль завершены, и затем мы видим страшный сбой при возврате:
Необработанное исключение в 0x04d1004c в helloworld.exe: 0xC0000005: Доступ нарушение считывания местоположения 0x04d1004c.
Если я войду в отладчик отсюда, все, что я увижу, это то, что последняя запись в стеке вызовов: > & Quot; 04d1004c () & Quot; который оценивается в десятичное значение: 80805964
Что интересно, только если вы посмотрите на консоль, которая показывает:
entering registerDataLogger
pointer to callback handle: 790848
fp for callback: 2631370
pointer to inst: 790844
in hReceiveLogEvent...
pInstance: 790844
pData: 80805964
exiting hReceiveLogEvent...
pInstance: 790844
pData: 80805964
Setting pData to zero...
pData: 0
pInstance: 0
Теперь я знаю, что между отладкой и выпуском некоторые вещи в мире Microsoft совершенно разные. Меня, конечно, беспокоит заполнение байтов и инициализация переменных, поэтому, если я что-то здесь не предоставлю, просто дайте мне знать, и я добавлю (уже давно) сообщение. Я также думаю, что управляемый код, возможно, НЕ освобождает всех владельцев, а затем нативная часть C ++ (для которой у меня нет кода) может пытаться удалить или уничтожить объект pData, что приводит к сбою приложения.
Более полное раскрытие, все работает нормально (на первый взгляд) в режиме отладки!
Настоящая проблема с царапинами на голове, которая будет признательна за любую помощь!
Решение
Я думаю, что стек разбился из-за несоответствия соглашений о вызовах: попробуйте поставить атрибут
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
в декларации делегата обратного вызова.
Другие советы
Это не дает прямого ответа на ваш вопрос , но может привести вас в правильном направлении, если режим отладки в порядке против режима выпуска не в порядке:
Так как отладчик добавляет в стек много информации о записях, как правило, добавляя размер и расположение моей программы в памяти, мне повезло & # 8220; повезло & # 8221; в режиме отладки путем пометки более 912 байт памяти, что не очень важно. Однако без отладчика я набрасывался на довольно важные вещи, в конечном итоге выходя за пределы собственного пространства памяти, в результате чего Interop удалял память, которой у него не было.
Какое определение для DataLoggerWrap? Поле char может быть слишком маленьким для данных, которые вы получаете.
Я не уверен, что вы пытаетесь достичь.
Несколько баллов:
1) Сборщик мусора более агрессивен в режиме выпуска, поэтому при плохом владении описанное вами поведение не редкость.
2) Я не понимаю, что пытается сделать приведенный ниже код?
IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData)));
DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target;
Вы используете GCHandle.Alloc для блокировки экземпляра DataLoggerWrap в памяти, но затем вы никогда не передаете его неуправляемому - так почему вы его блокируете? Вы также никогда не освобождаете это?
Затем вторая строка возвращает ссылку - почему круговой путь? почему ссылка - вы никогда не используете его?
3) Вы установили IntPtrs на ноль - почему? - это не будет действовать за пределами области действия функции.
4) Вы должны знать, каков контракт обратного вызова. Кто владеет pData обратным вызовом или вызывающей функцией?
Я с @jdehaan, за исключением того, что CallingConvetion.StdCall может быть ответом, особенно когда сторонняя библиотека написана, например, на BC ++.