У меня странная проблема с загрузкой сборки из файла, и, к сожалению, я не могу воспроизвести ее с помощью простого простого в распространении кода. Ниже я опишу, что я делаю.
У меня есть 4 приложения:
"Complex Invoker" - зарубежное (не мое) приложение с открытым исходным кодом, которое фактически является компьютерной игрой, для которой я пишу плагин. Это приложение может вызывать несколько функций из DLL (см. Определение прокси ниже). Для простоты он вызывает 3 функции init (); релиз(); Сделай что-нибудь(); реальные имена немного отличаются, но это не имеет значения. Сложный invoker написан на чистом неуправляемом c \ c ++ и скомпилирован с MSVC.
«Simple Invoker» - приложение, которое я написал с нуля, чтобы проверить, возникает ли проблема (см. Ниже) каким-либо образом. Он делает то же самое - вызывает 3 функции, как в Complex Invoker. Простой invoker написан на чистом неуправляемом c \ c ++ и скомпилирован с MSVC.
«Прокси» - dll, вызываемая обоими Призывателями. Экспортирует функции init (); релиз(); Сделай что-нибудь(); для вызова их Invokers. С другой стороны здесь находится управляемая (CLR) часть, которая вызывается функцией init (). Фактический управляемый класс используется для вызова Assembly.Load (Byte [], Byte []); Этот вызов функции загружает сборку из файла (см. Ниже) для создания экземпляра класса из этой сборки. Класс из сборки реализует интерфейс «SomeInterface», который также определен в «Proxy» («Assembly» имеет ссылку на «Proxy»). Прокси написан в смешанном режиме (управляемый + неуправляемый) C ++ в MSVC с флагом / clr.
«Сборка» - это 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, поскольку я получаю странное исключение, когда вызываю именно эту функцию.