2010-02-11 4 views
2

파일에서 어셈블리를로드하는 데 이상한 문제가있어서 유감스럽게도 간단한 공유 코드로 재생산 할 수 없습니다. 나는 아래에서 내가하고있는 일을 설명한다. - 외국 (안 내) 내가 플러그인을 쓰고있는 컴퓨터 게임 실제로 오픈 소스 응용 프로그램,Assembly.Load의 이상한 문제 *

  1. "복잡한 호출자"

    나는 4 응용 프로그램을. 이 응용 프로그램은 dll에서 여러 함수를 호출 할 수 있습니다 (아래 프록시 정의 참조). 간단하게하려면 3 개의 함수를 호출한다. init(); 해제(); DoSomething(); 실제 이름은 조금 다르지만 중요하지 않습니다. 복잡한 호출자는 관리되지 않는 순수한 c \ C++로 작성되고 MSVC로 컴파일됩니다.

  2. "Simple Invoker"- 문제 (아래 참조)가 어떤 식 으로든 발생하는지 테스트하기 위해 처음부터 작성한 응용 프로그램입니다. 똑같은 기능 - 복잡한 호출자와 같이 3 가지 기능을 호출합니다. 간단한 호출자는 순수한 관리되지 않는 c \ C++로 작성되고 MSVC로 컴파일됩니다.

  3. "프록시"- 둘 다 Invoker에 의해 호출되는 dll입니다. init() 함수를 내 보냅니다. 해제(); DoSomething(); Invokers에 의해 그들을 호출합니다. 다른 부분에서는 init() 함수에 의해 호출되는 관리되는 (CLR) 부분이 있습니다. 실제 관리되는 클래스는 Assembly.Load (Byte [], Byte [])를 호출하는 데 사용됩니다. 이 함수 호출은 파일 (아래 참조)에서 어셈블리를로드하여 해당 어셈블리에서 클래스를 인스턴스화합니다. 어셈블리의 클래스는 "Proxy"("Assembly"에는 "Proxy"에 대한 참조가 있음)에서도 정의 된 "SomeInterface"인터페이스를 구현합니다. 프록시는/clr 플래그를 사용하여 MSVC의 혼합 모드 (관리 + 비 관리) C++로 작성됩니다.

  4. "어셈블리"는 Proxy에서 "SomeInterface"를 구현하는 단일 클래스를 사용하는 dll (관리되는 어셈블리)입니다. 그것은 매우 간단하고 C#으로 작성되었습니다.

이제 여기 내 목표가 있습니다. Invoker (복잡한 하나)가 프록시를 호출하여 Assembly를로드하고 (어셈블리의) 인스턴스 내에서 함수를 호출하려고합니다. 핵심 요구 사항은 호출자를 다시 실행하지 않고도 어셈블리를 "다시로드"할 수 있어야한다는 것입니다. 호출자는 Assembly.Load (Byte [], Byte [])를 Assembly.dll로 다시 프록시로 다시로드 할 필요성을 알리는 메커니즘을 가지고 있습니다.

이제 문제가 있습니다. "Simple Invoker"와 잘 작동하며 "Complex Invoker"와 함께 작동하지 않습니다! 간단한 호출자로 어셈블리를 실제로 "다시로드"(실제로 어셈블리의 번호를로드)하고 어셈블리를 50 번 이상 "복잡한 호출자"로 불러올 수있었습니다. Assembly.Load() 메서드가 어셈블리를 다시로드하기위한 첫 번째 시도에서 실패합니다. 즉 프록시 처음으로 어셈블리를로드하고 필요할 때 다시로드하지 못합니다. Simple 및 Complex 로더가 동일한 함수 호출 흐름 즉 LoadLibraryA ("Pathto \ Proxy.dll")를 수행한다는 것은 흥미 롭습니다. init, release, handleEvent에 대한 GetProcAddress; 이 함수들을 호출한다; 그 후 FreeLibrary(). 그리고 정확히 복잡한 호출자와 .NET 라이브러리 사이의 상호 운용 (어떤 종류인지 몰라서) 문제를 봅니다. 복잡한 호출자는 MS .NET의 정확성을 손상시키는 코드를 가지고 있습니다. 나는 코더로서의 내 잘못이 아니라고 99 % 확신한다.

방금 ​​전에 내가 가진 예외에 대해 자세히 알아보고 AppDomains 사용과 같은 문제를 극복하기 위해 시도한 접근 방식에 대해 설명하고 싶습니다.이 포럼에있는 누군가가 능력, 시간 및 의지가 깊다면 System.Load() 내부 (즉 System 및 System.Reflection 소스가 있어야합니다. 이렇게하려면 "복잡한 호출자"와 프록시 및 어셈블리를 설치해야합니다 (너무 어려운 작업이라고 생각하지 마십시오).

여기에 관련 코드 조각을 게시하고 있습니다.

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

012 하나는 3 프로젝트를 컴파일하기 위해 관리하는 경우 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,516,

#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();}; 
}; 

그것이 작동 얻을 것입니다. 간단한 호출자 시나리오입니다.

또한 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 코드, 뮤텍스, 부스트 등이 있습니다. 응용 프로그램과 Proxy.dll 사이의 정확한 동작을 정확히 차단하는 것은 매우 어렵습니다. 그래서 누군가 내가 Assembly.Load 내부에서 무슨 일이 벌어지고 있는지를 묻는 이유는 정확히이 함수를 호출 할 때 이상한 예외가 생기고 있기 때문입니다.

+1

이러한 예외는 Google에서 귀하를 돕기 위해 중요합니다. –

답변

2

아마도로드 컨텍스트 문제가 발생합니다. This blog entry이 어떻게 작동하는지에 대한 가장 좋은 요약입니다. Assembly.Load (byte [])를 사용하면 동일한 어셈블리의 여러 인스턴스가 동일한 AppDomain으로 끝날 수 있습니다. 이것이 당신이 원하는 것처럼 보일 수도 있지만, 재앙을위한 방법이라고 생각합니다. 대신 여러 AppDomains를 만드는 것을 고려하십시오.