C++/CLI delegate as function pointer (System.AccessViolationException)

蹲街弑〆低调 提交于 2021-02-06 15:15:48

问题


I have been experimenting with C++/CLI delegates (as I am trying to make a .NET reference library), and I have been having the following problem.

I define a delegate in C++/CLI, and then create an instance of the delegate in C#, and then call the instance of the delegate through unmanaged C++ via a function pointer. This all works as expected.

Code to illustrate this (first my 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);
        }
    }
}

Next my managed c++ file (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);
        }
    };
}

And my unmanaged C++ file (Unmanaged.cpp)

#include "Unmanaged.hpp"

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

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

This code all works as expected, and the output is "1024", as Method() is called by the function pointer to the delegate method.

My problem arises when trying to apply the same method with a delegate with arguments, that is: delegate void MessageDelegate(int number);

My code is now as follows (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);
        }
    }
}

My managed C++ file:

#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);
        }
    };
}

And my unmanaged C++ file:

#include "Unmanaged.hpp"

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

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

When I execute the program I get the following error:

An unhandled exception of type 'System.AccessViolationException' occurred in Unmanaged Library Test.dll

Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

Incidentally, the console window does show 1024, but is then followed by a random int (~1000000), and then I get the error.

I can begin to imagine some of the reasons I am getting this error, but I'm not sure and am having difficulty finding out. If anyone could tell me why I am getting this error, and what I could do to fix it, I would greatly appreciate it.


回答1:


 void WriteMessage(void* Function, int number)

Passing function pointers as void* is a pretty bad idea. It prevents the compiler from checking you are doing anything wrong. There is something wrong, although the compiler cannot detect it in this specific case. The delegate is marshaled as a function pointer that uses the __stdcall calling convention, your actual function pointer uses the __cdecl calling convention, the default for native code. Which causes the stack to get imbalanced on the call.

You can fix it by applying the [UnmanagedFunctionPointer] attribute to the delegate declaration, specify CallingConvention::Cdecl.




回答2:


A function pointer created from a delegate is invisible to the garbage collector, and isn't counted during reachability analysis.

From the documentation:

You must manually keep the delegate from being collected by the garbage collector from managed code. The garbage collector does not track reference [sic] to unmanaged code.

If the delegate is collected, the function pointer is left dangling, and your program will behave badly. An access violation is one of the more likely outcomes, but not the only possibility. If the memory that used to contain the native/managed trampoline is reused for some other data, the CPU could try to interpret it as instructions, which might mean just about anything.

The solution is to keep the delegate reachable, for example via the C++/CLI gcroot class, which is a thin wrapper around .NET GCHandle.



来源:https://stackoverflow.com/questions/8675638/c-cli-delegate-as-function-pointer-system-accessviolationexception

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!