Seite 1 von 1

Template Plugin

Verfasst: 03.10.2010, 01:04
von Jiba
Diese PlugIn Template Klasse enthält bisher nur eine Methode CreateInstance.
Diese Methode soll aus einer DLL eine C Funktion laden.
Diese Funktion erzeugt ein Objekt einer beliebigen Klasse.
Wenn ich dann aber mit delete dieses Objekt freigeben will dann stürzt das Programm ab.
Ich verstehe nicht warum. Zum testen hab ich bisher die EXE Datei als Dateinamen angegeben.
Sonst funktioniert alles wie es soll nur halt die Zerstörung nicht.
Kann mir da jemand helfen?

Code: Alles auswählen

#ifndef PlugIn_h
#define PlugIn_h

#include <windows.h>

namespace Core
{
  class PlugIn
  {
  private:
    PlugIn();

  public:
    template<class T>
    static T* CreateInstance(const char *filename, const char *procname)
    {
      T* pInstance;
      HINSTANCE dll = LoadLibraryA(filename);
      T* (*CreateProc)(void) = (T* (*)(void)) GetProcAddress(dll, procname);

      pInstance = CreateProc();

      //FreeLibrary(dll);
      return pInstance;
    }
 };
}
#endif

Re: Template Plugin

Verfasst: 03.10.2010, 04:15
von B.G.Michi
Guten Morgen,
Kommt mir bekannt vor, hatte ein ähnliches Verhalten (mit VC++) bei der Verwendung verschiedener Einstellungen für die "Laufzeitbibliothek" in DLL und EXE.
Versuch mal diese für beide auf "Multithreaded-[Debug-]DLL (/MD[d]) (sofern du VC++ verwendest) oder ähnliches zu stellen.
JFF_B.G.Michi

Re: Template Plugin

Verfasst: 04.10.2010, 08:30
von Schrompf
Wenn ich das recht verstehe, wird das Instance-Objekt in der DLL-Funktion erzeugt, die Du da in CreateInstance() raussuchst. Jede DLL hat ihren eigenen Heap. Wenn Du ein Objekt, was in einer DLL allokiert wurde, im Hauptprogramm oder in einer anderen DLL freigeben willst, dann crasht es tatsächlich. Gibt jedem Plugin eine weitere Funktion DestroyInstance() oder sowas mit, die dann das deleten übernimmt.

Re: Template Plugin

Verfasst: 04.10.2010, 09:02
von Krishty
Aber zuerst B.G.Michis Vorschlag ausprobieren, der ist bedeutend einfacher – für eine statisch gelinkte CRT sprechen nämlich wirklich so gut wie keine Pros; aber jede Menge Kontras inklusive deinem Zerstörproblem dagegen.

Re: Template Plugin

Verfasst: 04.10.2010, 10:41
von Schrompf
Die CRT ist hier nicht das Problem, sondern nur die Tatsache, dass da ein Objekt von einer Funktion in der DLL erzeugt wird (und damit im DLL-Heap) und dann vom Hauptprogramm zerstört werden soll (was mit dem programm-eigenen Heap arbeitet).

Für mich sieht das wie ein Plugin-System aus. Und da ist statisches Linken eh sinnlos. Welche CRT das Hauptprogramm oder die DLL benutzt, ist dagegen reichlich schnuppe.

Re: Template Plugin

Verfasst: 04.10.2010, 10:52
von Krishty
Die CRT implementiert aber den Heap. Benutzen sowohl das Hauptprogramm als auch das Plugin die dynamisch gelinkte CRT, benutzen sie automatisch auch denselben Heap. Man kann delete auch vom Pluto aus aufrufen, sofern er die CRT ebenfalls dynamisch linkt.

Re: Template Plugin

Verfasst: 04.10.2010, 11:42
von Schrompf
Ok, dann passt das, stimmt. Wenn's denn wirklich die selbe CRT ist. Falls das Plugin mit dem GCC kompiliert wurde, knallt's auch wieder :-)

