2010-02-11 31 views
2

加載程序集時出現奇怪的問題,很遺憾,我無法通過簡單易用的代碼重現它。我在下面描述我正在做的事情。Assembly.Load的奇怪問題*

我有4個應用程序:

  1. 「情結祈求」 - 國外(不是我)開源應用程序,這是電腦遊戲其實,對我正在寫一個插件。這個應用程序可以從dll調用幾個函數(請參見下面的代理定義)。簡單來說,它調用3個函數init();發佈();做一點事();實際名稱有點不同,但沒關係。複雜的調用者是使用純非託管c \ C++編寫的,並使用MSVC進行編譯。

  2. 「Simple Invoker」 - 我從頭開始編寫的應用程序,以測試問題是否以任何方式發生(請參見下文)。它也是這樣 - 調用Complex Invoker中的3個函數。簡單的調用者是用純非託管的c \ C++編寫的,並使用MSVC進行編譯。

  3. 「Proxy」 - 兩個Invokers正在調用的dll。它導出函數init();發佈();做一點事();由調用者呼叫他們。從另一部分來看,這是一個由init()函數調用的託管(CLR)部分。實際的託管類用於調用Assembly.Load(Byte [],Byte []);這個函數調用從文件加載一個Assembly(見下面)來實例化該程序集中的類。來自程序集的類實現了「代理」中定義的接口「SomeInterface」(「程序集」引用了「代理」)。代理以混合模式(託管+非託管)C++在MSVC中用/ clr標誌寫入。

  4. 「Assembly」是dll(託管程序集),帶有實現Proxy的「SomeInterface」的單個類。它非常簡單,用c#編寫。

現在這裏是我的目標。我希望Invoker(複雜的一個)調用代理,然後加載Assembly並在類中調用函數(在Assembly中)。關鍵要求是能夠在不需要重新執行Invoker的情況下按需重新裝入程序集。 Invoker具有這種機制來表示需要重新加載到代理,代理又爲Assembly.dll執行Assembly.Load(Byte [],Byte [])。

所以現在是這個問題。它與「Simple Invoker」很好地合作,並且無法與「Complex Invoker」合作! 使用簡單的調用者我能夠「重新加載」(實際上加載組件的數量)大會超過50次,並且使用「複雜調用者」Assembly.Load()方法在第一次嘗試重新加載程序集時失敗,即代理在第一次加載彙編程序並且無法按需重新加載它。 有趣的是,簡單和複雜的加載程序完全相同的函數調用流程,即LoadLibraryA(「Pathto \ Proxy.dll」); GetProcAddress用於init,release,handleEvent;調用這些函數;並在此之後FreeLibrary()。我發現Complex Invoker和.NET libs之間的操作問題(不知道是什麼類型)。複雜的調用者有一些代碼破壞了MS .NET的正確性。我99%肯定這不是我作爲編碼員的錯。

就在我深入瞭解有關異常的更深入細節以及我嘗試克服此類問題的方法(如使用AppDomains)之前,我想澄清如果此論壇上的某人有能力,時間和意願要深入到System.Load()內部(即,這應該是System和System.Reflection源中的一個)。這需要安裝「Complex Invoker」以及Proxy和Assembly(不要認爲這是太艱難的任務)。

我在這裏發佈相關的代碼片段。

InvokerSimple

DWORD WINAPI DoSomething(LPVOID lpParam) 
{ 
    HMODULE h=LoadLibraryA("PathTo\\CSProxyInterface.dll"); //this is Proxy! 


    initType init=(initType)GetProcAddress(h,"init"); 
    releaseType release=(releaseType)GetProcAddress(h,"release"); 
    handleEventType handleEvent=(handleEventType)GetProcAddress(h,"handleEvent");  



    init(0,NULL); 

    int r; 

    for (int i=0;i<50;i++) 
    { 
     r=handleEvent(0,0,NULL); //causes just Assembly.SomeFunction invoke 
     r=handleEvent(0,0,NULL); //causes just Assembly.SomeFunction invoke 
     r=handleEvent(0,0,NULL); //causes just Assembly.SomeFunction invoke 
     r=handleEvent(0,4,NULL); //causes Assembly reload (inside the Proxy) 
    } 

    release(0); 

    FreeLibrary(h); 

    return 0; 
} 


int _tmain(int argc, _TCHAR* argv[]) 
{ 
    bool Threaded=true; //tried both Threaded and unThreaded. They work fine! 

    if (Threaded) 
    { 
     DWORD threadID; 
     HANDLE threadHNDL; 
     threadHNDL=CreateThread(NULL,0,&DoSomething,NULL,0, &threadID); 
     WaitForSingleObject(threadHNDL,INFINITE); 
    } 
    else 
    { 
     DoSomething(NULL); 
    } 

    return 0; 
} 

