我在从文件中加载程序集时遇到了奇怪的问题,不幸的是,我无法用简单易用的代码来再现它。我在下面描述我在做什么。
我有4份申请:
现在我的目标是。我希望调用者(特别是复杂的调用者)调用代理,代理反过来在类(程序集)实例中加载程序集和调用函数。关键的要求是能够根据需要“重新加载”程序集,而不需要重新执行调用程序。调用方具有将需要重新加载到代理的信号的机制,而代理则为Assembly.Load(Byte[],Byte[])提供Assembly.dll。
现在的问题也是如此。它与“简单调用者”很好地工作,并且不能与“复杂调用者”一起工作!使用简单调用程序,我能够“重新加载”(实际加载程序集的数量)--程序集超过50次;对于“复杂调用程序集”,Assembly.Load()方法在第一次尝试重新加载程序集时失败,即代理第一次加载程序集,并在需要时无法重新加载它。有趣的是,简单和复杂的加载程序执行完全相同的函数调用流,即LoadLibraryA("Pathto\Proxy.dll");init的GetProcAddress,function,handleEvent;调用这些函数;然后是FreeLibrary()。我看到了复杂调用者和.NET库之间的互操作(不知道是哪种)的问题。复杂的调用者在代码中有一些东西破坏了MS .NET的正确性。我99%肯定这不是我作为一个编码器的错。
在深入了解我获得的异常和试图克服这些问题的方法(比如使用AppDomains)之前,我想澄清一下这个论坛上的某个人是否有能力、时间和意志深入System.Load()内部(也就是说,这应该是有系统和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
#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个项目,它将使它工作。这是一个简单的调用者场景。
此外,我有开源游戏,它做的完全一样,在简单调用。但是在请求重新加载(调用handleEvent(0,4,NULL)之后),我在程序集中得到一个错误::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"}
为了尽可能的精确,这段代码确实适用于简单的调用场景,并且在复杂的调用场景中只工作一次(第一次Init)。
不幸的是,我似乎不像我应该做的那样清楚。我再试一次。
假设您有一个“黑匣子”,它正在做一些事情--这是我的proxy.dll。它加载assembly.dll,从程序集中实例化类的对象并运行它。
黑匣子有一个外部接口,这些都是函数init,release,DoSomething。如果我用简单的应用程序来触摸这个接口(没有线程,没有网络代码,没有互斥,关键部分等等)整个建筑都能用。它的意思是黑匣子做得好。在我的例子中,我能够多次“重新加载”程序集(50次),以便更具体。另一方面,我有一个复杂的应用程序,它执行完全相同的调用流。但不知何故,它干扰了CLR的工作方式:黑匣子中的代码停止工作。这个复杂的应用程序有线程、tcp/ip代码、互斥、boost等。很难说出到底是什么阻止了应用程序和Proxy.dll之间的正确行为。这就是为什么我问是否有人看到了Assembly.Load内部发生的事情,因为当我调用这个函数时,我得到了奇怪的异常。
发布于 2010-02-11 14:17:01
您可能遇到了一个负载上下文问题。这个博客条目是这方面工作原理的最好总结。我认为,使用Assembly.Load(byte[])可能会导致同一个程序集的多个实例在同一个AppDomain中结束。虽然这看起来像你想要的,但我认为这是一个灾难的配方。考虑创建多个AppDomains。
https://stackoverflow.com/questions/2244257
复制相似问题