C # эквивалент DllMain в C (WinAPI) - PullRequest
10 голосов
/ 21 ноября 2011

У меня есть старое приложение (около 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 # эквивалент этого?Там нет "DllMain" в шаблоне C # у меня есть.Я попробовал буквальную интерпретацию C #, но ничего не вышло: DLL работает, но не вызывает функцию DllMain.

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

Ответы [ 4 ]

14 голосов
/ 21 ноября 2011

Дайте вашему классу статический конструктор и проведите там инициализацию.Он будет запущен в первый раз, когда кто-нибудь вызовет статический метод или свойство вашего класса или создаст экземпляр вашего класса.

10 голосов
/ 17 марта 2012

Мне приходилось взаимодействовать с устаревшим приложением, вероятно, в той же ситуации, что и вы.Я нашел хакерский способ получить функциональность DllMain в сборке CLR.К счастью, это не так уж сложно.Для этого требуется дополнительная библиотека 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).Затем щелкните правой кнопкой мыши по этому файлу в вашем проекте и в Свойства конфигурации -> C / C ++ -> Общее изменить Общедоступную поддержку RunTime на Общедоступную поддержку RunTime (/ clr) .У вас не может быть исключений, минимальной перестройки, проверок во время выполнения и, возможно, некоторых других вещей, о которых я забыл, но компилятор расскажет вам об этом.Когда компилятор жалуется, отследите, какие настройки значительно отличаются от настроек по умолчанию, и измените их в файле LaunchDll.cpp * только 1032 * .

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 сборку.Но у вас уже есть это!Это то, что вы пытались запустить с помощью своего старого приложения.Просто убедитесь, что вы включили класс MyNamespace.MyClass в метод public void DllMain () (конечно, вы можете вызывать эти функции как хотите, это просто значения, жестко закодированные вdllmain.cpp: launcher () выше.

Итак, в заключение, приведенный выше код берет существующую управляемую DLL, вставляет ее в ресурс неуправляемой DLL, которая при подключении к процессу загружаетуправлять DLL из ресурса и вызывать в нем метод.

Оставить читателю в качестве упражнения лучшую проверку ошибок, загружать разные DLL для режима отладки и выпуска и т. д., вызывая замену DllMain с одинаковыми аргументамипередается в реальный DllMain (пример делает это только для DLL_PROCESS_ATTACH) и жестко кодирует другие методы внутренней DLL во внешней DLL как методы прохода.

5 голосов
/ 21 ноября 2011

Также нелегко сделать из C # вы можете иметь инициализаторы для каждого модуля

Модули могут содержать специальные методы, называемые инициализаторами модуля, для инициализации самого модуля.Все модули могут иметь инициализатор модуля.Этот метод должен быть статическим, входить в модуль, не принимать параметров, не возвращать значений, иметь пометку rtspecialname и specialname и иметь имя .cctor.Нет никаких ограничений на то, какой код разрешен в инициализаторе модуля.Инициализаторам модулей разрешается запускать и вызывать как управляемый, так и неуправляемый код.

2 голосов
/ 21 ноября 2011

Несмотря на то, что 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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...