Partage
  • Partager sur Facebook
  • Partager sur Twitter

FreeLibrary n'est pas très coopératif

Sujet résolu
    23 avril 2022 à 16:10:23

    Bonjour a tous,
    Dans le cadre d'un frontend retro, je dois charger et décharger des lib dynamiques pour changer d'émulateur.
    Tout fonctionne très bien hormis un irréductible MAME (émulateur arcade) qui pour une raison qui m'échappe refuse de se décharger.
    Pour éviter de mettre en cause mon code j'ai reproduit un exemple minimal, a savoir, charger, décharger, attendre 30sec(le temps de vérif. dans process explorer) et vous vous en doutez, il est toujours la, tapis dans l'obscurité.
    Le code de test:
    //Code java qui appelle ma lib, dans laquelle se trouve le code plus bas
    System.load("D:\\dev\\prj\\retro-player-emulator-libretro\\bin/libpxl.dll");
    LibRetro l = new LibRetro(Path.of("D:\\dev\\prj\\retro-player/data/local/emulators/libretro"));
    l.loadCore(LibRetroCore.MAME);
    Thread.sleep(30000);
    PXLEXPORT void loadCore(const char* libPath) {
       auto hndl = LoadLibraryA(libPath);
       auto r = FreeLibrary(hndl);
       if(r) {
           pxlLogInfo("UNLOAD LIB");
       }
    }
    Si quelqu'un a une idée de pourquoi cette lib(libretro_mame.dll) est capricieuse alors que les autres, aucun soucis
    • Partager sur Facebook
    • Partager sur Twitter
      24 avril 2022 à 7:57:36

      Moi, j'ai pas mal d'idée. :-°

      Thread en tache de fond,

      verrous sur des ressources système bloquant l'appel au Dllmain de fin

      bugs dans le Dllmain

      By Design

      etc...

      • Partager sur Facebook
      • Partager sur Twitter
      Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
        24 avril 2022 à 11:20:10

        Salut,

        L'exemple si dessus est mono thread, on est seulement dans le main thread

        verrou, possible, comment l'identifier et pourquoi spécifiquement sur cette ressource?

        Je n'ai pas implémenté dllmain, et je n'en ai pas trouve de trace dans le repo de libretro_mame non plus (https://github.com/libretro/mame/search?q=DLL_THREAD_ATTACH) donc c'est celui de win32 je suppose?

        by design, tu veux parler de la lib qui devrait être déchargée ou de celle qui décharge?

        Possible que la taille de la lib aie un impact?, elle fait 300mo contre 5-10 pour les autres émus

        • Partager sur Facebook
        • Partager sur Twitter
          25 avril 2022 à 14:00:51

          >L'exemple si dessus est mono thread, on est seulement dans le main thread

          Et ?

          Rien n'interdit une Dll de créer ses propres threads, et c'est souvent dés son chargement.

          >verrou, possible, comment l'identifier et pourquoi spécifiquement sur cette ressource?

          Pourquoi une ? Il en suffit d'une.

          Threads, mémoires partagées, handle de processus, etc...

          Le plus simple, si c'est de l'Open-Source et que ce n'est pas un énorme projet, c'est de regarder directement dans les sources.

          >Je n'ai pas implémenté dllmain,

          Et ?

          Ce n'est pas celle de la Dll "chargeante" qui est appelée mais celle de la Dll chargée, des Dll que la Dll chargée utilise, et ainsi de suite (tant que c'est un chargement "statique" de Dll). Donc, même si la Dll "racine" n'en a pas, c'est pas une raison.

          >donc c'est celui de win32 je suppose?

          C'est à dire ?

          Oui, l'OS a un mécanisme de protection qui empêche de virer de la mémoire les segments d'une Dll quand il y a encore du code qui tourne ou des données accessibles via un compteur.

          >by design, tu veux parler de la lib qui devrait être déchargée ou de celle qui décharge?

          Celle qui est chargée.

          Votre approche n'est viable que si vous n'utilisez que des composants logiciels qui ont été conçu pour être déchargeable, comme les composant COM, mais c'est rarement le cas de Dll "nues".

          • Partager sur Facebook
          • Partager sur Twitter
          Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
            25 avril 2022 à 15:05:13

            J'avais pris comme assomption que la lib etait naturellement dechargeable, d'autant plus qu'elle a une fonction dediee au deinit.

            Le code est ouvert, mais la lib est assez maousse (300mo pour le binaire) et pour avoir deja du creuser dedans, j'aime autant eviter.

            Comme tu dis l'approche que j'ai abordee n'est probablement pas viable, et sachant qu' il y a encore un tas de ses petites soeurs dll que je vais devoir integrer et le risque va se poser a chaque fois, je vais changer d'approche et les garder chargee.

            Merci pour l'echange.

            • Partager sur Facebook
            • Partager sur Twitter
              25 avril 2022 à 16:32:37

              >J'avais pris comme assomption que la lib ... une fonction dediee au deinit.

              Ok, cela confirme mes hypothèses mais votre assertion n'est pas fausse.

              S'ils ont pris la peine d'implémenter une fonction de "dé-initialisation", c'est qu'il y a des choses qu'ils doivent faire avant l'appel à "FreeLibrary", comme l'arrêt de threads en tâche de fond, etc... . Il y a des choses qu'on ne peut pas/plus faire au moment de l'appel à DllMain induit par FreeLibrary.

              Ils ont donc pensé au cas du déchargement mais c'est à vous d'appeler cette fonction "deinit" avant l'appel à "FreeLibrary".

              (Pensez à vérifier qu'il n'y a pas une fonction d'initialisation qu'il faut appeler après LoadLibrary, "en miroir" de "deinit".)

              Votre approche n'est pas très robuste mais avec un peu de chance vous n'aurez que des Dll "Load/Free Library compliante" ou "Init/UnInit compliante" que vous pourrez gérer juste avec peu de modification.

              Si vous tombez sur une Dll qui n'est récalcitrante, votre approche de laisser les Dll chargées peut poser des problèmes de disponibilité d'espace mémoire (surtout en 32bits). Et une approche d'autodestruction de l'exécutable après avoir lancer une image de lui-même configuré pour ne charger que ce qui sera nécessaire pour la suite fait le job de manière fiable mais un peu lourde.

              Si les performances ne sont pas "cruciales", une approche par utilisation de processus "surrogate" fait aussi le taf.

              • Partager sur Facebook
              • Partager sur Twitter
              Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                25 avril 2022 à 21:55:39

                J'appelle bien entendu le deinit avant le free sinon je me prendrais un crash dans les dents, mais malgre cet appel, il semble que le contrat ne soit pas respecte, au point que meme en faisant un simple loadLibrary sans appeler quoi que ce soit d'autre, le freeLibrary ne suffit pas au dechargement.

                J'ai bien peur que d'autres emus puissent en faire de meme je vais donc tenter l'approche de garder les emus en memoire(voir uniquement ceux qui posent probleme mais j'aimerais autant que possible eviter le specifique)

                Je ne peux pas me permettre un reboot de l'exe principal, il fait un certain nombre de taches au demarrage(scan directory, chargement db, connection serveur pour update...) et ca prend une petite dizaine de seconde de loading, redibitoire entre deux parties de jeu.

                • Partager sur Facebook
                • Partager sur Twitter
                  26 avril 2022 à 17:43:05

                  Vous indiquez "libretro_mame.dll" comme nom de la dll ; mais sur l'image c'est "mame_libretro.dll" qui est indiqué et en googleïsant "mame_libretro.dll" j'obtiens des utilisation de "regsvr" pour enregistrer la Dll comme un composant COM (sites peu fiables !).

                  Si c'est une Dll COM il ne faut pas passer par LoadLibrary/FreeLibrary pour charger/décharger la Dll mais via les API COM.

                  Pouvez-vous donner la liste des fonctions exportées par cette Dll ?

                  >J'appelle bien entendu le deinit avant le free sinon je me prendrais un crash dans les dents,

                  Sauf erreur, ce n'est pas ce que vous faites dans le code que vous donnez.

                  S'il il a une fonction "deinit" mais pas de fonction "init", c'est vraisemblablement que le travail d'initialisation se fait dans DllMain, et donc qu'il faut TOUJOURS appeler "deinit" avant le FreeLibrary, ce que votre code ne fait pas.

                  Si vous êtes en 32bits, votre approche de "tout garder en mémoire" risque de rapidement atteindre ses limites.

                  Si le compteur d'utilisation de la Dll ne tombe pas à 0 après le FreeLibrary, c'est "normal" que la Dll ne soit pas décharger. Il faudrait investiguer qui garde une référence sur la Dll (chargement croisée de Dll, etc...). Et comme la dll est "grosse", peut-être que la dll n'est pas instantanément déchargée, même avec un compteur d'utilisation à 0, tant que la pression sur l'usage de la mémoire physique n'est pas important.

                  De mes souvenirs, la fiabilité des émulateurs et des jeux n'est pas "idéals", c'est pour ça que j'aurais utilisé une approche par "surrogate" pour que le programme "tête de gondole" soit toujours "sain" et un programme "surrogate" par émulateur différent. Donc pas besoin de chargement/déchargement de Dll.

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                    26 avril 2022 à 18:09:53

                    Effectivement c'est bien mame_libretro (https://github.com/libretro/mame)

                    Dans le code de test ici je n'ai ni appelé init ni deinit pour isoler le load et free, dans le code de mon application, ils sont bien appelés en suivant le cycle de vie de la lib: load lib -> init -> run -> deinit -> free lib et identiquement le même comportement: la lib reste visible dans process explorer

                    mame_libretro implemente et expose les fonctions de ce header: https://github.com/libretro/RetroArch/blob/master/libretro-common/include/libretro.h dans https://github.com/libretro/mame/tree/master/src/osd/libretro

                    D'après process explorer, pas d'autre chargement de mame_libretro et aucune idee de la valeur du counter de load

                    • Partager sur Facebook
                    • Partager sur Twitter
                      27 avril 2022 à 20:02:29

                      Pour être sur des compteurs de références des Dll :

                      https://stackoverflow.com/questions/3553231/how-to-check-dlls-reference-count-how-to-know-where-the-dll-was-loaded

                      En espérant que WinDbg a été migré dans VS depuis 2010.

                      -
                      Edité par bacelar 27 avril 2022 à 22:11:42

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                        27 avril 2022 à 21:52:54

                        Merci, avant je vais faire demain 2 autres tests, au cas ou:

                        - sous linux (enfin sous wsl du moins)

                        - avec un init et deinit minimal en plus du code de test présenté au début

                        Je n'utilise pas d'IDE(enfin si, mais pour le java seulement), je compile depuis le terminal avec mingw-64, via cmake

                        • Partager sur Facebook
                        • Partager sur Twitter
                          27 avril 2022 à 22:14:12

                          Bin, tu pourras utiliser WinDbg, c'est très proche d'une interface "console".

                          C'est des données systèmes, pas besoin de .pdb du compilateur MSVC pour avoir ces compteurs, je pense.

                          Tu peux "debugger" dedans et donc voir l'évolution des compteurs après chaque appel.

                          • Partager sur Facebook
                          • Partager sur Twitter
                          Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                            30 avril 2022 à 20:03:11

                            J'ai teste sous linux, pas de blocage au 2e lancement donc a priori la librairie bien dechargee, ce qui est confime par pldd qui ne la liste plus un fois le jeu quitte.

                            C'est donc uniquement un probleme windows.

                            je vais tenter windbg pour voir ce que ca donne

                            • Partager sur Facebook
                            • Partager sur Twitter

                            FreeLibrary n'est pas très coopératif

                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                            • Editeur
                            • Markdown