8
votes

Chargement DLL Initialiser les classes statiques C ++

J'ai une DLL qui est chargée au temps d'exécution. La DLL s'appuie sur une variable statique pour le fonctionnement interne (c'est une carte STD :: map), cette variable est définie dans la DLL.

Lorsque j'appelle la première fonction de la DLL après le chargement, je reçois un segfault de la DLL , la carte n'a jamais été initialisée. De tout ce que j'ai lu à partir de la DLL Chargement, l'initialisation des données statiques et globales devrait arriver avant même l'appel à DllMain. P>

Pour tester une initialisation statique, j'ai ajouté une struct statique qui imprime un message, et même jeté dans une Point d'arrêt pour une bonne mesure. P>

  dll = LoadLibrary("NetSim");
  //Error Handling

  ChangeReliability   = reinterpret_cast<NetSim::ChangeReliability>
                        (GetProcAddress(dll, "ChangeReliability"));


ChangeReliability(100);


1 commentaires

Y a-t-il une référence à votre objet statica ? Sinon, cela pourrait être optimisé.


5 Réponses :


0
votes

Bien que je ne puisse pas savoir pourquoi l'initialisation échoue, une solution de contournement serait de créer et d'initialiser explicitement les variables de votre DLLMain. Selon Meilleures pratiques de Microsoft papier, vous devez éviter d'utiliser la fonction d'allocation CRT dans DLLMAIN. J'utilise plutôt les fonctions d'allocation de tas de Windows, avec un allocator personnalisé (Remarque: Code non testé, mais devrait être plus ou moins à droite):

template<typename T> struct HeapAllocator
{
    typedef T value_type, *pointer, &reference;
    typedef const T *const_pointer, &const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    HeapAllocator() throw() { }
    HeapAllocator(const HeapAllocator&) throw() { }
    typedef<typename U>
    HeapAllocator(const HeapAllocator<U>&) throw() { }

    pointer address(reference x) const { return &x; }
    const_pointer address(const_reference x) const { return &x; }

    pointer allocate(size_type n, HeapAllocator<void>::const_pointer hint = 0)
    {
        LPVOID rv = HeapAlloc(GetProcessHeap(), 0, n * sizeof(value_type));
        if (!rv) throw std::bad_alloc();
        return (pointer)rv;
    }

    void deallocate(pointer p, size_type n)
    {
        HeapFree(GetProcessHeap(), 0, (LPVOID)p);
    }

    size_type max_size() const throw()
    {
        // Make a wild guess...
        return (2 * 1024 * 1024 * 1024) / sizeof(value_type);
    }

    void construct(pointer p, const_reference val)
    {
        new ((void*)p) T(val);
    }

    void destroy(pointer p)
    {
        p->~T();
    }
};

std::map<foo, HeapAllocator> *myMap;

BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason, LPVOID lpReserved)
{
    switch(ul_reason) {
        case DLL_PROCESS_ATTACH:
            myMap = (std::map<foo, HeapAllocator> *)HeapAlloc(GetProcessHeap(), 0, sizeof(*myMap));
            if (!myMap) return FALSE; // failed DLL init

            new ((void*)myMap) std::map<foo, HeapAllocator>;
            break;
        case DLL_PROCESS_DETACH:
            myMap->~map();
            HeapFree(GetProcessHeap(), 0, (LPVOID)myMap);
            break;
    }
    return TRUE;
}


2 commentaires

L'allocation de la mémoire dynamique dans DLLMAIN est dangereuse et doit être évitée. Par exemple. Voir cet article de MS: Go.microsoft.com/fwlink/?Linkid=84138


Oui, mais qu'en est-il des objets stockés sur la carte? Ils ont destructeurs de leur propre, qui seront appelés de DllMain pendant le déchargement. Cette approche peut être apportée au travail, mais personnellement, je le réserverais pour des cas extrêmes lorsque tout le reste n'est pas une option.



10
votes

Lorsque vous avez lié votre DLL, l'interrupteur d'entrée était-il spécifié? Si oui, cela remplacera la valeur par défaut de la liaison qui est DLLMainCrTstartup. En conséquence, _crt_init ne sera pas appelé et, à son tour, les initialisateurs globaux ne seront pas appelés qui entraîneront des données globales (statiques) ininitialisées.

Si vous spécifiez / une entrée pour votre propre point d'entrée, vous devez appeler _crt_init pendant la joignée et le processus de processus de processus.

Si vous ne spécifiez pas / vous n'enregistrez pas / n'enregis pas, le linker doit utiliser le point d'entrée du CRT qui appelle _Crt_init sur le processus attacher / détacher avant d'appeler dans votre DLLMain.


2 commentaires

/ L'entrée n'est pas spécifiée. Je suppose que de la documentation de DllMain de Microsoft. Je vais essayer de l'appeler explicitement.


Bien cela semble fonctionner. La documentation de Microsoft est très déroutante. A fini par suivre les étapes suivantes dans support.microsoft.com/kb/94248 Section 2, et cela a fonctionné . Merci pour l'aide.



10
votes

J'aimerais souligner que les objets statiques complexes des DLL doivent être évités.

N'oubliez pas que les intialisateurs statiques d'une DLL sont appelés de DLLMain, et donc toutes les restrictions sur le code DLLMain s'appliquent aux constructeurs et aux destructeurs d'objets statiques. En particulier, STD :: La carte peut allouer une mémoire dynamique pendant la construction, ce qui peut entraîner des résultats imprévisibles si C ++ Runtime n'est pas encore initialisé (au cas où vous utilisez des heures d'exécution liées dynamiquement).

