C#相当于C中的DllMain(WinAPI)

C#相当于C中的DllMain(WinAPI),c#,winapi,dll,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#模板中没有“

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

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

使用C也不容易,您可以使用

模块可能包含称为模块初始化器的特殊方法来初始化模块本身。 所有模块都可能有一个模块初始值设定项。此方法应是静态的,是模块的一个成员,不接受任何参数,不返回任何值,用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);
    }
}

我不得不在与您相同的情况下与遗留应用程序进行交互。我找到了一种在CLR程序集中获得DllMain功能的方法。幸运的是,这并不难。它需要一个额外的DLL,但不需要您部署一个额外的DLL,这样您仍然可以使用“在该目录中放置一个DLL,应用程序将加载它”范例

首先,你做一个简单的规则C++,它看起来如下:

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;
}
在文件中。(很棘手,嗯?)

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

因此,总而言之,上面的代码获取一个现有的托管DLL,将其插入非托管DLL的资源中,该资源在连接到进程时,将从资源中加载托管DLL并调用其中的方法



留给读者的练习是更好的错误检查、为调试和发布加载不同的DLL等模式,使用传递给实际DllMain的相同参数调用DllMain替换(该示例仅针对DLL_进程_附加),并在外部DLL中硬编码内部DLL的其他方法作为传递方法。

什么的一次性初始化?由于DllMain是过程性的,而C#是面向对象的,我想知道您需要在无法从内部调用的类之外执行什么样的初始化?这还取决于您正在构建的应用程序的类型,控制台、web、windows客户端、windows服务等。您不应该在
DllMain
@Ritch Melton中进行重要的初始化。请考虑加载插件之类的事情,因为宿主应用程序不知道DLL中到底有什么。在这种情况下,您可能希望您的插件向应用程序注册,以便在加载时可以找到它们eded没有将主机绑定到插件。你会如何利用它?我认为你可能必须直接反汇编和插入IL代码。parapura是正确的。如果不直接修改IL,就无法做到这一点……希望有一天这会得到修复。请参阅(使用Einar Egilsson的InjectModuleInitializer)模块初始值设定项并不总是DllMain功能的好替代品,因为只有在DLL中调用某个方法时才会调用它们。如果您遇到一种情况,即旧版应用程序仅加载DLL并期望它执行某些操作,模块初始值设定项将使您失败(就像他们对我所做的那样)。有关此问题的解决方案,请参阅本问题的其他部分。这并不能回答问题,因为OP正在寻找与旧版应用程序交互的方法。@RitchMelton我不这么认为。OP正在尝试从托管应用程序或托管CL的非托管应用程序对托管DLL进行一次性初始化R.在第一种情况下,您将在启动时在MyMain类构造函数中添加代码,而在第二种情况下,您最有可能将代码放在最初传递给
ExecuteInDefaultAppDomain
的方法中。在这两种情况下,您都会得到OP想要的功能。@RitchMelton他正试图在“C#dll”我仍然不知道它与托管进程是否被管理有多大关系。或者运行一个构建后步骤来调整你的C#程序集:@mheyman:但是新的“wrapper.dll”不能像旧版应用程序使用interner.dll那样使用。interner.dll是一个C#程序集,我的旧版应用程序正在调用assembly.GetTypes()在inner.dll上,如果我使用wrapper.dll,它将返回一些c++/clr内容,而不是inner.dll的类型。这对我来说是一个大问题,我如何公开inner.dll的类型?@Blud你找到了这个问题的解决方案吗?程序集已加载,因此可以通过迭代所有加载的程序集来找到它。但是,使用nuget pac除非您需要调用非托管DLL,否则kage似乎是最干净的解决方案。这适用于在DLL_进程_附加情况下执行的操作,但其他情况(DLL_进程_分离、DLL_线程_附加、DLL_线程_分离)又如何?@thomasleveque这不是OP的问题。“我需要做一些一次性初始化。”C#静态构造函数完成一次性初始化。是的,我知道这不是OP所要求的……但它是
#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;
}
#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);
}
IDR_DLLENCLOSED DLL "C:\\Path\\to\\Inner.dll"