Access Violation Exception / Crash из C ++ обратного вызова для функции C #

StackOverflow https://stackoverflow.com/questions/1411110

Вопрос

Итак, у меня есть собственная сторонняя кодовая база 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 ++.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top