C#中等效的DllMain(WinAPI)

MrS*_*kly 10 c# dll winapi

我有一个较旧的应用程序(约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...]
}
Run Code Online (Sandbox Code Playgroud)

有没有C#相当于此?我拥有的C#模板中没有"DllMain".我尝试了一个文字C#解释,但没有去:dll工作,但它不会触发DllMain函数.

public static bool DllMain(int hModule, int reason, IntPtr lpReserved) {
  [one time stuff here...]
}
Run Code Online (Sandbox Code Playgroud)

Ray*_*hen 15

给你的类一个静态构造函数并在那里进行初始化.它将在任何人第一次调用类的静态方法或属性或构造类的实例时运行.

  • @ThomasLevesque然后问一个新问题.不要劫持现有的问题. (6认同)
  • 因为寻找第二个问题答案的人不会找到它。这是一个问答网站而不是讨论网站。 (3认同)
  • @ThomasLevesque 这不是OP的问题。“我需要做一些一次性初始化。” C# 静态构造函数完成一次性初始化。 (2认同)

mhe*_*man 11

我不得不与遗留应用程序进行交互,可能与您的情况相同.我发现了一种在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;
}
Run Code Online (Sandbox Code Playgroud)

注意线程创建.这是为了让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);
}
Run Code Online (Sandbox Code Playgroud)

现在是非常棘手的部分.您可能已经注意到dllmain.cpp中的资源加载:launcher().这样做是检索已作为资源插入的第二个DLL到此处创建的DLL中.要执行此操作,请通过 右键单击 - > 添加 - > 新项 - > Visual C++ - > 资源 - > 资源文件(.rc)来创建资源文件.然后,您需要确保有一行如下:

RESOURCE.RC:

IDR_DLLENCLOSED DLL "C:\\Path\\to\\Inner.dll"
Run Code Online (Sandbox Code Playgroud)

在文件中.(整蛊,嗯?)

剩下要做的唯一事情是创建Inner.dll程序集.但是,你已经拥有它了!这就是您首先尝试使用旧版应用程序启动的内容.只需确保包含带有public void DllMain()方法的MyNamespace.MyClass类(当然,您可以随意调用这些函数,这些只是上面硬编码到dllmain.cpp:launcher()中的值.

因此,总之,上面的代码采用现有的托管DLL,将其插入到非托管DLL的资源中,在附加到进程后,将从资源加载托管DLL并调用其中的方法.

作为练习向读者留下更好的错误检查,为调试和释放等模式加载不同的DLL,使用传递给真实DllMain的相同参数调用DllMain替换(该示例仅用于DLL_PROCESS_ATTACH),以及硬编码其他外部DLL中的内部DLL的方法作为传递方法.


par*_*mar 5

使用C#也不容易,你可以拥有每个模块的初始化程序

模块可能包含称为模块初始值设定项的特殊方法,用于初始化模块本身.所有模块都可能有一个模块初始化程序.此方法应为静态,模块的成员,不带参数,不返回值,标记为rtspecialname和specialname,并命名为.cctor.模块初始化程序中允许的代码没有限制.允许模块初始化程序运行并调用托管代码和非托管代码.

  • 请参阅http://stackoverflow.com/a/9739535/240845(使用Einar Egilsson的InjectModuleInitializer) (2认同)