C ++ / CLI委托作为函数指针(System.AccessViolationException)

时间:2011-12-30 02:44:19

标签: .net c#-4.0 interop c++-cli

我一直在尝试使用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);
    }
}

当我执行程序时,我收到以下错误:

Unmanaged Library Test.dll中出现未处理的“System.AccessViolationException”类型异常

附加信息:尝试读取或写入受保护的内存。这通常表明其他内存已损坏。

顺便提一下,控制台窗口确实显示1024,但随后是一个随机的int(~1000000),然后我收到错误。

我可以开始想象我遇到这个错误的一些原因,但我不确定并且很难找到。如果有人能告诉我为什么我会收到这个错误,以及我能做些什么来修复它,我将非常感激。

2 个答案:

答案 0 :(得分:12)

 void WriteMessage(void* Function, int number)

将函数指针传递为void *是一个非常糟糕的主意。它可以防止编译器检查你做错了什么。虽然编译器在这种特定情况下无法检测到它,但是有些问题。委托被封送为使用__stdcall调用约定的函数指针,实际的函数指针使用__cdecl调用约定,本机代码的默认值。这导致堆栈在通话中失衡。

您可以通过将[UnmanagedFunctionPointer] attribute应用于委托声明来修复它,指定CallingConvention :: Cdecl。

答案 1 :(得分:1)

从委托创建的函数指针对垃圾回收器是不可见的,在可达性分析期间不计算。

来自the documentation

  

您必须手动防止垃圾收集器从托管代码收集委托。垃圾收集器不会将引用[ sic ]跟踪到非托管代码。

如果收集了委托,则函数指针悬空,程序运行不正常。访问冲突是更可能的结果之一,但不是唯一的可能性。如果用于包含本机/托管trampoline的内存被重用于其他一些数据,则CPU可能会尝试将其解释为指令,这可能意味着什么。

解决方案是保持委托可访问,例如通过C ++ / CLI gcroot类,这是.NET GCHandle的薄包装。