Template Plugin

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Jiba
Beiträge: 31
Registriert: 16.01.2010, 17:42

Template Plugin

Beitrag 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
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: Template Plugin

Beitrag 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
Benutzeravatar
Schrompf
Moderator
Beiträge: 4884
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Template Plugin

Beitrag 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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Template Plugin

Beitrag 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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4884
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Template Plugin

Beitrag 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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Template Plugin

Beitrag 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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4884
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Template Plugin

Beitrag 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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Jiba
Beiträge: 31
Registriert: 16.01.2010, 17:42

Re: Template Plugin

Beitrag 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 ;-).
Zuletzt geändert von Jiba am 11.10.2010, 20:45, insgesamt 2-mal geändert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Template Plugin

Beitrag 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 ;)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jiba
Beiträge: 31
Registriert: 16.01.2010, 17:42

Re: Template Plugin

Beitrag 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.
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: Template Plugin

Beitrag 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.
Jiba
Beiträge: 31
Registriert: 16.01.2010, 17:42

Re: Template Plugin

Beitrag von Jiba »

Dann bleiben mir wohl nur Log Dateien
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Template Plugin

Beitrag 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).
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Template Plugin

Beitrag 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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: Template Plugin

Beitrag 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
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Template Plugin

Beitrag 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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: Template Plugin

Beitrag von Jörg »

Das passt schon so, LoadLibrary benutzt einen internen Referenz-Zaehler.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Template Plugin

Beitrag 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.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jiba
Beiträge: 31
Registriert: 16.01.2010, 17:42

Re: Template Plugin

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