Il y a un bon article sur l'écriture Dllmain: Meilleures pratiques pour créer des DLL .

Je suggérerais de changer votre objet de carte statique sur un pointeur statique (qui peut être utilisé en toute sécurité à zéro) et ajout d'une fonction distincte-exportée de la DLL pour l'initialisation ou à l'aide de l'initialisation paresseuse (c.-à-d. Vérifier le pointeur avant d'y accéder et Créez l'objet si c'est NULL).


5 commentaires

Votage-celles-ci, puisque ATZ amène un excellent point. Vous voulez vraiment éviter de faire une grande partie de quelque chose sous la serrure de la chargeuse et même à vous limiter aux actions que vous savez sont intrinsèquement sûres (la liste n'est pas longue, c'est sûr).


Paresseux init ne vous aidera pas - vous finiriez par appeler des fonctions CRT pour libérer la mémoire de DLLMain () lorsque la DLL est déchargée.


@bdonlan - Cela dépend de la mise en œuvre. La façon dont je l'ai formée ci-dessus, il n'y a pas de répartition du tout et l'objet sera divulgué sur la DLL Décharger :). Mais je suis d'accord avec votre point, qu'utiliser une initialisation paresseuse ici complique le nettoyage.


En effet. Voir ma réponse mise à jour pour une autre possibilité - si la carte est allouée à l'aide d'un allocator C ++ personnalisé en fonction du tas de processus, il n'y a aucun problème à l'ordre d'initialisation (Kernel32.dll sera toujours initialisé en premier).


@atzz - avec des fuites d'exécution liées statique sera effacée sur le déchargement de la DLL. Dans ce cas, l'initialisation paresseuse fonctionne simplement bien.



2
votes

En réalité, il est probable que vous faites une mauvaise hypothèse:

L'initialisation des données de chargement, statique et globale devrait se produire avant même l'appel à DLLMain.

Voir l'élément 4 de l'efficacité C ++:

L'ordre d'initialisation de Objets statiques non locaux définis dans différentes unités de traduction sont indéfini

La raison pour laquelle vous assurer une commande d'initialisation «correcte» est impossible, et donc la norme C ++ l'abandonne simplement et laissez simplement cela comme indéfini.

Donc, si votre DLLMain est dans un fichier différent de celui du code où la variable statique est déclarée, le comportement est indéfini et vous avez de très bonnes chances d'obtenir DLLMain appelé avant que l'initialisation soit réellement effectuée.

solution est assez simple et décrit dans le même élément de C ++ efficace (BTW: Je vous recommande vivement de lire ce livre!), et nécessite de déclarer la variable statique à l'intérieur d'une fonction, qui le renvoie simplement.


5 commentaires

Vous êtes confusant Standard C ++ (qui ne connaît même pas les DLL) et Windows. Sous Windows, l'initialisation statique et globale se produit toujours dans l'ordre non spécifié, mais cela se produit de DLLMain.


L'ordre d'initialisation est spécifique au compilateur. Windows connaît quelque chose sur l'initialisation (et comment cela pourrait-il?) - Cela appelle simplement DllMain avec dll_process_attach. Il est possible qu'un compilateur initialise toute variable statique avant DLLMain, mais elle n'est pas garantie. Au moins, je me souviens que avec VC 6.0 (oui, il y a beaucoup de temps), j'ai eu le même problème que celui rapporté et le coupable a finalement été une hypothèse erronée sur l'initialisation statique.


@ROBERTO: déclarer des variables statiques à l'intérieur des fonctions est assez dangereuse car elle n'est pas la sécurité du fil (et peut donc conduire à une double initialisation). Les initialisateurs statiques à l'intérieur des fonctions doivent être utilisés uniquement pour des variables de pod simples uniquement.


Si vous souhaitez assurer la sécurité du thread, vous devez utiliser un objet de synchronisation. S'appuyant sur des données statiques non locales pour la sécurité du fil n'est définitivement pas une solution.


@Roberto: Pourquoi? Les variables globales sont initialisées dans un seul fil. (Je doute que CRT ne soit pas initialisé même en utilisant une liaison dynamique) et une initialisation restrictive peut être effectuée à ce stade.



-1
votes

La mise en œuvre "classique" singleton simple fonctionnera:

std::map<Key,Value>& GetMap() {
  static std::map<Key,Value> the Map;
  return theMap;
}


4 commentaires

Il a toujours le problème de la classique, qui se produira dans DLLMain sur DLL Décharger (et peut avoir des conséquences amusantes: blogs.msdn.com/b/oldnewthing/archive/9951750.aspx ).


Vous êtes sûr? C'est sur les sections critiques. Autant que je comprenne, les CRT modernes ne se soucient pas de la répartition de la mémoire lors de l'arrêt. Le système d'exploitation va nettoyer quand même.


Sur DLL Décharger, destructeurs de Themap seront appelés de DllMain. Ce destructeur distribuera la mémoire dynamique détenue par la carte. Le tas CRT est protégé avec une section critique, les effets décrits dans la liaison peuvent se produire. En outre, les destructeurs de clé et Les objets sur la carte seront appelés aussi, et nous ne savons pas s'ils sont dllmain-sécurité ou non.


@atzz: Je viens de résoudre un bug de ces conséquences. Ne peut que le confirmer!