【问题标题】:C++/CLI delegate as function pointer (System.AccessViolationException)C++/CLI 委托作为函数指针 (System.AccessViolationException)
【发布时间】:2012-01-30 07:56:02
【问题描述】:

我一直在尝试使用 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() 由指向委托方法的函数指针调用。

我的问题是在尝试对带参数的委托应用相同的方法时,即:

delegate 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”类型的未处理异常 发生在 Unmanaged Library Test.dll 中

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

顺便说一句,控制台窗口确实显示 1024,但后面跟着一个随机整数 (~1000000),然后我得到了错误。

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

【问题讨论】:

  • +1 描述了一个很好的问题

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


【解决方案1】:
 void WriteMessage(void* Function, int number)

将函数指针作为 void* 传递是一个非常糟糕的主意。它可以防止编译器检查你做错了什么。尽管在这种特定情况下编译器无法检测到它,但还是有问题。委托被编组为使用 __stdcall 调用约定的函数指针,您的实际函数指针使用 __cdecl 调用约定,本机代码的默认值。这会导致堆栈在调用时变得不平衡。

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

【讨论】:

  • 非常感谢,为什么没有参数就不需要这个?
  • 它仍然是必需的,它只是没有任何区别,因为没有任何东西被压入堆栈。
【解决方案2】:

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

来自the documentation

您必须手动阻止垃圾收集器从托管代码中收集委托。垃圾收集器不跟踪对非托管代码的引用 [sic]。

如果委托被收集,则函数指针悬空,您的程序将表现不佳。访问冲突是最可能的结果之一,但不是唯一的可能性。如果用于包含本机/托管蹦床的内存被重新用于某些其他数据,CPU 可能会尝试将其解释为指令,这可能意味着任何事情。

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

【讨论】:

  • 我无法通过使用 GCHandle 解决问题。似乎即使使用 GCHandle 分配委托也会发生相同的错误,所以我认为这不是垃圾收集器的问题。
  • 我还应该补充一点,一旦我用 /cli 编译 Unmanaged.cpp,问题就会消失
猜你喜欢
  • 2012-11-06
  • 1970-01-01
  • 2011-04-11
  • 1970-01-01
  • 2010-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-22
相关资源
最近更新 更多