Ich rate auch weiterhin davon ab, Speicher über DLL-Grenzen hinweg zu transportieren.

Re: Template Plugin

Verfasst: 11.10.2010, 20:11
von Jiba
Ok der Code Funktioniert, der Fehler lag woanders. Das Objekt das mit CreateInstance() erzeugt wurde hat ein DirectX COM Objekt gehalten und ich hab versucht es mit delete frei zu geben :lol: . Nun hab ich aber ein neues Problem. Wenn ich ein Objekt erzeugt habe, die Dll mit FreeLibrary frei gegeben habe aber das Objekt zuvor nicht zerstöre, dann stürzt das Programm ab. Ich würde gerne den Fehler behandel weiß aber nicht wie. Bis jetzt kann ich nur in der DllMain() das Event DLL_PROCESS_DETACH abfangen. Am besten währe es wenn das Programm das die Dll nutzt eine Fehlermeldung bekommt, aber das Programm schmiert ab während FreeLibrary() aufgerufen wird.

Ich poste mal den Code wie er jetzt aussieht. Neu ist vorallem das ich Smartpointer benutze.
Compiler: GCC

Code: Alles auswählen

#ifndef PlugIn_h
#define PlugIn_h

#include <Types.h>
#include <Error.h>
#include <Helper.h>

#include <boost/smart_ptr/shared_ptr.hpp>
#include <windows.h>

#define CHECK_ERROR_WIN32(exp, msg)     if(!(exp)){throw new Core::ExceptionWin32(msg, #exp, __FILE__, __FUNCTION__, __LINE__);};

namespace Core
{
    template<class T>
    class PlugIn
    {
    public:
        PlugIn() : m_hDll(0) {}
        virtual ~PlugIn()
        {
            try
            {
                UnLoad();
            }
            catch(Core::Exception* e)
            {
                     //währe cool hier fehler abfangen zu können
            }
            catch(...)
            {
            }
        }

        void Load(std::wstring libName, std::wstring procName)
        {
            UnLoad();

            CHECK_ERROR_WIN32(m_hDll = LoadLibraryW(libName.c_str()), libName);

            CHECK_ERROR_WIN32(CreateProc = (boost::shared_ptr<T> (API*)(void)) GetProcAddress(m_hDll, UnicodeToAscii(procName).c_str()),
                std::wstring(libName + L"->" + procName));
        }

        void UnLoad()
        {
            if(m_hDll)
            {
                CHECK_ERROR_WIN32(FreeLibrary(m_hDll), L""); //Hier bricht die ausführung ab und windows zeigt eine Fehlermeldung an
                m_hDll = 0;
            }
        }

        boost::shared_ptr<T> CreateInstance()
        {
            return CreateProc();
        }

    protected:
        HINSTANCE m_hDll;
        boost::shared_ptr<T> (*API CreateProc)(void);
    };
}

#endif
Das Testprogramm sieht so aus

Code: Alles auswählen

#include ...
using namespace ...

int main()
{
    try
    {
        PlugIn<IGameShell> shellPlugIn;
        shellPlugIn.Load(L"GameShellWin32.dll", L"CreateGameShell"); //Lädt die Dll

        boost::shared_ptr<IGameShell> shell;
        shell = shellPlugIn.CreateInstance(); //erzeugt das Objekt
        cout<<shell.use_count()<<endl; //giebt 2 zurück da in der Dll auch ein shared_ptr gehalten wird
        //shell.reset(); //falls ich mal nicht das Objekt zerstöre bevorich die Dll frei gebe so stürzt das Programm beim freigeben ab
        shellPlugIn.UnLoad(); //giebt die Dll Frei
    }
    catch(Core::Exception* e)
    {
        wprintf(L"%s\n", e->GetFormatedMessage().c_str());
    }
    catch(...)
    {
        printf("Unknown Exception!\n");
    }

    return 0;
}
Die Erzeugung in der Dll sieht so aus

Code: Alles auswählen

#include ...
using namespace ...

