2011-12-30 51 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