我从文件加载程序集时遇到奇怪的问题,不幸的是我无法用简单的易于共享的代码重现它。我在下面描述了我正在做的事情。
我有 4 个应用程序:
“Complex Invoker” - 外国(不是我的)开源应用程序,实际上是电脑游戏,我正在为此编写插件。此应用程序可以从 dll 调用多个函数(请参阅下面的代理定义)。为简单起见,它调用了 3 个函数 init(); 发布(); 做一点事(); 实际名称略有不同,但没关系。复杂的调用程序是用纯非托管 c\c++ 编写的,并用 MSVC 编译。
“Simple Invoker” - 我从头开始编写的应用程序,用于测试问题(见下文)是否以任何方式发生。它的作用相同 - 调用 3 个函数,就像在 Complex Invoker 中一样。简单的调用程序是用纯非托管 c\c++ 编写的,并用 MSVC 编译。
“代理” - 两个调用者都调用的 dll。它导出函数 init(); 发布(); 做一点事(); 调用者调用它们。这里的另一部分是由 init() 函数调用的托管 (CLR) 部分。实际托管类用于调用 Assembly.Load(Byte[],Byte[]); 此函数调用从文件(见下文)加载程序集以从该程序集中实例化类。来自程序集的类实现接口“SomeInterface”,该接口也在“Proxy”中定义(“Assembly”引用了“Proxy”)。代理是在带有 /clr 标志的 MSVC 中以混合模式(托管+非托管)C++ 编写的。
“Assembly”是具有单个类的 dll(托管程序集),它从 Proxy 实现“SomeInterface”。它非常简单,用 c# 编写。
现在这是我的目标。我希望调用者(特别是复杂的)调用代理,代理依次加载程序集并调用类(在程序集中)实例中的函数。关键要求是能够按需“重新加载”程序集,而无需重新执行 Invoker。Invoker 具有指示需要重新加载到 Proxy 的机制,而后者又为 Assembly.dll 执行 Assembly.Load(Byte[],Byte[])。
所以现在是问题所在。它与“Simple Invoker”配合得很好,但不能与“Complex Invoker”配合使用!使用简单调用程序,我能够“重新加载”(实际加载程序集的数量)超过 50 次,使用“复杂调用程序”,Assembly.Load() 方法在第一次尝试重新加载程序集时失败,即代理加载大会第一次并未能按需重新加载。有趣的是,简单加载器和复杂加载器执行完全相同的函数调用流程,即 LoadLibraryA("Pathto\Proxy.dll"); GetProcAddress 用于 init、release、handleEvent;调用这些函数;之后是 FreeLibrary()。而且我看到了复杂调用程序和 .NET 库之间的互操作问题(不知道是什么样的)。Complex Invoker 的代码中有一些东西破坏了 MS 。NET 正确性。我 99% 确定这不是我作为程序员的错。
就在我更深入地了解我遇到的异常以及我为克服这些问题(例如使用 AppDomains)而采取的方法之前,我想澄清一下这个论坛上的某个人是否有能力、时间和意愿深入研究系统。 Load() 内部(即这应该是具有 System 和 System.Reflection 源的那个)。这将需要安装“Complex Invoker”以及 Proxy 和 Assembly(不要认为这是太艰巨的任务)。
我在这里发布相关的代码片段。
调用者简单
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(代理)
标准数据文件
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;
}
}
包装类.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();};
};
包装类.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[]) <--你可以在代理代码中找到它
异常如下所示:
"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"
内部异常:
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 内部发生的事情,因为当我'