void Deletor( IGameShell *pGameShell)
{
    cout<<"DoIt"<<endl; //mal zum testen
    delete pGameShell;
}

EXPORT_C shared_ptr<IGameShell> API CreateGameShell()
{
    static shared_ptr<IGameShell> spGameShell(new GameShellWin32(), Deletor); //GameShell soll nen Singleton sein

    return spGameShell;
}

EXPORT_C BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_DETACH:
            // detach from process
            try
            {
                shared_ptr<IGameShell> spGameShell = CreateGameShell();
                CHECK_ERROR(!(spGameShell.use_count()>2), IntToUnicode(spGameShell.use_count()));
//wirft eine exception wenn außerhalb der Dll noch shared_ptr auf das Objekt existieren
            }
            catch(Core::Exception* e) //fang ich die Exception hier nicht verschwindet sie irgendwohin wo ich keine ahnung ab
            {
                wprintf(L"%s\n", e->GetFormatedMessage().c_str());
                return FALSE;
            }
            break;
    }
    return TRUE; // succesful
}
So, ich hoffe ich find den fehler nicht wieder irgendwo anders ;-).

Re: Template Plugin

Verfasst: 11.10.2010, 20:25
von Krishty
Dein Fehler ist, dass du die DLL nach dem Laden des Plugins sofort wieder freigibst. Das ist ein fundamentales No-go. Behalt die DLL geladen, bis das Plugin zerstört wird – am besten als Member der Plugin-Verwaltungsklasse, vor dem Zeiger zur Schnittstelle deklariert, damit der Compiler automatisch in der richtigen Reihenfolge initialisiert (dann kannst du die Load-Funktion zum Konstruktor machen und bekommst RAII gratis).

Und benutz [ code = cpp ], dann bleibt die Formatierung erhalten ;)

Re: Template Plugin

Verfasst: 11.10.2010, 21:01
von Jiba
Ok, so ist es intuitiver zu benutzen. Aber eigendlich ging es mir ja darum, dass wenn man nicht alle Ressourcen frei giebt vor der Zerstörung des PlugIns, dass das Programm nicht einfach abstürzen soll.

Re: Template Plugin

Verfasst: 11.10.2010, 21:04
von Aramis
DLLs vertragen sich absolut nicht mit der Form von Resourcenmanagement, das die Hochsprache C++ mitbringt. IMHO besteht die beste Loesung darin, 'Plugins' (wenn du sie wirklich brauchst) grundsaetzlich niemals zu 'zerstoeren' im Sinne von 'DLL aus dem Adressraum rauswerfen'. Ein Resource-Leak in Verbindung mit FreeLibrary-Aufrufen ist ein Crash. Ein Resource-Leak ohne FreeLibrary ist ein Resource-Leak und damit mehr oder weniger harmlos.

Re: Template Plugin

Verfasst: 11.10.2010, 21:08
von Jiba
Dann bleiben mir wohl nur Log Dateien

Re: Template Plugin

Verfasst: 11.10.2010, 21:13
von Krishty
Jiba hat geschrieben:Aber eigendlich ging es mir ja darum, dass wenn man nicht alle Ressourcen frei giebt vor der Zerstörung des PlugIns, dass das Programm nicht einfach abstürzen soll.
Nein, ging es dir nicht:
Jiba hat geschrieben:Wenn ich ein Objekt erzeugt habe, die Dll mit FreeLibrary frei gegeben habe aber das Objekt zuvor nicht zerstöre, dann stürzt das Programm ab.
Der gesamte Code deines Plugins – nicht nur der Destruktor, sondern auch alle virtuellen Funktionen (und im Fall einer statisch gelinkten CRT auch alles, was das Plugin je allokiert hat) liegen im Adressraum der DLL … in dem Augenblick, in dem du FreeLibrary aufrufst, jagst du das alles zum Teufel. Entsprechend kracht es, wenn dann der D’tor durchläuft.