CSProxyInterface(代理)

stdafx.h中

int Safe_init(void); 
int Safe_release(void); 

extern "C" __declspec(dllexport) int __cdecl init(int teamId, const void* callback); 
extern "C" __declspec(dllexport) int __cdecl release(int teamId); 
extern "C" __declspec(dllexport) int __cdecl handleEvent(int teamId, int topic, const void* data); 

stdafx.cpp

#include "stdafx.h" 
#include "WrapperClass.h" 

#include <vcclr.h> 

using namespace System; 
using namespace System::Diagnostics; 
using namespace System::Threading; 

// TODO: reference any additional headers you need in STDAFX.H 
// and not in this file 

#define CSMAXPATHLEN 8192 

static gcroot<WrapperClass^> wc; 
static char* my_filename="PathTo\\Assembly.dll"; 

int __cdecl init(int teamId, const void* callback) 
{ 
    return Safe_init(); 
} 

int Safe_init(void) 
{ 
    Safe_release(); 
    wc=gcnew WrapperClass(gcnew String(my_filename)); 
    return 0; 
} 

int __cdecl release(int teamId) 
{ 
    return Safe_release(); 
} 

int Safe_release(void) 
{ 
    if (static_cast<WrapperClass^>(wc)!=nullptr) 
    { 
     delete wc; 
     wc=nullptr; 
    } 
    return 0; 
} 

int __cdecl handleEvent(int teamId, int topic, const void* data) 
{ 

    if (topic!=4) 
    { 
     int r=wc->Do(topic); 
     return r; 
    } 
    else 
    {   
     Safe_init(); 
     return 0; 
    } 

} 

WrapperClass.h

#pragma once 

using namespace System; 
using namespace System::Reflection; 

#include "SomeInterface.h" 

ref class WrapperClass 
{ 
private: 
    ResolveEventHandler^ re; 
    Assembly^ assembly; 
    static Assembly^ assembly_interface; 
    SomeInterface^ instance; 
private: 
    Assembly^ LoadAssembly(String^ filename_dll); 
    static Assembly^ MyResolveEventHandler(Object^ sender, ResolveEventArgs^ args); 
    static bool MyInterfaceFilter(Type^ typeObj, Object^ criteriaObj); 
public: 
    int Do(int i); 
public: 
    WrapperClass(String^ dll_path); 
    !WrapperClass(void); 
    ~WrapperClass(void) {this->!WrapperClass();}; 
}; 

WrapperClass.cpp

