Обратный вызов из неуправляемого C ++ в C # работает, но только в отладчике - PullRequest
7 голосов
/ 13 декабря 2011

Обратные вызовы в C # из неуправляемого C ++ сложно.Из этой статьи MSDN и этой подсказки stackoverflow я узнал большую часть требуемых результатов, и результат отлично работает в отладчике.Но вне отладчика он завершается с ошибкой «Ссылка на объект не установлена ​​для экземпляра объекта».

Вот (упрощенный) код C #:

class CSharpCode
{
    delegate void CallbackDelegate();

    void DoCSharp()
    {
        CallbackDelegate callbackDelegate = TheCallback;
        IntPtr callbackDelegatePointer = Marshal.GetFunctionPointerForDelegate(callbackDelegate);
        GCHandle gchCallbackDelegate = GCHandle.Alloc(callbackDelegatePointer);

        GC.Collect(); // create max space for unmanaged allocations
        CppCliCode.DoCppCli(callbackDelegatePointer);
    }

    public static void TheCallback()
    {
        MessageBox.Show("It worked");
    }
}

А вот код C ++:

#pragma managed

public ref class CppCliCode
{
    static void DoCppCli(IntPtr^ callbackDelegatePointer)
    {
        callback theCallback = static_cast<callback>(callbackDelegatePointer->ToPointer());
        DoCpp(theCallback);
    }
}

#pragma unmanaged

typedef void (__stdcall *callback)();

void DoCpp(callback theCallback)
{
    theCallback();
}

Ошибка возникает где-то между вызовом theCallback() и достижением TheCallback().Ошибка предполагает, что какой-то невидимый управляемый объект стал null.

Если я удалю GC.Collect (), проблема исчезнет.Но это просто означает, что он когда-нибудь снова появится как прерывистая загадка, когда GC случается в неподходящий момент.

GCHandle защищает делегата от сбора, но позволяет перемещать его.В статье MSDN говорится: «Если делегат перемещается с помощью сборки мусора, это не повлияет на управляемый обратный вызов, лежащий в основе, поэтому Alloc используется для добавления ссылки на делегат, что позволяет перемещать делегат, но предотвращает его удаление. Использование GCHandleвместо pin_ptr уменьшает потенциал фрагментации управляемой кучи. "

Что не так?

1 Ответ

7 голосов
/ 14 декабря 2011

Вы должны назначить сам делегат, а не его IntPtr.Также вы должны освободить GCHandle, когда закончите с экземпляром CSharpCode.

class CSharpCode : IDisposible
{
    delegate void CallbackDelegate();
    GCHandle gchCallbackDelegate;

    void DoCSharp()
    {
        CallbackDelegate callbackDelegate = TheCallback;
        IntPtr callbackDelegatePointer = Marshal.GetFunctionPointerForDelegate(callbackDelegate);
        gchCallbackDelegate = GCHandle.Alloc(callbackDelegate); // !!!!

        GC.Collect(); // create max space for unmanaged allocations
        CppCliCode.DoCppCli(callbackDelegatePointer);
    }

    public void Dispose()
    {
        CleanUp();
    }

    ~CSharpCode()
    {
        CleanUp();
    } 

    CleanUp()
    {
        if(gchCallbackDelegate.IsAllocated)
            gchCallbackDelegate.Free();
    }


}

Кстати, я надеюсь, у вас есть более мощная система именования.Такие имена, как DoCSharp, TheCallBack, theCallBack и т. Д., Затрудняют понимание вопроса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...