Exceptions: sicher, auch über DLLs?



  • Okay, Danke! 👍



  • dot schrieb:

    Wenn dll und exe beide mit MSVC gebaut werden, könnte man das sogar zum Laufen bekommen, aber im Allgemeinen geht das nicht.

    Da bei C++-dlls das ABI ohnehin immer speziell passend sein muss, ist das in der Regel kein Problem. Wenn Du im dll-Interface C++ benutzt, müssen dll- und exe-Compiler und deren Einstellungen ebenfalls zusammenpassen.



  • Hmh. Das heißt also, wenn ich die DLL weitergeben will, dürfen keinerlei Exceptions fliegen (ich weiß ja nicht, welches Werkzeug der Client verwendet). Ich muß also in jeder exportierten Funktion alle möglichen Exceptions fangen und entsprechend in Fehlercodes übersetzen, damit ich wieder sauber bin.

    Wie löst man das denn am Saubersten? Eben hört sich das jedenfalls danach an, als wäre cpp für DLLs generell ungeeignet. Oder verzichtet man auf die Standard-Bibliothek komplett, damit nicht vielleicht doch mal eine Exception durchrutscht, die man nicht bedacht hat?

    Oder ist Euch das ganz einfach egal, da eine Exception im Normalfall sowieso nicht passiert?



  • Warum genau soll eine Exception aus der dll fliegen? dlls bieten nur ein C-ABI, ergo kann man in der Schnittstelle einer dll auch nur C verwenden. Du kannst keine std:: Sachen in die dll rein übergeben oder aus der dll rausbekommen. Die Innereien können aber natürlich in C++ implementiert sein. Wenn die Standardbibliothek eine Exception wirft, dann heißt das also, dass in deiner dll was schiefgelaufen ist. Was sollte der Aufrufer denn deiner Meinung nach mit so einer Exception anfangen, die doch klarerweise ein Implementierungsdetail der dll betrifft!?

    Natürlich könntest du theoretisch auch das Exception-Handling des Betriebssystems benutzen...



  • dot schrieb:

    Exceptionist schrieb:

    dot schrieb:

    Nein.

    Auch nicht, wenn man alle DLLs die diese Exception fangen / werfen könnten, komplett selber schreibt und alle den gleichen Compiler verwenden?

    In deiner dll kannst du tun was du willst, du kannst die dll auch in irgendeiner selbsterfundenen Sprache schreiben wenn du Lust hast. So lange die Exception in der dll bleibt ist alles perfekt in Ordnung. Aber du kannst nicht in deiner dll eine Exception werfen und sie dann erst in der exe fangen. Aber sowas will man noramlerweise sowieso nicht.

    Klar geht das, ganz wunderbar sogar. Und wieso sollte man es nicht wollen?

    Wenn dll und exe beide mit MSVC gebaut werden, könnte man das sogar zum Laufen bekommen, aber im Allgemeinen geht das nicht.

    Ich würde eher sagen im Allgemeinen geht es, so lange der selbe Compiler verwendet wird, sei das nun MSVC oder was anderes. Und wenn es unterschiedliche Compiler sind, dann geht sowieso wenig. Bzw. wenn die Compiler ne kompatible ABI haben, dann geht wieder viel, und vermutlich auch wieder das Werfen von Exceptions über DLL Grenzen hinweg.



  • dot schrieb:

    Warum genau soll eine Exception aus der dll fliegen? dlls bieten nur ein C-ABI, ergo kann man in der Schnittstelle einer dll auch nur C verwenden. Du kannst keine std:: Sachen in die dll rein übergeben oder aus der dll rausbekommen.

    Quatsch mit Sosse.
    Man kann komplette C++ Klassen aus einer DLL exportieren, beliebige Standard-Typen als Parameter oder Returnwerte verwenden etc.
    Das setzt natürlich wieder voraus dass der selbe Compiler (und die selbe Runtime) verwendet wird.

    Nirgends steht geschrieben dass man sich bei DLLs auf ein klassisches Win32 C-Interface festlegen muss.
    In vielen Fällen wird das Sinn machen (z.B. wenn man ne DLL als API an Kunden ausliefern will, um denen nicht eine bestimmte Compiler-Version aufzuzwingen).

    Aber gleich so zu tun als gäbe es nix anderes, als müsste man DLLs immer in dieser kastrierten Form verwenden, halte ich für etwas komisch.

    p.S.: DLLs definieren gar kein ABI, sondern lediglich eine Möglichkeit "Symbole" zu ex- und importieren. Was sich hinter diesen Symbolen verbirgt ist Sache der DLL.



  • Gästchen schrieb:

    Hmh. Das heißt also, wenn ich die DLL weitergeben will, dürfen keinerlei Exceptions fliegen (ich weiß ja nicht, welches Werkzeug der Client verwendet).

    Kommt drauf an.
    Wir hatten z.T. schon closed-source ThirdParty DLLs mit unbeschnittenen C++ Interfaces. Die gab es dann halt in mehreren Versionen, eben für alle "noch interessanten" C++ Compiler für Windows. Damals waren das bei uns vc6, vc7.0, vc7.1 und vc80.
    Und falls deine DLL open-source ist, musst du auch "nur" sicherstellen dass man sie mit allen "noch interessanten" Compilern bauen kann - der User/Kunde muss das dann halt selbst machen.

    Ich muß also in jeder exportierten Funktion alle möglichen Exceptions fangen und entsprechend in Fehlercodes übersetzen, damit ich wieder sauber bin.

    Kann man so machen denke ich.

    Wie löst man das denn am Saubersten? Eben hört sich das jedenfalls danach an, als wäre cpp für DLLs generell ungeeignet. Oder verzichtet man auf die Standard-Bibliothek komplett, damit nicht vielleicht doch mal eine Exception durchrutscht, die man nicht bedacht hat?

    Selbst wenn man auf die Standard-Library verzichtet, bleiben noch Exceptions wie bad_alloc (new).



  • hustbaer schrieb:

    dot schrieb:

    Warum genau soll eine Exception aus der dll fliegen? dlls bieten nur ein C-ABI, ergo kann man in der Schnittstelle einer dll auch nur C verwenden. Du kannst keine std:: Sachen in die dll rein übergeben oder aus der dll rausbekommen.

    Quatsch mit Sosse.
    Man kann komplette C++ Klassen aus einer DLL exportieren, beliebige Standard-Typen als Parameter oder Returnwerte verwenden etc.

    Ja, aber das sind compilerspezifische Features und ist damit absolut nicht portabel. Wenn man sicherstellen kann dass alles mit MSVC gebaut wird, dann geht es natürlich (bedingt), habe auch nie was anderes behauptet.

    hustbaer schrieb:

    Das setzt natürlich wieder voraus dass der selbe Compiler (und die selbe Runtime) verwendet wird.

    Eben. Und noch einiges mehr (bestimmte Compilerflags etc.).

    hustbaer schrieb:

    Nirgends steht geschrieben dass man sich bei DLLs auf ein klassisches Win32 C-Interface festlegen muss.

    Wenn man compilerunabhängig sein will, dann muss man das.

    hustbaer schrieb:

    In vielen Fällen wird das Sinn machen (z.B. wenn man ne DLL als API an Kunden ausliefern will, um denen nicht eine bestimmte Compiler-Version aufzuzwingen).

    ...und ich hatte eben den Eindruck, dass genau das für den Threadersteller von Bedeutung wäre...

    hustbaer schrieb:

    Aber gleich so zu tun als gäbe es nix anderes, als müsste man DLLs immer in dieser kastrierten Form verwenden, halte ich für etwas komisch.

    dot schrieb:

    Wenn dll und exe beide mit MSVC gebaut werden, könnte man das sogar zum Laufen bekommen, aber im Allgemeinen geht das nicht.

    Mit dem "könnte" wollte ich implizieren dass es bedingt möglich ist. Bedingt, weil man eben auf vieles mehr achten muss als nur gleicher Compiler (gleiche Runtime, Dinge wie kompatibles struct packing, etc.).



  • Mich stört einfach, wenn als erste Antwort gleich mal ein "nein" ohne weitere Erklärung kommt. Denn das stimmt so einfach nicht.

    dot schrieb:

    (gleiche Runtime, Dinge wie kompatibles struct packing, etc.)

    Ach...
    Runtime und struct packing sind jetzt kein Problem bei reinen C-APIs?

    Was die Runtime angeht... OK, in C gibt es wesentlich weniger Dinge auf die man achten muss, damit die Runtime egal ist (z.B. keine Zeiger zurückgeben die der Aufrufer dann mit einer Runtime-Funktion (free) löschen muss). Man muss aber auch wissen was man machen darf und was nicht, d.h. das Problem ist prinzipiell genau so vorhanden wie mit einer C++ API.

    Und z.T. struct packing: das ist effektiv kein Problem, weil es keiner umstellt. Sollte es doch ein Problem sein, und man hat sein Projekt nicht darauf vorbereitet (z.B. mittels #pragma pack(push, N) ... #pragma pack(pop) in jedem Header-File), dann erwischt es einen mit ner C API genau so wie mit ner C++ API.

    Nach deiner Logik könnte ich jetzt sagen: DLLs kann man im Allgemeinen gar nicht verwenden. Es sei denn man passt eben auf dass bestimmte Dinge zusammenpassen. Eine grundsätzlich richtige, aber nicht sehr sinnvolle Aussage.



  • Gut, struct Packing war ein schlechtes Beispiel. Sagen wir halt RTTI und Iterator Debugging...

    EDIT: Ich hab wohl einfach schon zuviele schlechte Erfahrungen mit solchen dlls gemacht 😉
    Es gibt einfach nichts nervtötenderes als irgendwelche Bugs die damit zu tun haben dass die exe mit VS2005 gebaut wurde und das PlugIn mit VS2008...meistens ist sowas an sich schon nahezu unmöglich zu debuggen, aber am tollsten ist natürlich, wenn der Bug sich dann noch durch eine Race-Condition äußert (so wars beim letzten Mal als ich mit so einem C++ Interface arbeiten durfte)...



  • Ich hab' durchwegs gute Erfahrungen mit C++ DLLs gemacht. Wobei wir auch keine Compiler mischen.
    Wenn man das tut, dann muss man natürlich auf wesentlich mehr aufpassen, als wenn man nicht mischt.

    Aber auch das geht gut, wenn man es eben tut (aufpassen). Natürlich muss man dazu ziemlich genau wissen was problematisch sein kann, und was nicht.

    Daher würde ich Compiler mischen auch nicht unbedingt empfehlen.



  • hustbaer schrieb:

    Gästchen schrieb:

    Hmh. Das heißt also, wenn ich die DLL weitergeben will, dürfen keinerlei Exceptions fliegen (ich weiß ja nicht, welches Werkzeug der Client verwendet).

    Kommt drauf an.
    Wir hatten z.T. schon closed-source ThirdParty DLLs mit unbeschnittenen C++ Interfaces. Die gab es dann halt in mehreren Versionen, eben für alle "noch interessanten" C++ Compiler für Windows. Damals waren das bei uns vc6, vc7.0, vc7.1 und vc80.
    Und falls deine DLL open-source ist, musst du auch "nur" sicherstellen dass man sie mit allen "noch interessanten" Compilern bauen kann - der User/Kunde muss das dann halt selbst machen.

    Die DLL ist nicht open-source, als Client wird teilweise noch VB6 verwendet.

    hustbaer schrieb:

    Ich muß also in jeder exportierten Funktion alle möglichen Exceptions fangen und entsprechend in Fehlercodes übersetzen, damit ich wieder sauber bin.

    Kann man so machen denke ich.

    Und wenn ich jetzt ein beispielweise ein COM-Objekt ausgebe, dann habe ich aber viel zu tun. Bislang habe ich jedenfalls ganz und gar keine Lust, in wirklich jeder Methode mit try und unzähligen catches zu fummeln. Dabei muss ich zudem doch auch noch ganz genau wissen, welche Exceptions geworfen werden können. Kann ich das überhaupt wissen? Und selbst wenn ich das weiss, bleibt das dann auch auf immer und ewig das selbe, oder kann sich das auch mal ändern? Und wie kann ich sicherstellen, dass mir nichts durchrutscht? Gibt es für die Üversetzung von Exception nach Fehlercode etwas einfaches/allgemeingültiges?

    hustbaer schrieb:

    Wie löst man das denn am Saubersten? Eben hört sich das jedenfalls danach an, als wäre cpp für DLLs generell ungeeignet. Oder verzichtet man auf die Standard-Bibliothek komplett, damit nicht vielleicht doch mal eine Exception durchrutscht, die man nicht bedacht hat?

    Selbst wenn man auf die Standard-Library verzichtet, bleiben noch Exceptions wie bad_alloc (new).

    Kein Problem dank std::nothrow.


  • Administrator

    @dot,
    Hast du noch nie ein C++ Framework benutzt? 😕
    Fast alle C++ Frameworks, welche ich kenne, benutzen C++ DLLs und haben oftmals auch Exceptions, welche über die DLL Grenzen hinweg geworfen werden. Die C++ Standardbibliothek (oder MSVC Runtime), Boost, Qt, SFML, Irrlicht, usw. usf.

    Ich kann mich der Aussage von hustbaer nur anschliessen. Solange man keine Kompiler mischt, stellt dies absolut kein Problem dar. Und für den MSVC muss man halt noch ein Makro PROJECT_DLL_EXPORT basteln:

    #ifdef _MSC_VER
    # ifdef PROJECT_DLL_BUILD
    #  define PROJECT_DLL_EXPORT __declspec(dllexport)
    # else
    #  define PROJECT_DLL_EXPORT __declspec(dllimport)
    # endif
    #else
    # define PROJECT_DLL_EXPORT
    #endif
    

    Grüssli



  • Wenn als Client wirklich viele verschiedene Sprachen genutzt werden, dann gibt es IMO nur zwei gute Möglichkeiten: COM oder ganz ganz einfache Win32 DLLs.

    Wobei ich mit ganz ganz einfach meine, dass der Client keine Strukturen im Speicher aufbauen muss, dass es kaum oder keine Objekte gibt die man dann mit Handles "verstecken" muss. Dass es nicht allzu viele Funktionen gibt. Dass man bei Parametern und Returnwerten mit int, long, bool & Strings auskommt etc.
    Sonst wird das nämlich mit Sprachen wie VB, VB.Net, C# etc. ziemlich schnell ziemlich lästig.

    Wenn es einfach möglich ist, die Funktionalität die du anbieten musst auf so eine API abzubilden, dann wäre das vermutlich die beste Möglichkeit. COM ist nämlich auch ein wenig lästig, z.B. muss man die COM DLL auch erstmal registrieren (oder dem Programm mittels Manifest unterjubeln), sonst kann das Programm das COM Objekt gar nicht erzeugen. Und einen COM-Wrapper für so eine API zu basteln, ist dann auch sehr einfach - für Clients die lieber mit COM arbeiten.

    Wenn du mit so einer "beschnittenen" API nicht auskommst, dann nimm COM "pur".

    Was das Fangen und Übersetzen von Exceptions angeht... ja, das kann auch lästig werden. Muss aber nicht. Ich kann folgenden "Trick" anbieten:

    HRESULT HresultFromCurrentException()
    {
    	try
    	{
    		throw; // re-throw current exception
    	}
    	catch (_com_error const& e)
    	{
    		return e.Error();
    	}
    	catch (std::bad_alloc const&)
    	{
    		return E_OUTOFMEMORY;
    	}
    	catch (CMemoryException*)
    	{
    		return E_OUTOFMEMORY;
    	}
    	catch (std::invalid_argument const&)
    	{
    		return E_INVALIDARG;
    	}
    	// more catch handlers
    	catch (...)
    	{
    		return E_FAIL;
    	}
    }
    
    void DeleteCurrentException()
    {
    	try
    	{
    		throw; // re-throw current exception
    	}
    	catch (CException* e)
    	{
    		e->Delete();
    	}
    	catch (...)
    	{
    		// not an MFC exception -> nothing to delete
    	}
    }
    
    // COM Methoden nach diesem Schema implementieren:
    HRESULT MyCoClass::MyMethod(LONG myParam)
    {
    	try
    	{
    		m_myCppClass->MyMethod(myParam);
    		return S_OK;
    	}
    	catch (...)
    	{
    		HRESULT hr = HresultFromCurrentException();
    		DeleteCurrentException();
    		return hr;
    	}
    }
    

    Vorteil: du musst den ganzen Übersetzungscode Exception -> Errorcode nur an einer Stelle schreiben, und nur an einer Stelle warten.

    Natürlich ist das keine Silver-Bullet. Fälle wo der Context einer Exception nötig ist, um daraus ableiten zu können was schief gegangen ist (d.h. welchen Error-Code man zurückgeben sollte), kann man damit nicht so einfach abdecken.

    Allgemein würde ich sagen: je weniger wichtig die 100% zuverlässige Unterscheidung verschiedener Fehlerursachen ist, desto besser kann man damit arbeiten.

    Achja, nochwas: mit COM kann man irgendwie ne erweiterte Error-Info für den "aktuellen Fehler" setzen, so dass der Client sich z.B. eine textuelle Fehlermeldung holen kann. Oft reicht es dann wenn im Text drinnen steht was schief gegangen ist, auch ohne dass anhand des Error-Codes 100% genau erkennbar wäre was es war. Von ADO/OLEDB bekommt man sehr oft solche Fehler, der Errorcode ist dann einfach irgendein unspezifischer "geht net" Code, und im Text steht dann "constraint violation soundso in zeile soundso etcpp.". Reicht mMn. auch vollkommen - im Logfile ist es ne super Hilfe die Textuelle Fehlermeldung zu haben, und im Programm könnte ich mit der Info sowieso nix anfangen.

    Falls du das machen willst, kannst du analog zu dem Code oben eine SetComErrorMessageFromCurrentException() Funktion basteln, die einfach die Error-Message von der aktuellen Exception holt, und mit den entsprechenden Funktionen an das COM Subsystem übergibt.

    Wie genau die übergabe der Error-Message an das COM Subsystem geht hab ich nicht im Kopf, und ich hab auch keinen Code hier wo ich nachsehen könnte. Sollte sich aber relativ einfach ergoogeln lassen.



  • Dravere schrieb:

    @dot,
    Hast du noch nie ein C++ Framework benutzt? 😕

    Doch, tu ich ständig.

    Dravere schrieb:

    Ich kann mich der Aussage von hustbaer nur anschliessen. Solange man keine Kompiler mischt, stellt dies absolut kein Problem dar.

    Da es kein eingermaßen portables C++-ABI gibt, ist C++ als Interfacesprache im Allgemeinen ungeeignet, denn es funktioniert eben nur unter ganz speziellen Voraussetzungen.
    Nämlich eben nur mit herstellerspezifischen Spracherweiterungen und nur solange man ein und den selben Compiler verwendet. Und das heißt exakt ein und die selbe Version des Compilers mit ein und den selben Einstellungen etc.
    Solange man mit diesen Einschränkungen leben kann geht es natürlich.
    Irgendwelche Frameworks, die ich mir ganz spezifisch für meine aktuelle Toolchain builde, funktionieren so natürlich völlig problemlos, sonst wären Features wie dllexport ja komplett sinnlos.
    Und da habe ich auch nie widersprochen, auch wenn ich mich da anfangs zugegebenermaßen sehr ungünstig ausgedrückt hab.
    Aber load-time dynamic linking solcher Frameworks ist eben bei weitem nicht der allgemeine Fall, viele dieser Libraries könnte ich z.B. wohl genausogut auch statisch linken.
    Ich wage sogar mal zu behaupten, dass das nur eine sehr sehr spezielle, wenn auch häufig anzutreffende, Anwendung von dlls ist.
    Wenn es in Bereiche geht wo run-time dynamic linking gebraucht wird, z.B. in irgendwelchen Plug-In-Systemen, schauts z.B. schon wieder ganz anders aus.

    Für den allgemeinen Fall gibts da eben C, COM etc.

    Btw: Solange man sich auf rein abstrakte Interfaces, einfache Vererbung und keine STL-Typen beschränkt, könnte man mit C++ sogar relativ weit kommen, denn die meisten Compiler werden für den Fall unter Windows wohl ein COM-kompatibles Layout generieren...



  • dot schrieb:

    Btw: Solange man sich auf rein abstrakte Interfaces, einfache Vererbung und keine STL-Typen beschränkt, könnte man mit C++ sogar relativ weit kommen, denn die meisten Compiler werden für den Fall unter Windows wohl ein COM-kompatibles Layout generieren...

    Tun sie, ja.
    Gibt auch Projekte die auf diese Art recht erfolgreich Plugin-Systeme bauen, z.B. foobar2000.

    Mit Exceptions wäre ich da allerdings vorsichtig. Das geht wohl nur, wenn eine mehr oder weniger standardisierte ABI verwendet wird. Beispielsweise die Itanium ABI die u.a. auch GCC verwendet.

    (Wobei ich mir beim Thema Exceptions unter Windows nicht wirklich sicher bin was GCC da macht. Gibt ja immerhin zwei verschiedene Modelle beim Windows GCC - zumindest eins davon wird nicht dem Itanium Standard entsprechen)


Anmelden zum Antworten