Darum: Die DLL und die Plugin-Schnittstelle sind untrennbar miteinander verbunden und sollten auch entsprechend verwaltet werden, dass es unmöglich ist, Unload() bzw. FreeLibrary aufzurufen, bevor alle Funktionen des Plugins (vor allem: sein Destruktor) durchgelaufen sind. Wenn du LoadLibrary() und FreeLibrary() in den Konstruktor und Destruktor einer eigenen Klasse packst, und die zum Member von PlugIn machst, hast du diese Garantie – natürlich musst du dann Plugins auch per Konstruktor initialisieren (RAII geht, wie const-Correctness, nur ganz oder garnicht).

Re: Template Plugin

Verfasst: 11.10.2010, 21:27
von Krishty
Moment mal – jetzt sehe ich erst, dass das Plugin ja seine eigene DLL verwaltet?!? Das kann und darf nicht sein, die DLL wird schließlich vor dem Plugin initialisiert und muss danach zerstört werden … oder verchecke ich hier was total?

Mein Vorschlag wäre ein Wrapper um die Plugin-Klasse:

Code: Alles auswählen

class CPluginInDLL {
private:
    ::HANDLE MyDLLsHandle;
    PlugIn * ToMyPlugin; // würde ich sogar zu einer Referenz machen, wenn die Factory-Funktion entsprechend aussähe

    static ::HANDLE LoadDLL(wchar_t const * const ItsPath) {
        ::HANDLE Result = ::LoadLibraryW(DLLsPath);
        if(NULL == Result)
            throw "DLL not found";
       return Result;
    }

    static PlugIn * LoadPlugin(::HANDLE const DLLsHandle, char const * const ItsName) {

       PlugIn * (API*)(void) ToFactory = (PlugIn * (*)(void))::GetProcAddress(DLLsHandle, ItsName);
        if(NULL == ToFactory)
            throw "Factory function not found";

        PlugIn * ToPlugin = (*ToFactory)()
        if(nullptr == ToPlugin)
            throw "Plugin fail";

       return ToPlugin;
    }

public:

    CPluginInDLL(wchar_t const * const DLLsPath, char const * const FactorysName)
        : MyDLLsHandle(LoadDLL(DLLsPath))
        , ToMyPlugin(LoadPlugin(MyDLLsHandle, FactorysName))
    { }

    PlugIn * operator -> () { return ToMyPlugin; }
    PlugIn const * operator -> () const { return ToMyPlugin; }

    ~CPluginInDLL() {
        delete *MyPlugin; // Oder alternativ eine Funktion aus der DLL (siehe Schrompf)
        ::FreeLibrary(MyDLLsHandle);
    }

};
Ich habe einfach mal PlugIn eingesetzt, weil ich nicht verstehe, wozu man überhaupt noch Plugins braucht, wenn man den Typ, den man erzeugt, schon im Hauptprogramm kennt. Ist nur hingehackt, darum ohne Garantie auf Kompilierbarkeit. Ist auch für dich, Aramis!

Einfach im Hauptprogramm instanzieren, lädt DLL und Plugin automatisch, gibt sie auch wieder frei und gestattet Zugriff auf die Schnittstelle per DLLPlugin->Blubb(). Und nochmal: Wenn man seine Typen sowieso schon im Hauptprogramm kennt, kann man auf DLLs auch einfach verzichten.

Re: Template Plugin

Verfasst: 12.10.2010, 11:04
von kimmi
Du könntest das Feigeben der Dll erst dann zulassen, wenn alle Resourcen von ihren Erzeugern / Referenz-Owners ebenfalls wieder freigegeben wurden. Stichwort Reference-Counting. Dann löst du das Problem korrekt.

Gruß Kimmi

Re: Template Plugin

Verfasst: 12.10.2010, 16:53
von Krishty
Hier braucht es nicht einmal Reference-Counting, sondern bloß klare Zuständigkeit – ist das Plugin selber für alles verantwortlich, was es erzeugt, und ist die DLL für das Plugin verantwortlich, hängen alle Ressourcen an demselben Zweig, der vor FreeLibrary freigegeben wird. Darum finde ich auch den Einsatz von Smart-Pointern sehr bedenklich; es ist schon sehr unkonventionell, dass ein Plugin mehrere Besitzer haben soll.

