Делегат C ++ / CLI в качестве указателя на функцию (System.AccessViolationException) - PullRequest
18 голосов
/ 30 декабря 2011

Я экспериментировал с делегатами C ++ / CLI (поскольку я пытаюсь создать справочную библиотеку .NET), и у меня возникла следующая проблема.

Я определяю делегата в C ++ / CLI,а затем создайте экземпляр делегата в C #, а затем вызовите экземпляр делегата через неуправляемый C ++ через указатель на функцию.Это все работает, как и ожидалось.

Код для иллюстрации этого (сначала мой C #)

using System;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            Library.Test.MessageDelegate messageDelegate = new Library.Test.MessageDelegate(Message);
            Library.Test test = new Library.Test(messageDelegate);
            test.WriteMessage();
            Console.Read();
        }

        static void Message()
        {
            Console.WriteLine(1024);
        }
    }
}

Далее мой управляемый файл C ++ (Managed.cpp)

#include "Unmanaged.hpp"
#include <string>

namespace Library
{
    using namespace System;
    using namespace System::Runtime::InteropServices;

    public ref class Test
    {
    public:
        delegate void MessageDelegate();
    internal:
        MessageDelegate^ Message;
        void* delegatePointer;

    public:
        Test(MessageDelegate^ messageDelegate)
        {
            delegatePointer = (void*)Marshal::GetFunctionPointerForDelegate(messageDelegate).ToPointer();
        }

        void WriteMessage()
        {
            Unmanaged::WriteMessage(delegatePointer);
        }
    };
}

Имой неуправляемый файл C ++ (Unmanaged.cpp)

#include "Unmanaged.hpp"

namespace Unmanaged
{
    typedef void (*WriteMessageType)();
    WriteMessageType WriteMessageFunc;

    void WriteMessage(void* Function)
    {
        WriteMessageType WriteMessageFunc = (WriteMessageType)(Function);
        WriteMessageFunc();
    }
}

Этот код работает как положено, и вывод «1024», так как Method () вызывается указателем функции на метод делегата.

Моя проблема возникает при попытке применить тот же метод с делегатом с аргументами, а именно: делегат void MessageDelegate (int number);

Мой код теперь выглядит следующим образом (C #):

using System;

namespace AddProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            Library.Test.MessageDelegate messageDelegate = new Library.Test.MessageDelegate(Message);
            Library.Test test = new Library.Test(messageDelegate);
            test.WriteMessage(1024);
            Console.Read();
        }

        static void Message(int number)
        {
            Console.WriteLine(number);
        }
    }
}

Мой управляемый файл C ++:

#include "Unmanaged.hpp"
#include <string>

namespace Library
{
    using namespace System;
    using namespace System::Runtime::InteropServices;

    public ref class Test
    {
    public:
        delegate void MessageDelegate(int number);
    internal:
        MessageDelegate^ Message;
        void* delegatePointer;

    public:
        Test(MessageDelegate^ messageDelegate)
        {
            delegatePointer = (void*)Marshal::GetFunctionPointerForDelegate(messageDelegate).ToPointer();
        }

        void WriteMessage(int number)
        {
            Unmanaged::WriteMessage(delegatePointer, number);
        }
    };
}

И мой неуправляемый файл C ++:

#include "Unmanaged.hpp"

namespace Unmanaged
{
    typedef void (*WriteMessageType)(int number);
    WriteMessageType WriteMessageFunc;

    void WriteMessage(void* Function, int number)
    {
        WriteMessageType WriteMessageFunc = (WriteMessageType)(Function);
        WriteMessageFunc(number);
    }
}

При запуске программы появляется следующая ошибка:

Произошло необработанное исключение типа «System.AccessViolationException» в неуправляемой библиотеке Test.dll

Дополнительная информация: Попытка чтения или записи в защищенную память.Это часто указывает на то, что другая память повреждена.

Кстати, в окне консоли отображается 1024, но затем следует случайный тип int (~ 1000000), а затем я получаю ошибку.

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

Ответы [ 2 ]

13 голосов
/ 30 декабря 2011
 void WriteMessage(void* Function, int number)

Передача указателей на функции как void * - довольно плохая идея. Он не позволяет компилятору проверить, что вы делаете что-то не так. Что-то не так, хотя компилятор не может обнаружить это в этом конкретном случае. Делегат маршалируется как указатель функции, который использует соглашение о вызовах __stdcall, ваш фактический указатель функции использует соглашение о вызовах __cdecl, по умолчанию для собственного кода. Что приводит к дисбалансу стека при вызове.

Это можно исправить, применив атрибут [UnmanagedFunctionPointer] к объявлению делегата, указав CallingConvention :: Cdecl.

1 голос
/ 30 декабря 2011

Указатель функции, созданный из делегата, невидим для сборщика мусора и не учитывается при анализе достижимости.

С документация :

Необходимо вручную запретить сбор делегата сборщиком мусора из управляемого кода. Сборщик мусора не отслеживает ссылку [ sic ] на неуправляемый код.

Если делегат собран, указатель функции остается висящим, и ваша программа будет вести себя плохо. Нарушение доступа является одним из наиболее вероятных результатов, но не единственной возможностью. Если память, которая раньше содержала собственный / управляемый батут, повторно используется для некоторых других данных, ЦП может попытаться интерпретировать его как инструкции, которые могут означать что угодно.

Решение состоит в том, чтобы сохранить доступность делегата, например, через класс C ++ / CLI gcroot, который является тонкой оболочкой для .NET GCHandle.

...