#include "StdAfx.h" 
#include "WrapperClass.h"  
WrapperClass::WrapperClass(String^ dll_path) 
    { 
     re=gcnew ResolveEventHandler(&MyResolveEventHandler); 
     AppDomain::CurrentDomain->AssemblyResolve +=re; 

     assembly=LoadAssembly(dll_path); 

     array<System::Type^>^ types; 

     try 
     {  
      types=assembly->GetExportedTypes(); 
     } 
     catch (Exception^ e) 
     { 
      throw e;   
     } 

     for (int i=0;i<types->Length;i++) 
     { 
      Type^ type=types[i]; 

      if ((type->IsClass)) 
      { 
       String^ InterfaceName = "SomeInterface"; 

       TypeFilter^ myFilter = gcnew TypeFilter(MyInterfaceFilter); 
       array<Type^>^ myInterfaces = type->FindInterfaces(myFilter, InterfaceName); 

       if (myInterfaces->Length==1) //founded the correct interface     
       { 
        Object^ tmpObj=Activator::CreateInstance(type); 
        instance = safe_cast<SomeInterface^>(tmpObj); 
       } 

      } 
     } 
    } 

    bool WrapperClass::MyInterfaceFilter(Type^ typeObj, Object^ criteriaObj) 
    { 
     return (typeObj->ToString() == criteriaObj->ToString()); 
    } 

    WrapperClass::!WrapperClass(void) 
    { 
     AppDomain::CurrentDomain->AssemblyResolve -=re; 
     instance=nullptr; 
     assembly=nullptr; 
    } 

    int WrapperClass::Do(int i) 
    { 
     return instance->Do(); 
    } 

    Assembly^ WrapperClass::MyResolveEventHandler(Object^ sender, ResolveEventArgs^ args) 
    { 
     Assembly^ return_=nullptr; 

     array<Assembly^>^ assemblies=AppDomain::CurrentDomain->GetAssemblies(); 

     for (int i=0;i<assemblies->Length;i++) 
     { 
      if (args->Name==assemblies[i]->FullName) 
      { 
       return_=assemblies[i]; 
       break; 
      } 
     } 


     return return_; 
    } 


    Assembly^ WrapperClass::LoadAssembly(String^ filename_dll) 
    { 
     Assembly^ return_=nullptr; 

     String^ filename_pdb=IO::Path::ChangeExtension(filename_dll, ".pdb"); 

     if (IO::File::Exists(filename_dll)) 
     { 
      IO::FileStream^ dll_stream = gcnew IO::FileStream(filename_dll, IO::FileMode::Open, IO::FileAccess::Read); 
      IO::BinaryReader^ dll_stream_bytereader = gcnew IO::BinaryReader(dll_stream); 
      array<System::Byte>^ dll_stream_bytes = dll_stream_bytereader->ReadBytes((System::Int32)dll_stream->Length); 
      dll_stream_bytereader->Close(); 
      dll_stream->Close(); 

      if (IO::File::Exists(filename_pdb)) 
      { 
       IO::FileStream^ pdb_stream = gcnew IO::FileStream(filename_pdb, IO::FileMode::Open, IO::FileAccess::Read); 
       IO::BinaryReader^ pdb_stream_bytereader = gcnew IO::BinaryReader(pdb_stream); 
       array<System::Byte>^ pdb_stream_bytes = pdb_stream_bytereader->ReadBytes((System::Int32)pdb_stream->Length); 
       pdb_stream_bytereader->Close(); 
       pdb_stream->Close(); 

       try 
       { 
        //array<Assembly^>^ asses1=AppDomain::CurrentDomain->GetAssemblies(); 
        return_=Assembly::Load(dll_stream_bytes,pdb_stream_bytes); 
        //array<Assembly^>^ asses2=AppDomain::CurrentDomain->GetAssemblies(); 
       } 
       catch (Exception^ e) 
       { 
        //array<Assembly^>^ asses3=AppDomain::CurrentDomain->GetAssemblies(); 
        throw e; 
       }   
      } 
      else 
      { 
       try 
       { 
        return_=Assembly::Load(dll_stream_bytes); 
       } 
       catch (Exception^ e) 
       { 
        throw e; 
       } 
      } 
     } 
     return return_; 

    } 

最後Assembly.dll

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace Assembly 
{ 
    public class Class1:SomeInterface 
    { 
     private int i; 

     #region SomeInterface Members 

     public int Do() 
     { 

      return i--; 
     } 

     #endregion 
    } 
} 

如果一個管理編譯所有3項目會得到它的工作。這是簡單的調用方案。

另外我有一個和Simple Invoker完全一樣的開源遊戲。但是,在請求重載(handleEvent(0,4,NULL))後,我在Assembly :: Load(Byte [],Byte [])<內收到錯誤 - 您可以在代理代碼

中找到它

例外如下:

"Could not load file or assembly '4096 bytes loaded from CSProxyInterface, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Exception from HRESULT: 0x800703E6" 

的InnerException:

0x1a158a78 { "Could not load file or assembly 'sorttbls.nlp' or one of its dependencies. Exception from HRESULT: 0x800703E6"} 

只是要更精確地驗證碼確實與簡單調用器方案的工作和不復雜的工作,一旦(第一初始化)一。


不幸的是,我似乎並不像我應該清楚的那樣清楚。我會再試一次。

想象一下,你有一個「黑匣子」,它正在做的是我的proxy.dll。它加載assembly.dll,實例化來自程序集的類的對象並運行它。

黑盒子有一個外部接口,這些是函數init,release,DoSomething。如果我通過簡單的應用程序(無線程,無網絡代碼,無互斥,關鍵部分等)觸摸此接口,整個構造都可以工作。這意味着黑匣子做得很好。在我的情況下,我能夠「重新裝配」組裝幾次(50次)以更具體。從另一方面來說,我擁有完全相同的呼叫流程的複雜應用程序。但不知怎的,它干擾了CLR的工作方式:黑盒子裏面的代碼停止工作。複雜的應用程序有線程,tcp/ip代碼,互斥,boost等。很難說什麼確切地阻止了應用程序和Proxy.dll之間的正確行爲。這就是爲什麼我問是否有人看到Assembly.Load內部發生了什麼,因爲我正在調用這個函數時出現了奇怪的異常。

+1

這些例外情況對幫助我們幫助您至關重要。 – 2010-02-11 16:53:38

回答

2

您可能正在運行負載上下文問題。 This blog entry是如何工作的最好的總結。我認爲您使用Assembly.Load(byte [])可能會導致同一個程序集的多個實例最終在同一個AppDomain中。雖然這可能看起來像你想要的,但我認爲這是一場災難。考慮創建多個AppDomains。