我不得不与遗留应用程序进行交互,可能与您的情况相同。我找到了一种在 CLR 程序集中获取 DllMain 功能的 hacky 方法。幸运的是,这并不难。它需要一个额外的 DLL,但它不需要您部署一个额外的 DLL,因此您仍然可以拥有“在该目录中放置一个 DLL,应用程序将加载它”的范例。
首先,您创建一个简单的常规 C++ DLL,如下所示:
dllmain.cpp:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "resource.h"
extern void LaunchDll(
unsigned char *dll, size_t dllLength,
char const *className, char const *methodName);
static DWORD WINAPI launcher(void* h)
{
HRSRC res = ::FindResourceA(static_cast<HMODULE>(h),
MAKEINTRESOURCEA(IDR_DLLENCLOSED), "DLL");
if (res)
{
HGLOBAL dat = ::LoadResource(static_cast<HMODULE>(h), res);
if (dat)
{
unsigned char *dll =
static_cast<unsigned char*>(::LockResource(dat));
if (dll)
{
size_t len = SizeofResource(static_cast<HMODULE>(h), res);
LaunchDll(dll, len, "MyNamespace.MyClass", "DllMain");
}
}
}
return 0;
}
extern "C" BOOL APIENTRY DllMain(HMODULE h, DWORD reasonForCall, void* resv)
{
if (reasonForCall == DLL_PROCESS_ATTACH)
{
CreateThread(0, 0, launcher, h, 0, 0);
}
return TRUE;
}
注意线程的创建。这是为了让 Windows 满意,因为在 DLL 入口点内调用托管代码是不允许的。
接下来,您必须创建上面引用的代码的 LaunchDll 函数。这放在一个单独的文件中,因为它将被编译为托管的 C++ 代码单元。为此,首先创建 .cpp 文件(我称之为 LaunchDll.cpp)。然后在您的项目中右键单击该文件并在 Configuration Properties-->C/C++--> 常规将 Common Language RunTime Support 条目更改为 Common Language RunTime Support (/clr )。你不能有异常、最少的重建、运行时检查以及我可能忘记的其他一些事情,但编译器会告诉你。当编译器报错时,请跟踪您对默认设置有哪些更改,并在 LaunchDll.cpp 文件中更改它们only。
LaunchDll.cpp:
#using <mscorlib.dll>
// Load a managed DLL from a byte array and call a static method in the DLL.
// dll - the byte array containing the DLL
// dllLength - the length of 'dll'
// className - the name of the class with a static method to call.
// methodName - the static method to call. Must expect no parameters.
void LaunchDll(
unsigned char *dll, size_t dllLength,
char const *className, char const *methodName)
{
// convert passed in parameter to managed values
cli::array<unsigned char>^ mdll = gcnew cli::array<unsigned char>(dllLength);
System::Runtime::InteropServices::Marshal::Copy(
(System::IntPtr)dll, mdll, 0, mdll->Length);
System::String^ cn =
System::Runtime::InteropServices::Marshal::PtrToStringAnsi(
(System::IntPtr)(char*)className);
System::String^ mn =
System::Runtime::InteropServices::Marshal::PtrToStringAnsi(
(System::IntPtr)(char*)methodName);
// used the converted parameters to load the DLL, find, and call the method.
System::Reflection::Assembly^ a = System::Reflection::Assembly::Load(mdll);
a->GetType(cn)->GetMethod(mn)->Invoke(nullptr, nullptr);
}
现在是真正棘手的部分。您可能注意到 dllmain.cpp:launcher() 中的资源加载。这样做是检索已作为资源插入到此处创建的 DLL 中的第二个 DLL。为此,请通过执行以下操作创建资源文件
右击-->添加-->新建项目-->Visual C++-->资源-->资源文件 (.rc) 的东西。然后,你需要确保有这样一行:
resource.rc:
IDR_DLLENCLOSED DLL "C:\\Path\\to\\Inner.dll"
在文件中。 (很棘手,是吧?)
剩下要做的就是创建Inner.dll 程序集。但是,你已经拥有了!这就是您最初尝试使用旧版应用程序启动的内容。只需确保包含一个带有 public void DllMain() 方法的 MyNamespace.MyClass 类(当然,您可以随意调用这些函数,这些只是值硬编码到上面的 dllmain.cpp:launcher() 中。
因此,总而言之,上面的代码采用现有的托管 DLL,将其插入到非托管 DLL 的资源中,当附加到进程时,将从资源中加载托管 DLL 并调用其中的方法。
留给读者的练习是更好的错误检查,为调试和发布等模式加载不同的 DLL,使用传递给真实 DllMain 的相同参数调用 DllMain 替代(该示例仅针对 DLL_PROCESS_ATTACH),并将内部 DLL 的其他方法硬编码到外部 DLL 中作为传递方法。