Best Practices DLL auf Festplatte zugreifen lassen oder im Hauptprogramm?



  • Hallo liebes Forum,

    ich schreibe momentan für meinen Arbeitgeber ein Programm zur automatischen Auswertung von Webseiten. Dieses Programm sollte erweiterbar sein und deshalb habe ich mich dazu entschieden ein Pluginsystem mit DLLs zu implementieren. Das funktioniert auch soweit.

    Nun muss ich die Ergebnisse der Auswertung in eine Datei schreiben und da stellt sich mir die Frage, ob ich das die DLL machen lasse oder lieber das Hauptprogramm und wenn das Hauptprogramm, wie ich den Text zurück ins Hauptprogramm bekomme, ohne ein gigantisches char-Array benutzen zu müssen.

    Ich habe schon überlegt der DLL einfach einen Stream mitzugeben aber da es anscheinend zu Problemen führen kann, die Schnittstelle in C++ zu definieren, da die C++ Compiler die Namen der Funktionen ändern können, geht das nicht.

    Da beides prinzipiell funktionieren würde, frage ich mich nun, was die bessere Praxis ist.

    Vielen Dank schon mal

    Atlan



  • da die C++ Compiler die Namen der Funktionen ändern können, geht das nicht.

    Das ist Quatsch!

    Solange alles mit der gleichen Compilerversion und den gleichen Schaltern übersetzt wird, funktioniert eine Schnittstelle in C++ tadellos.



  • Vom Software-Design her ist es besser Dinge wie "Zeugs in File schreiben" aus der Komponente rauszutrennen die das "Zeugs" erzeugt.
    D.h. du machst z.B. ein Interface

    class ZeugsSchreiber
    {
    public:
        virtual ~ZeugsSchreiber() {} // Oft nicht nötig, aber schadet in Interface-Klassen auch eher nicht
    
        virtual void SchreibeZeugs(int a, char const* b, double d) = 0;
        // ...
    
        virtual void HabenFertig() = 0;
    };
    

    Dann baust du deinen ZeugsInFileSchreiber der dieses Interface implementiert (=von dieser Interface-Klasse ableitet und alle "pure" Funktionen überschreibt).

    Dann stellt sich als nächstes die Frage: Wer erzeugt den ZeugsSchreiber für die ZeugsErzeuger Klasse... die ZeugsErzeuger selbst oder eine Instanz weiter oben (=z.B. der selbe der auch die ZeugsErzeuger Klasse erzeugt)? Die bessere Antwort ist dann meistens: eine Instanz weiter oben. Weil das flexibler ist.

    D.h. der ZeugsErzeuger bekommt z.B. einen Zeiger auf einen ZeugsSchreiber von aussen mitgegeben. Im Konstruktor oder direkt in allen Memberfunktionen die den ZeugsSchreiber benötigen.

    Nennt sich BTW "Dependency Injection" oder als etwas allgemeineres Prinzip auch "Inversion of Control" (=>googeln, lesen, schlau werden :)).

    Und schon hast du ne schöne Trennung die es dir u.A. auch ermöglicht den ZeugsSchreiber im Hauptprogramm zu implementieren und den ZeugsErzeuger in der DLL. Und das ganz ohne dass du std::ostream Objekte irgendwo übergeben musst (bzw. Zeiger/Referenzen darauf).

    ----

    Davon abgesehen hat manni66 Recht: WENN die DLL und das Hauptprogramm mit dem selben Compiler gebaut werden, mit den selben Einstellungen und mit der selben Runtime DLL, dann kannst du auch einfach beliebige C++ Objekte (inklusive Strings, Streams und was nicht noch alles) über DLL-Grenzen hinweg übergeben. Machen wir in der Firma in vielen Projekten andauernd, funktioniert problemlos.



  • Sorry. Hatte mich nicht korrekt ausgedrückt.

    Was ich meinte war: "Da bei C++ die Funktionen nur dann sicher erkannt werden, wenn derselbe Compiler und dieselben Compilerschalter verwendet werden."

    Das Programm soll darauf ausgelegt sein, sehr lange zu halten und da war mir die C++-Schnittstelle zu unsicher und habe lieber auf die von C gesetzt oder ist das gar nicht so schwer, die DLL auf eine bestimmte Version oder einen bestimmten Compiler zu trimmen?

    EDIT: In die Dependency Injection werden ich mich mal hereinlesen. Danke.



  • Wenn du nicht vom Compiler abhängig sein willst, dann kannst du in Windows immer noch "pure virtual" Interfaces verwenden.

    Aus meinem Beispiel müsste dann der virtuelle Destruktor raus, weil der Aufruf des Dtors halt doch compilerabhängig ist. Statt dessen müsste, falls nötig, eine virtuelle "Delete" Funktion rein. Wie "virtual pure" Funktionen aufgerufen werden, ist unter Windows aber nicht compilerabhängig - ist nötig damit COM Schnittstellen in C++ funktionieren.

    Oder du macht es gleich vollständig über COM.

    Oder du machst "C Style" Interfaces. Im Prinzip structs mit Funktionszeigern drinnen.

    Oder aber du verlangst einfach dass das Hauptprogramm im Falle des Falles einfach neu übersetzt werden muss. (Also falls die DLL mal mit nem neueren Compiler gebaut werden soll/muss.)
    Ich kenne deine Situation nicht, aber wenn das möglich wäre, dann würde ich es empfehlen. Weil alles was man machen kann um uneingeschränkte C++ Interfaces zu vermeiden ein sehr deutlicher Aufwand ist. Und wenn der nicht nötig ist, dann sollte man ihn vermeiden.



  • Meine Situation ist, dass ich für eine Charity in England arbeite. Nun ist meine Zeit dort fast vorbei, das Programm soll aber bei denen verbleiben und auch von denen erweiterbar sein, in erster Linie aber von mir. Die haben keine Softwareentwicklung und so, wenn jemand eine Erweiterung programmieren will, müsste er jedes Mal mich fragen, welche Konfiguration ich verwendet habe, weil diese Information leicht verloren gehen kann. Ich bin kein professioneller Softwareentwickler, sondern von den Fähigkeiten her eher ein Hobbyentwickler.
    Das Programm wird definitiv nur unter Windows laufen.

    Wäre es noch möglich, dass ich das Schreiben auf die Festplatte in einer DLL mit C-Schnittstelle definiere, diese im Hauptprogramm initialisiere und den Funktionszeiger mit der Funktion zum Schreiben der ErweiterungsDLL übergebe? Das wäre ja eigentlich die Dependency Injection oder wäre das ein Overkill?

    EDIT: Das sollte ich mal lieber lassen, da ich die SchreibeDLL dadurch pro ErweiterungsDLL ein mal kopieren müsste.

    EDIT2: Ich habe noch einmal nachgedacht und habe alle Möglichkeiten, die mir eingefallen sind, zusammengefasst:

    • Die DLLs den Text selber speichern lassen (Quick and Dirty)
    • Auf die Kompatibilität verzichten und den DLLs die Möglichkeit zu schreiben mitgeben
    • Die DLLs den Text an den aufrufenden Prozess übergeben lassen, diesen den Text speichern lassen und damit einen Speicherüberlauf riskieren
    • Mit shared memory und Threads arbeiten und sehr kompliziert einer Race Condition vorbeugen
    • Den DLLs einen Buffer zum Vollschreiben mitgeben. Wenn sie diesen vollgeschrieben haben, frieren sie ein, der Hauptprozess schreibt den Text und ruft die DLL erneut auf. Diese macht da weiter, wo sie aufgehört hat. Wenn das Ende erreicht ist, wird false zurückgegeben, ansonsten true.
    • Mit Events arbeiten. Bei der Initialisierung bekommt jede DLL eine Kennung. Wenn eine DLL gerne schreiben möchte, löst sie ein Event aus, welches vom Hauptprogramm aufgefangen wird. Dieses guckt, anhand der Kennung nach, an welchem Ort der Text geschrieben werden soll und schreibt diesen dort hin.

    Meine Favoriten sind die letzten beiden, auch wenn ich die Letze threadsafe machen müsste, da die DLLs ihre eigenen Threads bekommen.



  • Natürlich kannst du es über Funktionszeiger machen. Ob die Funktion dabei aus einer DLL kommt oder nicht ist ziemlich egal. So lange die Funktion ne reine C-Schnittstelle hat, gibt es da auch kein Problem.

    Allerdings ist mir das Problem nicht so ganz klar. Normalerweise überlässt man dem Kunden bei "Work for Hire" den kompletten Source-Code, inklusive Project-Files, Build-Scripts etc.

    Wenn das Programm nur unter Windows laufen muss, dann kannst du ja einfach ne Visual Studio Solution machen, die alle nötigen Komponenten beinhaltet. Dann muss jemand der etwas ändern möchte diese nur in Visual Studio aufmachen, seine Änderung machen, und dann alles neu bauen (1x "Build Solution", mehr ist da ja nicht nötig).
    Als Erbegnis sollte er ein "bin" Verzeichnis vorfinden wo alle nötigen Files (.EXEn, .DLLs, Config-Files etc.) drinnen liegen. Das muss er dann bloss noch auf den PC kopieren wo es laufen soll, und die Sache ist erledigt.

    Die haben keine Softwareentwicklung und so, wenn jemand eine Erweiterung programmieren will, müsste er jedes Mal mich fragen, welche Konfiguration ich verwendet habe, weil diese Information leicht verloren gehen kann.

    Siehe oben - weitere Doku als die komplette Solution inklusive aller Sourcen sollte eigentlich nicht nötig sein. Falls du weitere SDKs benötigst kannst du die ja alle mit in das Solution-Verzeichnis packen und dann einfach Include-Pfade relativ zum .sln File definieren.

    Und wenn das aus irgend einem Grund nicht geht, kannst du immer noch ein .txt File schreiben wo alles nötige dokumentiert/erklärt ist. Wenn der Kunde es schafft selbst das zu "verlieren", dann frage ich mich wie er es schaffen will dein Programm nicht zu verlieren.

    Natürlich sollte alles in ein Source Control Repository. Subversion oder was auch immer. Und das Repository verbleibt beim Kunden. Alles andere ist Pfusch. Dass das Repository nicht verloren geht, darum muss sich natürlich ebenfalls der Kunde kümmern. Irgend ein Serverlaufwerk wo man Dinge ablegen kann die dann z.B. auch gesichert werden sollte wohl jede Organisation haben die irgendwas ernstzunehmendes mit Computern macht.

    Ansonsten gibt es Source Control auch gehostet. Gibt verschiedene Anbieter wo man sowas gratis bekommt, z.T. auch für "closed source" Projekte.



  • Also bist du der Meinung, ich sollte einfach auf eine C++ Schnittstelle setzen und den DLLs einen Outstream übergeben (so habe ich zumindest Injected Dependency verstanden. Ich lese da aber noch mal mehr drüber), und wenn die eine neue Erweiterung mit einer neueren Version oder einem anderen Compiler haben wollen, müssen sie halt das Programm neu bauen?

    Mein Problem ist, dass ich die Entwicklung von Plugins so einfach, wie möglich für sie machen möchte aber so wie du das schreibst, ist es auch mit C++ Schnittstellen nicht so kompliziert, wie mir das Internet weismachen wollte.



  • Wenn ich meinen Senf dazugeben darf - nimm simple C-Interfaces. Das ist zwar nicht so schön und persönlich würde ich für sowas auch eher C++ nehmen, aber mit sauberen C-Interfaces erlaubst du deinem Nachfolger z.B. auch einfach Plugins in $MODESPRACHE zu schreiben, weil quasi jede Sprache auf der Welt irgendwie mit C-Bindings umgehen kann, meistens sogar mit kaum Aufwand.

    Zumindest meine Erfahrung aus der Arbeitswelt beim Tools schreiben war, dass das am besten funktioniert, wenn nicht fest steht wer mit welchem Wissen später weitere Plugins schreiben soll.



  • @Atlan
    Sorry für die späte Antwort.
    Was stimmt ist dass ein reines C Interface eine gute Sache ist wenn man maximale Kompatibilität möchte, ohne ein Programm anpassen zu müssen. (Bzw. noch besser wäre unter Windows vermutlich COM, nur wenn du mit COM noch keine Erfahrung hast und es darum geht "noch schnell" gegen Projekteende etwas einzubauen, dann würde ich dir das mal nicht unbedingt empfehlen.)

    Die Frage ist aber ob es überhaupt nötig ist.
    Und das kann ich nicht wirklich beurteilen.
    Kann sein, kann aber auch nicht.

    Auf jeden Fall müsstest du dein Programm dann aber so auslegen, dass man wirklich einfach Plugins schreiben kann, ohne das Programm zu modifizieren. Das bedeutet dann auch dass man, z.B. indem man ein Config-File modifiziert, dein Programm dazu bringen kann dieses Plugin zu verwenden.
    Normalerweise heisst das dann dass du LoadLibrary und GetProcAddress verwenden musst um das Plugin zu laden.
    Oder umgekehrt, dass du dein Programm als DLL auslegst, mit z.B. einer "Run" Funktion, der man das oder die Plugins mitgibt. (Und dann einer kleinen .exe dazu, die einfach nur Run() mit den "Default-Plugins" aufruft.)

    Und das ist einiges an Aufwand, zumindest wenn man es noch nie gemacht hat. Weiters kann man dann nur Plugins schreiben die genau dort eingreifen wo du es vorgesehen hast.

    Wenn du dagegen ein halbwegs einfach aufgebautes, halbwegs übersichtliches C++ Programm mit vollständigem Source und einer einfachen "Build-Anleitung" betrachtest - darin sollte jeder C++ Programmierer mehr oder weniger einfach Änderungen machen können.


Anmelden zum Antworten