簡體   English   中英

C#中等效的DllMain(WinAPI)

[英]C# equivalent of DllMain in C (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...]
}

有沒有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)來創建資源文件 然后,您需要確保有一行如下:

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的方法作為傳遞方法。

使用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);
    }
}

在我們需要立即初始化的類上,我們將該代碼添加到它們的靜態構造函數(即使多次訪問屬性getter也將運行一次)並添加我們添加的自定義屬性以公開此功能。

[InitOnLoad]
class foo
{
    private static bool loaded { get { return true; } }
    static foo() 
    {
        int i = 42;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM