Странная проблема с Assembly.Load * - PullRequest
2 голосов
/ 11 февраля 2010

У меня странная проблема с загрузкой сборки из файла, и, к сожалению, я не могу воспроизвести ее с помощью простого простого в распространении кода. Ниже я опишу, что я делаю.

У меня есть 4 приложения:

  1. "Complex Invoker" - зарубежное (не мое) приложение с открытым исходным кодом, которое фактически является компьютерной игрой, для которой я пишу плагин. Это приложение может вызывать несколько функций из DLL (см. Определение прокси ниже). Для простоты он вызывает 3 функции init (); релиз(); Сделай что-нибудь(); реальные имена немного отличаются, но это не имеет значения. Сложный invoker написан на чистом неуправляемом c \ c ++ и скомпилирован с MSVC.

  2. «Simple Invoker» - приложение, которое я написал с нуля, чтобы проверить, возникает ли проблема (см. Ниже) каким-либо образом. Он делает то же самое - вызывает 3 функции, как в Complex Invoker. Простой invoker написан на чистом неуправляемом c \ c ++ и скомпилирован с MSVC.

  3. «Прокси» - dll, вызываемая обоими Призывателями. Экспортирует функции init (); релиз(); Сделай что-нибудь(); для вызова их Invokers. С другой стороны здесь находится управляемая (CLR) часть, которая вызывается функцией init (). Фактический управляемый класс используется для вызова Assembly.Load (Byte [], Byte []); Этот вызов функции загружает сборку из файла (см. Ниже) для создания экземпляра класса из этой сборки. Класс из сборки реализует интерфейс «SomeInterface», который также определен в «Proxy» («Assembly» имеет ссылку на «Proxy»). Прокси написан в смешанном режиме (управляемый + неуправляемый) C ++ в MSVC с флагом / clr.

  4. «Сборка» - это dll (управляемая сборка) с одним классом, который реализует «SomeInterface» из Proxy. Это очень просто и написано с помощью c #.

Теперь вот мои цели. Я хочу, чтобы Invoker (в частности, сложный) вызывал прокси, который, в свою очередь, загружал Assembly и вызывал функции в экземпляре класса (в Assembly). Ключевым требованием является возможность «перезагрузки» сборки по требованию без повторного запуска Invoker. В Invoker есть механизм для сигнализации о необходимости перезагрузки в Proxy, который, в свою очередь, выполняет Assembly.Load (Byte [], Byte []) для Assembly.dll.

Так что теперь это проблема. Он очень хорошо работает с "Simple Invoker" и не работает с "Complex Invoker"! С помощью Simple Invoker я смог «перезагрузить» (на самом деле загрузить количество сборок) сборку более чем в 50 раз, а с помощью «Complex Invoker» метод Assembly.Load () завершился неудачно при первой попытке перезагрузить сборку, т.е. Proxy загружает Сборка в первый раз и не может перезагрузить его по требованию. Интересно, что простой и сложный загрузчик выполняют ТОЛЬКО один и тот же поток вызовов функций, то есть LoadLibraryA ("Pathto \ Proxy.dll"); GetProcAddress для init, release, handleEvent; вызов этих функций; и после этого FreeLibrary (). И я вижу проблему с взаимодействием (не знаю, какого рода) между именно Complex Invoker и библиотеками .NET. Сложный Invoker содержит в своем коде что-то, что нарушает корректность MS .NET. Я на 99% уверен, что это не моя вина как кодера.

Непосредственно перед тем, как я углублюсь в подробности об Исключениях, которые я получил, и подходах, которые я использовал, чтобы попытаться преодолеть такие проблемы (например, использование доменов приложений), я хочу уточнить, есть ли у кого-то на этом форуме возможность, время и желание углубиться внутренние компоненты 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 []) <- вы можете найти ее в коде прокси </p>

Исключение показано ниже:

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

Просто чтобы быть как можно более точным, этот код работает с простым сценарием Invoker и работает один раз (первый Init) в сложном.


К сожалению, похоже, я был не так ясен, как следовало бы. Попробую еще раз.

Представьте, что у вас есть "черный ящик", который делает что-то, это мой proxy.dll. он загружает файл assembly.dll, создает экземпляр объекта класса из сборки и запускает его.

Черный ящик имеет интерфейс снаружи, это функции init, release, DoSomething. если я коснусь этого интерфейса простым приложением (без потоков, без сетевого кода, без мьютексов, критических секций и т. д.), то вся конструкция работает. Это означает, что черный ящик сделан хорошо. В моем случае я смог «перезагрузить» сборку несколько раз (50 раз), чтобы быть более конкретным. С другой стороны, у меня есть сложное приложение, которое делает точно такой же поток вызовов. Но как-то это мешает работе CLR: код внутри черного ящика перестает работать. Сложное приложение имеет потоки, tcp / ip-код, мьютексы, boost и т. Д. Очень сложно сказать, что именно мешает корректному поведению между приложением и Proxy.dll. Вот почему я спрашиваю, видел ли кто-то, что происходит внутри Assembly.Load, поскольку я получаю странное исключение, когда вызываю именно эту функцию.

1 Ответ

2 голосов
/ 11 февраля 2010

Возможно, вы столкнулись с проблемой контекста загрузки. Эта запись в блоге является лучшим описанием того, как это работает. Я думаю, что использование Assembly.Load (byte []) может привести к тому, что несколько экземпляров одной сборки окажутся в одном и том же домене приложений. Хотя это может показаться, что вы хотите, я думаю, что это рецепт катастрофы. Вместо этого рассмотрите возможность создания нескольких доменов приложений.

...