Équivalent C # de DllMain en C (WinAPI)

J’ai une application plus ancienne (ca. 2005) qui accepte les plugins dll. L’application a été conçue à l’origine pour les plug-ins Win32 C, mais j’ai un modèle de travail en dll C #. Mon problème: je dois faire une initialisation unique, qui dans une DLL Win32 C serait faite dans DllMain:

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

Existe-t-il un équivalent C #? Il n’y a pas de “DllMain” dans le modèle C # que j’ai. J’ai essayé une interprétation littérale en C #, mais rien ne va: la dll fonctionne mais elle ne déclenchera pas la fonction DllMain.

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

Donnez à votre classe un constructeur statique et faites votre initialisation à cet endroit. Il sera exécuté la première fois que quelqu’un appelle une méthode statique ou une propriété de votre classe ou construit une instance de votre classe.

J’ai dû interagir avec une application existante probablement dans la même situation que vous. J’ai trouvé un moyen astucieux d’obtenir la fonctionnalité DllMain dans un assemblage CLR. Heureusement, ce n’est pas trop difficile. Il nécessite une DLL supplémentaire, mais ne nécessite pas de déployer une DLL supplémentaire pour que vous puissiez toujours avoir le paradigme “mettre une DLL dans ce répertoire et l’application le chargera”.

Tout d’abord, vous créez une DLL C ++ régulière simple et qui ressemble à ce qui suit:

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

Notez la création du fil. Ceci permet de garder Windows heureux, car l’appel de code géré dans un point d’entrée de DLL est interdit.

Ensuite, vous devez créer cette fonction LaunchDll avec le code ci-dessus références. Cela va dans un fichier séparé car il sera compilé comme une unité de code C ++ gérée. Pour ce faire, créez d’abord le fichier .cpp (je l’ai appelé LaunchDll.cpp). Cliquez ensuite avec le bouton droit de la souris sur ce fichier dans votre projet et dans Propriétés de configuration -> C / C ++ -> Général, modifiez l’entrée Common Language RunTime Support en Common Language RunTime Support (/ clr) . Vous ne pouvez pas avoir d’exceptions, une reconstruction minimale, des contrôles d’exécution et probablement d’autres choses que j’ai oubliées, mais le compilateur vous en parlera. Lorsque le compilateur se plaint, repérez les parameters par défaut que vous modifiez beaucoup et modifiez-les uniquement dans le fichier 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::Ssortingng^ cn = System::Runtime::InteropServices::Marshal::PtrToSsortingngAnsi( (System::IntPtr)(char*)className); System::Ssortingng^ mn = System::Runtime::InteropServices::Marshal::PtrToSsortingngAnsi( (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); } 

Maintenant pour la partie vraiment délicate. Vous avez probablement remarqué le chargement de la ressource dans dllmain.cpp: launcher (). Cela permet de récupérer une deuxième DLL insérée en tant que ressource dans la DLL créée ici. Pour ce faire, créez un fichier de ressources en faisant un clic droit -> Ajouter -> Nouvel élément -> Visual C ++ -> Ressource -> Fichier de ressources (.rc) . Ensuite, vous devez vous assurer qu’il y a une ligne comme:

resource.rc:

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

dans le fichier. (Tricky, hein?)

La seule chose à faire est de créer cet assemblage Inner.dll . Mais vous l’avez déjà! C’est ce que vous tentiez de lancer avec votre ancienne application. Assurez-vous simplement d’inclure une classe MyNamespace.MyClass avec une méthode publique void DllMain () (bien sûr, vous pouvez appeler ces fonctions comme bon vous semble, il ne s’agit que des valeurs codées en dur dans dllmain.cpp: launcher () ci-dessus.

Donc, en conclusion, le code ci-dessus prend une DLL gérée existante, l’insère dans une ressource d’une DLL non gérée qui, une fois attaché à un processus, chargera la DLL gérée à partir de la ressource et y appellera une méthode.

Laissant un exercice au lecteur, il vaut mieux vérifier les erreurs, charger différentes DLL pour le mode Debug, Release, etc., appeler le substitut DllMain avec les mêmes arguments transmis au réel DllMain (l’exemple le fait uniquement pour DLL_PROCESS_ATTACH), et coder en dur méthodes de la DLL interne dans la DLL externe en tant que méthodes de transfert.

Aussi pas facile à faire à partir de C #, vous pouvez avoir un initialiseurs par module

Les modules peuvent contenir des méthodes spéciales appelées initialiseurs de module pour initialiser le module lui-même. Tous les modules peuvent avoir un initialiseur de module. Cette méthode doit être statique, membre du module, ne prendre aucun paramètre, ne renvoyer aucune valeur, être marquée avec rtspecialname et specialname et être nommée .cctor. Il n’y a pas de limitation quant au code autorisé dans un initialiseur de module. Les initialiseurs de module sont autorisés à s’exécuter et à appeler du code géré et du code non géré.

Même si C # ne prend pas directement en charge l’initialisation de module, nous pouvons l’implémenter à l’aide de constructeurs de reflection et statiques. Pour ce faire, nous pouvons définir un atsortingbut personnalisé et l’utiliser pour rechercher les classes devant être initialisées lors du chargement du module:

 public class InitOnLoadAtsortingbute : Atsortingbute {} 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.GetCustomAtsortingbutes(typeof(InitOnLoadAtsortingbute), 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); } } 

Sur les classes que nous devons initialiser immédiatement, nous ajoutons ce code à leur constructeur statique (qui sera exécuté une fois même si l’access à la propriété est consulté plusieurs fois) et ajoutons l’atsortingbut personnalisé que nous avons ajouté pour exposer cette fonctionnalité.

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