2010-12-17 165 views
4

我正在使用C++編寫的非託管庫。該庫有一個託管的C++(CLI)包裝器,並且正在使用託管代碼中的庫。非託管庫(包括CLI包裝器)由第三方編寫,但我可以訪問源代碼。如何在使用託管C++時從另一AppDomain返回一個託管對象?

不幸的是,託管包裝與AppDomains無法正常工作。非託管庫創建線程並將從這些線程調用託管代碼。當託管代碼在非默認AppDomain中運行時,這會導致問題。我需要跨AppDomain調用才能使用標準工具進行單元測試。

爲了解決這個問題,我在託管包裝中引入了委託,並使用Marshal.GetFunctionPointerForDelegate()來獲取允許跨AppDomain調用成功的函數指針。

這一般工作正常,但現在我有一個新問題。我有以下一系列事件。

 
Unmanaged thread -> 
Unmanaged code 1 -> 
Managed wrapper 1 -> 
AppDomain transition (via delegate) -> 
Managed wrapper 2 -> 
Unmanaged code 2 -> 

我已經離開了對圖書館如何讓你在託管代碼凌駕於託管包裝2這是做非託管的託管過渡擺在首位的整點的某些功能的一些細節。

最終非託管代碼2則要非託管對象返回到非託管代碼1

沒有委託和應用程序域的東西託管包裝2將包裝的非託管對象,並將其返回到託管包裝1然後,將狀態轉移到由非託管代碼1使用的非託管對象。

不幸的是,我很難在AppDomain轉換中返回一個託管對象。

我想我必須讓管理對象通過AppDomain邊界可序列化。但是,這並不容易。相反,我創建了一個簡單的類,可以存儲要傳輸的對象的類型以及表示對象狀態的字符串。無論TypeString容易整理,幸運的是,我可以使用默認構造函數總是創建對象的實例,然後從一個字符串初始化:

// Message is the base class of large hierarchy of managed classes. 

[Serializable] 
// Sorry for the "oldsyntax", but that is what the C++ library uses. 
__gc class SerializedMessage { 
public: 
    SerializedMessage(Message* message) 
    : _type(message->GetType()), _string(message->ToString()) { } 

    Message* Create() { 
    Message* message = static_cast<Message*>(Activator::CreateInstance(_type)); 
    message->InitializeFromString(_string); 
    return message; 
    } 

private: 
    Type* _type; 
    String* _string; 
}; 

託管包裝2我返回SerializedMessage管理包裝1然後我通過致電SerializedMessage::Create取回原始郵件的副本。或者至少這是我希望實現的。

不幸的是,AppDomain轉換失敗,InvalidCastException收到消息無法轉換'SerializedMessage'類型的對象以鍵入'SerializedMessage'。

我不確定發生了什麼,但錯誤消息可能表明SerializedMessage對象正在從錯誤的AppDomain訪問。但是,使用Marshal.GetFunctionPointerForDelegate的全部要點是能夠跨AppDomain進行調用。

我也試圖從MarshalByRefObject派生SerializedMessage但然後我得到和具有InvalidCastException消息無法轉換類型「System.MarshalByRefObject」的目的爲類型「SerializedMessage」。

當我通過由Marshal.GetFunctionPointerForDelegate()返回的指針調用時,我需要做什麼才能夠從另一個AppDomain傳遞一個託管對象?在接受的答案

評論

關於「大會是怎麼加載到第二的AppDomain」的部分是正確的。

的C++代碼執行在由所述單元測試運行控制的應用程序域的默認。託管程序集加載到由此單元測試運行器創建的第二個AppDomain中。我使用Marshal.GetFunctionPointerForDelegate來啓用從第一個AppDomain到第二個AppDomain的託管C++調用。

最初我得到了一些FileNotFoundException試圖加載我的託管程序集並解決此問題我將我的託管程序集複製到了單元測試運行器的應用程序庫。我仍然有點困惑,爲什麼.NET堅持加載正在執行的程序集,但後來決定在那個問題上工作,並簡單地將缺少的文件複製爲一個雜湊。

不幸的是,這會從兩個不同的AppDomain中的相同程序集的兩個不同副本加載相同類型,我認爲這是InvalidCastException的根本原因。

我的結論是,我無法使用「標準」單元測試運行器來測試託管C++庫,因爲來自此庫的回調來自我無法控制的錯誤AppDomain。

回答

1

無法投射'SerializedMessage'類型的對象來鍵入'SerializedMessage'。

我會集中在這個問題上你的問題的核心。真正的消息應該是「類型Foo.SerializedMessage類型Bar.SerializedMessage」。換句話說,這裏涉及兩種類型,從不同的組件。 .NET類型的標識不僅僅是類名,還包括完全限定的程序集名稱。程序集顯示名稱和程序集版本和文化。一個DLL地獄反措施。

檢查您的程序集是如何構建的,並驗證SerializedMessage只在您的任何程序集中出現一次。它也可能是由程序集加載到第二個AppDomain的方式引起的,使用LoadFile()將導致它例如。它在沒有加載上下文的情況下加載程序集,並且從這種程序集加載的任何類型甚至不能與通常加載的完全相同的程序集中的完全相同的類型兼容。

你最好從GetFunctionPointerForDelegate遠離(),非託管指針不遵守的AppDomain邊界。雖然我不知道你爲什麼使用它。

+0

感謝您的回覆。我認爲你的第二段內容是正確的,但我必須在週一更詳細地檢查。我使用'GetFunctionPointerForDelegate',因爲非託管線程調用託管代碼。當託管代碼加載到非默認AppDomain中時,'gcroot '將會拋出一個異常,指示應用程序域之間的調用不被允許(出於顯而易見的原因)。爲了解決這個問題,我使用'GetFunctionPointerForDelegate'封裝了這些調用,但是現在我偶然發現了這個新問題,我無法從非默認的AppDomain中返回一個託管類型。 – 2010-12-17 14:00:54

+1

GetFunctionPointerForDelegate()確實允許AppDomain關聯。一個常見的用例是針對傳遞給PInvoked非託管代碼的回調函數。回調函數是作爲.NET代理實現的,然後使用GetFunctionPointerForDelegate()獲取函數指針傳遞給非託管函數。由於您的回調委託可以使用任何類型的託管代碼,因此如果回調發生在任意AppDomain中,將會是災難性的。委託引用包含有關它駐留的AppDomain的信息,所以當非託管代碼調用函數指針時,它就會起作用。 – robertburke 2014-11-14 23:18:03

相關問題