Edit: Wtf, jetzt werde ich schon senil und verwechsle die Threads, in denen ich poste. Hier stand Schmarrn.

Re: Template Plugin

Verfasst: 12.10.2010, 17:50
von Jörg
Das passt schon so, LoadLibrary benutzt einen internen Referenz-Zaehler.

Re: Template Plugin

Verfasst: 12.10.2010, 18:15
von Krishty
Ja, aber solange nicht jedes new und delete innerhalb der DLL LoadLibrary() bzw. FreeLibrary aufruft, hilft das bei fehlender Programmstruktur und herumliegenden Ressourcen nicht … relevant ist es nur für Fälle, in denen mehrere Plugins in der selben DLL liegen. (Okay, das meintest du wohl auch.)

Re: Template Plugin

Verfasst: 12.10.2010, 20:02
von Jiba
Der Code von Krishty löst ziemlich gut was ich haben wollte. Habe ihn ein bischen meinen bedürfnissen angepasst. Jetzt kann man nicht mehr so leicht einen Absturz verursachen.

SmartPointer brauch ich, weil z.B. die Grafikschnittstelle nur einmal existieren soll. So ist sichergestellt das erst der letzte Code der sein Grafik PlugIn zerstört auch das Grafikobjekt zerstört. Jetzt müsst ich nur noch sicherstellen das für PlugIns die ein Singleton halten (wie für die Grafikschnittstelle) immer der selbe Dllpfad und Funktionsname angegeben wird.

Der Code sieht jetzt so aus:

Code: Alles auswählen

template<class T>
class PlugIn
{
public:
    typedef shared_ptr<T> (API *Factory)(void);

    static uInt LoadLib(const std::wstring &libName)
    {
        uInt result;
        CHECK_ERROR_WIN32(result = (uInt) ::LoadLibraryW(libName.c_str()), libName);
        return result;
    }

    static Factory LoadFactory(const uInt libHandle, const std::wstring factoryName)
    {
        Factory result;
        CHECK_ERROR_WIN32(result = (Factory) ::GetProcAddress((HINSTANCE) libHandle, UnicodeToAscii(factoryName).c_str()), factoryName);
        return result;
    }

public:
    PlugIn(const std::wstring &libName, const std::wstring &factoryName)
        : m_libName(libName)
        , m_factoryName(factoryName)
        , m_libHandle(LoadLib(m_libName))
        , m_spInstance(LoadFactory(m_libHandle, m_factoryName)())
    {
        CHECK_ERROR(m_spInstance.get(), std::wstring(libName + L"->" + factoryName));
    }

    ~PlugIn()
    {
        m_spInstance.reset();

        if(m_libHandle)
        {
            CHECK_ERROR_WIN32(FreeLibrary((HINSTANCE) m_libHandle), L"");
            m_libHandle = 0;
        }
    }

    T& GetInstance() { return *m_spInstance.get(); }
    const T& GetInstance() const  { return *m_spInstance.get(); }
    T* operator -> ()  { return m_spInstance.get(); }
    const T* operator -> () const { return m_spInstance.get(); }

    PlugIn(const PlugIn& rhs)
        : m_libName(rhs.m_libName)
        , m_factoryName(rhs.m_factoryName)
        , m_libHandle(LoadLib(m_libName))
        , m_spInstance(LoadFactory(m_libHandle, m_factoryName)())
    {
        CHECK_ERROR(m_spInstance.get(), std::wstring(m_libName + L"->" + m_factoryName));
    }

private:
    PlugIn& operator = (const PlugIn&);

protected:
    std::wstring m_libName;
    std::wstring m_factoryName;
    uInt m_libHandle;
    shared_ptr<T> m_spInstance;
};