NullReferenceException во время обратного вызова C ++ к функции C # - PullRequest
7 голосов
/ 05 февраля 2011

разработчики!
У меня очень странная проблема. Мой проект имеет DLL, написанную на C ++ и GUI, написанный на C #. И я реализовал обратный вызов для некоторой совместимости. Я планировал, что C ++ dll будет вызывать код C # при некоторых обстоятельствах. Это работает ... но не долго, и я не могу понять, почему. Проблема отмечена в комментарии в C # part
Вот полный код упрощенного образца:

C ++ DLL:

#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

BOOL APIENTRY DllMain( HMODULE hModule,
                    DWORD  ul_reason_for_call,
                    LPVOID lpReserved
                                    )
    {
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

extern "C" 
{    
    typedef void  (*WriteSymbolCallback) (char Symbol); 
    WriteSymbolCallback Test;

    _declspec(dllexport) void InitializeLib()
    {
        Test = NULL;
    }

    _declspec(dllexport) void SetDelegate(WriteSymbolCallback Callback)
    {
        Test = Callback;
    }

    _declspec(dllexport) void TestCall(const char* Text,int Length)
    {
        if(Test != NULL)
        {
            for(int i=0;i<Length;i++)
            {
                Test(Text[i]);
            }
        }
    }
};

C # часть:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace CallBackClient
{
    class Program
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate void WriteToConsoleCallback(char Symbol);

        [DllImport("CallbackSketch.dll",CharSet=CharSet.Ansi,CallingConvention=CallingConvention.Cdecl)]
        private static extern void InitializeLib();

        [DllImport("CallbackSketch.dll",CharSet=CharSet.Ansi,CallingConvention=CallingConvention.Cdecl)]
        private static extern void SetDelegate(WriteToConsoleCallback Callback);

        [DllImport("CallbackSketch.dll",CharSet=CharSet.Ansi,CallingConvention=CallingConvention.Cdecl)]
        private static extern void TestCall(string Text,int Length);

        private static void PrintSymbol(char Symbol)
        {
            Console.Write(Symbol.ToString());
        }

        static void Main(string[] args)
        {
            InitializeLib();
            SetDelegate(new WriteToConsoleCallback(PrintSymbol));

            string test = "Hello world!";


            for (int i = 0; i < 15000; i++)
            {
                TestCall(test, test.Length);// It crashes when i == 6860!!!! Debugger told me about System.NullReferenceException
            }            
        }
    }
}

Проблема в том, что он падает на 6860-й итерации! Я считаю, что проблема заключается в недостатке моих знаний в этой теме. Может ли кто-нибудь помочь мне?

1 Ответ

12 голосов
/ 05 февраля 2011
       SetDelegate(new WriteToConsoleCallback(PrintSymbol));

Да, это не может работать должным образом. Собственный код хранит указатель функции для объекта делегата, но сборщик мусора не может видеть эту ссылку. Что касается этого, то существует нет ссылок на объект. И следующая коллекция уничтожает его. Kaboom.

Вы должны хранить ссылку на объект самостоятельно. Добавьте поле в классе, чтобы сохранить его:

    private static WriteToConsoleCallback callback;

    static void Main(string[] args)
    {
        InitializeLib();
        callback = new WriteToConsoleCallback(PrintSymbol);
        SetDelegate(callback);
        // etc...
    }

Правило состоит в том, что класс, в котором хранится объект, должен иметь время жизни, по крайней мере, столько же, сколько у нативного кода возможность сделать обратный вызов. В данном конкретном случае оно должно быть статичным, оно твердое.

...