C # эквивалент DllMain в C (WinAPI)

У меня есть более старое приложение (около 2005 года), которое принимает плагины dll. Приложение было первоначально разработано для плагинов Win32 C, но у меня есть рабочий шаблон dll C #. Моя проблема: мне нужно выполнить некоторую разовую инициализацию, которая в dll Win32 C будет выполнена в 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...] } 

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

Мне пришлось взаимодействовать с устаревшим приложением, вероятно, в той же ситуации, что и у вас. Я нашел хакерский способ получить функциональность DllMain в сборке CLR. К счастью, это не слишком сложно. Для этого требуется дополнительная DLL, но вам не требуется развертывать дополнительную DLL, поэтому вы все равно можете «поставить DLL в эту директорию и приложение будет загружать ее» парадигмой.

Сначала вы создаете простую обычную C ++ DLL, которая выглядит следующим образом:

dllmain.cpp:

 #define WIN32_LEAN_AND_MEAN #include  #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(h), MAKEINTRESOURCEA(IDR_DLLENCLOSED), "DLL"); if (res) { HGLOBAL dat = ::LoadResource(static_cast(h), res); if (dat) { unsigned char *dll = static_cast(::LockResource(dat)); if (dll) { size_t len = SizeofResource(static_cast(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; } 

Обратите внимание на создание streamа. Это значит, что Windows довольна, потому что вызов управляемого кода внутри точки входа DLL – это нет-нет.

Затем вам нужно создать эту функцию LaunchDll для кода выше ссылки. Это происходит в отдельном файле, поскольку он будет скомпилирован как управляемый блок кода C ++. Для этого сначала создайте файл .cpp (я назвал его LaunchDll.cpp). Затем щелкните правой кнопкой мыши на этом файле в вашем проекте и в Configuration Properties -> C / C ++ -> General измените запись поддержки RunTime Common Language на CommonTown Support (/ clr) . У вас не могут быть исключения, минимальная перестройка, проверки времени выполнения и, возможно, некоторые другие вещи, о которых я забыл, но компилятор расскажет вам об этом. Когда компилятор жалуется, отследите, какие настройки вы сильно измените по умолчанию и измените их только в файле LaunchDll.cpp.

LaunchDll.cpp:

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

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

В качестве упражнения для читателя лучше проверять ошибки, загружать разные DLL-файлы для режима Debug и Release и т. Д., Вызывая замену DllMain теми же аргументами, которые передаются в реальный DllMain (пример только для DLL_PROCESS_ATTACH), а hardcoding – другой методы внутренней DLL во внешней DLL в качестве сквозных методов.

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

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

Несмотря на то, что C # напрямую не поддерживает инициализацию модуля, мы можем реализовать его с помощью рефлексивных и статических конструкторов. Для этого мы можем определить пользовательский атрибут и использовать его для поиска classов, которые необходимо инициализировать при загрузке модуля:

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

в classах, которые нам нужно инициализировать немедленно, мы добавляем этот код к своему статическому конструктору (который будет запускаться один раз, даже если к ресурсу getter обращаются несколько раз) и добавьте добавленный пользовательский атрибут, чтобы открыть эту функцию.

 [InitOnLoad] class foo { private static bool loaded { get { return true; } } static foo() { int i = 42; } } 
  • Установите системный часовой пояс из .NET.
  • Создание приложения без windows
  • Как запустить дочерний процесс, который требует повышения и ожидания?
  • Обнаруживать, работает ли программа с полными правами администратора
  • Как преобразовать std :: string в LPCWSTR в C ++ (Unicode)
  • Добавление внешней библиотеки в проект Qt Creator
  • Что такое насос сообщений?
  • Как открыть встроенный диалог копирования файлов?
  • Возможно ли программно добавить папки на панель быстрого доступа Windows 10 в окне проводника?
  • Как процесс Win32 может получить pid своего родителя?
  • Получить текущую позицию курсора
  • Давайте будем гением компьютера.