我有一个较旧的应用程序(约2005年)接受DLL插件。该应用程序最初是为Win32 C插件设计的,但我有一个有效的C#dll模板。我的问题:我需要做一些一次性初始化,在Win32 C dll中将在DllMain中完成:
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
[one-time stuff here...]
}
有没有C#相当于此?我拥有的C#模板中没有“DllMain”。我尝试了一个文字C#解释,但没有去:dll工作,但它不会触发DllMain函数。
public static bool DllMain(int hModule, int reason, IntPtr lpReserved) {
[one time stuff here...]
}
给你的类一个静态构造函数并在那里进行初始化。它将在任何人第一次调用类的静态方法或属性或构造类的实例时运行。
我不得不与遗留应用程序进行交互,可能与您的情况相同。我发现了一种在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 ++ - > General中,将Common Language RunTime Support条目更改为Common Language RunTime Support(/ clr)。你不能有异常,最小化重建,运行时检查以及我忘记的其他一些事情,但编译器会告诉你。当编译器抱怨时,跟踪您从默认设置中更改的设置,并仅在LaunchDll.cpp文件中更改它们。
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)来创建资源文件。然后,您需要确保有一行如下:
我们走吧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的方法作为传递方法。
从C#也不容易做到你可以有一个每module initializers
模块可能包含称为模块初始值设定项的特殊方法,用于初始化模块本身。所有模块都可能有一个模块初始化程序。此方法应为静态,模块的成员,不带参数,不返回值,标记为rtspecialname和specialname,并命名为.cctor。模块初始化程序中允许的代码没有限制。允许模块初始化程序运行并调用托管代码和非托管代码。
尽管C#不直接支持模块初始化,但我们可以使用反射和静态构造函数来实现它。为此,我们可以定义一个自定义属性,并使用它找到需要在模块加载时初始化的类:
public class InitOnLoadAttribute : Attribute {}
private void InitAssembly(Assembly assembly)
{
foreach (var type in GetLoadOnInitTypes(assembly)){
var prop = type.GetProperty("loaded", BindingFlags.Static | BindingFlags.NonPublic); //note that this only exists by convention
if(prop != null){
prop.GetValue(null, null); //causes the static ctor to be called if it hasn't already
}
}
}
static IEnumerable<Type> GetLoadOnInitTypes(Assembly assembly)
{
foreach (Type type in assembly.GetTypes())
{
if (type.GetCustomAttributes(typeof(InitOnLoadAttribute), true).Length > 0){
yield return type;
}
}
}
public MyMainClass()
{
//init newly loaded assemblies
AppDomain.CurrentDomain.AssemblyLoad += (s, o) => InitAssembly(o.LoadedAssembly);
//and all the ones we currently have loaded
foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies()){
InitAssembly(assembly);
}
}
在我们需要立即初始化的类上,我们将该代码添加到它们的静态构造函数(即使多次访问属性getter也将运行一次)并添加我们添加的自定义属性以公开此功能。
[InitOnLoad]
class foo
{
private static bool loaded { get { return true; } }
static foo()
{
int i = 42;
}
}