2011-12-30 71 views
16

我一直在尝试使用C++/CLI委托(因为我试图制作一个.NET参考库),并且一直存在以下问题。作为函数指针的C++/CLI委托(System.AccessViolationException)

我在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()函数指针指向del egate方法。

当试图应用相同的方法与委托与参数,我的问题出现:委托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'012.dll错误

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

顺便提一句,控制台窗口显示1024,但随后是一个随机int(〜1000000),然后我得到错误。

我可以开始想象一些我得到这个错误的原因,但我不确定,并且很难找出答案。如果有人能告诉我为什么我得到这个错误,并且我能做些什么来解决它,我将不胜感激。

+0

对于一个很好描述的问题的+1 – 2011-12-30 05:26:01

回答

11
void WriteMessage(void* Function, int number) 

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

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

+0

非常感谢,为什么没有参数这不是必需的? – xcvd 2011-12-30 12:19:25

+4

它仍然是必需的,它没有任何区别,因为堆栈上没有任何东西被推入。 – 2011-12-30 12:23:59

1

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

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.

如果委托收集,函数指针是左晃来晃去,和你的程序将表现不好。访问违规是更可能的结果之一,但不是唯一的可能性。如果用于包含本机/托管蹦床的内存重新用于某些其他数据,CPU可能会尝试将其解释为指令,这可能意味着任何事情。

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

+0

我一直无法通过使用GCHandle来解决问题。它似乎即使代理分配与GCHandle发生相同的错误,所以我不认为这是垃圾收集器的问题。 – xcvd 2011-12-30 11:03:35

+0

我还应该补充说,只要我使用/ cli编译Unmanaged.cpp,问题消失 – xcvd 2011-12-30 11:07:44