C#中等效的DllMain(WinAPI)

时间:2011-11-21 02:48:12

标签: c# winapi dll

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

4 个答案:

答案 0 :(得分:14)

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

答案 1 :(得分:10)

我必须与遗留应用程序进行交互,可能与您的情况相同。我发现了一种在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)。然后右键单击项目中的该文件,并在 配置属性 - &gt; C / C ++ 中 - &gt; 常规 公共语言运行时支持 条目更改为 公共语言运行时支持( / 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中。为此,请通过执行此操作来创建资源文件 右键单击 - &gt; 添加 - &gt; 新项目 - &gt; Visual C ++ - &gt; 资源 - &gt; 资源文件(.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的其他方法。

答案 2 :(得分:5)

从C#开始也不容易,你可以有module initializers

  

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

答案 3 :(得分:2)

即使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;
    }
}