C # equivalente de DllMain en C (WinAPI)

Tengo una aplicación más antigua (aproximadamente 2005) que acepta complementos dll. La aplicación fue diseñada originalmente para plugins Win32 C, pero tengo una plantilla dll C # en funcionamiento. Mi problema: necesito hacer una inicialización única, que en un DLL de Win32 C se haría en DllMain:

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { [one-time stuff here...] } 

¿Hay un equivalente de C # de esto? No hay “DllMain” en la plantilla de C # que tengo. Probé una interpretación literal de C #, pero no voy: el dll funciona pero no activará la función DllMain.

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

Dale a tu clase un constructor estático y haz tu inicialización allí. Se ejecutará la primera vez que alguien llama a un método estático o propiedad de su clase o construye una instancia de su clase.

Tuve que interactuar con una aplicación heredada, probablemente en la misma situación que tú. Encontré una forma estrafalaria de obtener la funcionalidad DllMain en un ensamblado CLR. Afortunadamente, no es muy difícil. Requiere una DLL adicional pero no requiere que implemente una DLL adicional para que pueda tener el paradigma de “poner una DLL en ese directorio y la aplicación lo cargará”.

En primer lugar, crea una DLL normal simple de C ++ que se parece a la siguiente:

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

Tenga en cuenta la creación de hilo. Esto es para mantener a Windows contento porque llamar al código administrado dentro de un punto de entrada DLL es un no-no.

A continuación, debe crear esa función LaunchDll del código de referencias anteriores. Esto va en un archivo separado porque se comstackrá como una unidad de código administrada de C ++. Para hacer esto, primero crea el archivo .cpp (lo llamé LaunchDll.cpp). A continuación, haga clic con el botón derecho en ese archivo en su proyecto y en Propiedades de configuración -> C / C ++ -> General cambie la entrada Soporte de Common Language RunTime al Soporte de Common Language RunTime (/ clr) . No puede haber excepciones, reconstrucción mínima, comprobaciones de tiempo de ejecución y, probablemente, algunas otras cosas que olvidé pero del comstackdor le contarán. Cuando el comstackdor se queja, rastrea qué configuraciones cambias mucho de las predeterminadas y cámbialas solo en el archivo 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); } 

Ahora para la parte realmente difícil. Probablemente haya notado la carga de recursos en dllmain.cpp: launcher (). Lo que hace es recuperar una segunda DLL que se ha insertado como un recurso en la DLL que se está creando aquí. Para hacer esto, cree un archivo de recursos haciendo clic con el botón derecho -> Agregar -> Nuevo elemento -> Visual C ++ -> Recurso -> Archivo de recursos (.rc) . Luego, debes asegurarte de que haya una línea como esta:

resource.rc:

 IDR_DLLENCLOSED DLL "C:\\Path\\to\\Inner.dll" 

en el archivo. (Difícil, ¿eh?)

Lo único que queda por hacer es crear ese ensamblaje de Inner.dll . Pero, ¡ya lo tienes! Esto es lo que intentaba lanzar con su aplicación heredada en primer lugar. Simplemente asegúrese de incluir una clase MyNamespace.MyClass con un método público vacío DllMain () (por supuesto, puede llamar a estas funciones como quiera, estos son solo los valores codificados en dllmain.cpp: launcher () arriba.

Entonces, en conclusión, el código anterior toma una DLL administrada existente, la inserta en un recurso de una DLL no administrada que, al conectarse a un proceso, cargará la DLL administrada desde el recurso y llamará a un método en ella.

Dejar como ejercicio al lector es una mejor comprobación de errores, cargando diferentes DLL para el modo Depurar y liberar, etc., llamando al sustituto DllMain con los mismos argumentos pasados ​​al DllMain real (el ejemplo solo lo hace para DLL_PROCESS_ATTACH), y hardcoding otro métodos de la DLL interna en la DLL externa como métodos de paso.

Además, no es fácil de hacer desde C #, puede tener un inicializador por módulo

Los módulos pueden contener métodos especiales llamados inicializadores de módulo para inicializar el módulo. Todos los módulos pueden tener un inicializador de módulo. Este método debe ser estático, ser miembro del módulo, no tomar parámetros, no devolver ningún valor, estar marcado con rtspecialname y specialname, y ser nombrado .cctor. No hay limitaciones sobre qué código está permitido en un inicializador de módulo. Los inicializadores de módulos pueden ejecutarse y llamar tanto al código administrado como al no administrado.

Aunque C # no admite directamente la inicialización del módulo, podemos implementarlo utilizando reflexiones y constructores estáticos. Para hacer esto, podemos definir un atributo personalizado y usarlo para encontrar las clases que necesitan ser inicializadas en la carga del módulo:

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

en las clases que necesitamos inicializar inmediatamente agregamos ese código a su constructor estático (que se ejecutará una vez incluso si se accede al getter de propiedades varias veces) y agregamos el atributo personalizado que agregamos para exponer esta funcionalidad.

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