Access Violation Exception / Crash из C ++ обратного вызова для функции C # - PullRequest
6 голосов
/ 11 сентября 2009

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

Если я войду в отладчик отсюда, все, что я увижу, это то, что последняя запись в стеке вызовов:> "04d1004c ()", что приводит к десятичному значению: 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, что приводит к сбою приложения.

Более полное раскрытие, все работает нормально (на первый взгляд) в режиме отладки!

Настоящая проблема с царапинами на голове, которая будет признательна за любую помощь!

Ответы [ 4 ]

3 голосов
/ 12 сентября 2009

Я думаю, что стек разбился из-за несоответствия соглашений о вызовах: попробуйте поставить атрибут

 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]

в объявлении делегата обратного вызова.

0 голосов
/ 16 сентября 2009

Я с @jdehaan, кроме CallingConvetion.StdCall может быть ответом, особенно когда сторонняя библиотека написана, например, в BC ++.

0 голосов
/ 16 сентября 2009

Я не уверен, что вы пытаетесь достичь.

Несколько баллов:

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 обратного вызова или вызывающей функции?

0 голосов
/ 11 сентября 2009

Это не дает прямого ответа на ваш вопрос , но может привести вас в правильном направлении, если режим отладки в порядке против режима выпуска не в порядке:

Так как отладчик добавляет в стек много информации о ведении записей, обычно распределяя размер и расположение моей программы в памяти, мне «повезло» в режиме отладки, набросав 912 байт памяти, которые не были это очень важно. Однако без отладчика я набрасывался на довольно важные вещи, в конечном счете выходя за пределы своего собственного пространства памяти, заставляя Interop удалять память, которой он не имел.

Какое определение для DataLoggerWrap? Поле char может быть слишком маленьким для данных, которые вы получаете.

...