2010-02-11 8 views
2

J'ai un problème étrange avec le chargement de l'assemblage à partir du fichier et malheureusement je ne peux pas le reproduire avec un simple code facile à partager. Je décris ce que je fais juste en dessous.Étrange problème avec Assembly.Load *

J'ai 4 applications:

  1. "Invoker complexe" - l'étranger (pas le mien) l'application open source, qui est le jeu d'ordinateur en fait, pour lequel j'écris un plugin. Cette application peut appeler plusieurs fonctions de dll (voir la définition de proxy ci-dessous). Pour être simple, il appelle 3 fonctions init(); Libération(); Faire quelque chose(); Les noms réels sont un peu différents, mais cela n'a pas d'importance. L'invocateur complexe est écrit en c \ C++ pur et non géré et compilé avec MSVC. "Simple Invoker" - l'application que j'ai écrite de zéro pour tester si le problème (voir ci-dessous) se produit de quelque façon que ce soit. Il fait la même chose - appelle 3 fonctions comme dans Complex Invoker. L'invocateur simple est écrit en c \ C++ pur et non géré et compilé avec MSVC.

  2. "Proxy" - le dll qui est appelé par les deux Invokers. Il exporte les fonctions init(); Libération(); Faire quelque chose(); pour les appeler par des Invokers. D'autre part, voici une partie gérée (CLR) qui est appelée par la fonction init(). La classe managée réelle est utilisée pour appeler Assembly.Load (Byte [], Byte []); Cet appel de fonction charge un assembly à partir du fichier (voir ci-dessous) pour instancier la classe à partir de cet assembly. La classe d'assembly implémente l'interface "SomeInterface" qui est également définie dans "Proxy" ("Assembly" a une référence à "Proxy"). Le proxy est écrit en mode mixte (géré + non géré) C++ dans MSVC avec l'indicateur/clr. "Assembly" est dll (assembly géré) avec une seule classe qui implémente "SomeInterface" à partir de Proxy. C'est très simple et écrit avec C#.

Maintenant, voici mes objectifs. Je veux que l'Invoker (complexe en particulier) appelle un proxy qui à son tour charge les fonctions Assembly et invoke dans l'instance de classe (dans Assembly). L'exigence clé est de pouvoir "recharger" l'Assemblée à la demande sans réexécuter l'Invoker. L'Invoker a le mécanisme pour signaler le besoin de recharger en Proxy, qui à son tour font Assembly.Load (Byte [], Byte []) pour Assembly.dll.

Alors maintenant est le problème. Cela fonctionne très bien avec "Simple Invoker" et ne fonctionne pas avec "Complex Invoker"! Avec Simple Invoker, j'ai été capable de "recharger" (assembler le nombre d'assemblages) l'assemblage plus de 50 fois et avec "Complex Invoker" la méthode Assembly.Load() échoue lors de la première tentative de rechargement d'un assembly, ie Proxy charge l'assemblage la première fois et ne le recharge pas à la demande. Il est intéressant que le chargeur simple et complexe fasse EXACTEMENT le même flux d'appels de fonction, c'est-à-dire LoadLibraryA ("Pathto \ Proxy.dll"); GetProcAddress pour init, release, handleEvent; appeler ces fonctions; et après cette FreeLibrary(). Et je vois le problème de l'inter-opération (non du genre) entre exactement le Complexe Invoker et les librairies .NET. Complexe Invoker a quelque chose dans son code qui brise l'exactitude de MS .NET. Je suis sûr à 99% que ce n'est pas ma faute en tant que codeur. Juste avant d'entrer dans les détails sur les exceptions et les approches que j'ai prises pour essayer de surmonter de tels problèmes (comme utiliser AppDomains), je veux clarifier si quelqu'un sur ce forum a la capacité, le temps et la volonté d'approfondir dans les internes de System.Load() (c'est-à-dire celui avec les sources System et System.Reflection). Cela nécessiterait l'installation de "Complex Invoker" et à la fois Proxy et Assembly (ne pense pas que c'est une tâche trop difficile).

Je poste ici des extraits de code pertinents.

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 (Proxy)

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_; 

    } 

Et enfin 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 
    } 
} 

Si l'on réussi à compiler tous les trois projets il le faire fonctionner. C'est un scénario d'invocateur simple.

J'ai aussi le jeu open-source qui fait exactement la même chose que dans Simple Invoker. Mais après rechargement est demandé (handleEvent (0, 4, NULL) est invoqué) Je reçois une erreur dans l'Assembly :: Load (Byte [], Byte []) < - vous pouvez le trouver dans le code proxy

L'exception est présentée ci-dessous:

"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"} 

Juste pour être aussi plus précis que possible ce code ne fonctionne avec le scénario de invocateur simple et ne fonctionne qu'une seule fois (première Init) dans le complexe un.


Malheureusement, il semble que je n'étais pas aussi clair que j'aurais dû l'être. Je vais essayer encore une fois. Imaginez que vous ayez une "boîte noire" qui fait quelque chose, c'est mon proxy.dll. il charge assembly.dll, instancie l'objet de la classe depuis l'assembly et l'exécute.

Boîte noire a une interface à l'extérieur de ces fonctions sont init, release, DoSomething. si je touche cette interface avec une application simple (pas de threads, pas de code net, pas de mutex, section critique, etc.) l'ensemble de la construction fonctionne. Cela signifie que la boîte noire est bien faite. Dans mon cas, j'ai pu "recharger" l'assemblage plusieurs fois (50 fois) pour être plus précis. De l'autre côté, j'ai l'application complexe qui fait EXACTEMENT le même flux d'appels. Mais d'une manière ou d'une autre cela interfère le fonctionnement du CLR: le code à l'intérieur de la boîte noire cesse de fonctionner. L'application complexe a des threads, du code tcp/ip, mutexes, boost, etc. Il est très difficile de dire ce qui empêche exactement le comportement correct entre l'application et Proxy.dll. C'est pourquoi je demande si quelqu'un a vu ce qui se passe à l'intérieur de l'Assemblée.Charge depuis que je reçois une étrange exception quand j'appelle exactement cette fonction.

+1

Ces exceptions sont cruciales pour nous aider à vous aider. –

Répondre

2

Vous êtes probablement confronté à un problème de contexte de charge. This blog entry est le meilleur résumé de la façon dont cela fonctionne. Votre utilisation de Assembly.Load (byte []), je pense, pourrait faire que plusieurs instances du même assembly se retrouvent dans le même AppDomain. Bien que cela puisse sembler ce que vous voulez, je pense que c'est une recette pour un désastre. Envisagez de créer plusieurs AppDomains à la place.

Questions connexes