Exportieren von Klassen, die `std::`-Objekte (Vektor, Karte etc.) aus einer DLL enthalten

Lesezeit: 10 Minuten

Exportieren von Klassen die std Objekte Vektor Karte etc aus einer
RnR

Ich versuche, Klassen aus einer DLL zu exportieren, die Objekte wie enthalten std::vectors und std::strings – Die gesamte Klasse wird als DLL-Export deklariert durch:

class DLL_EXPORT FontManager {

Das Problem ist, dass ich für Mitglieder der komplexen Typen diese Warnung erhalte:

warning C4251: 'FontManager::m__fonts' : class 'std::map<_Kty,_Ty>' needs to have dll-interface to be used by clients of class 'FontManager'
      with
      [
          _Kty=std::string,
          _Ty=tFontInfoRef
      ]

Ich kann einige der Warnungen entfernen, indem ich ihnen die folgende Forward-Klassendeklaration voranstelle, obwohl ich den Typ der Member-Variablen selbst nicht ändere:

template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;
template class DLL_EXPORT std::vector<tCharGlyphProviderRef,std::allocator<tCharGlyphProviderRef> >;
std::vector<tCharGlyphProviderRef> m_glyphProviders;

Sieht so aus, als würde die Vorwärtsdeklaration die DLL_EXPORT denn wenn das Mitglied kompiliert wird, aber ist es sicher?
Ändert es wirklich etwas, wenn der Client diesen Header kompiliert und die std:: Behälter auf seiner Seite?
Wird es alle zukünftigen Verwendungen eines solchen Containers machen DLL_EXPORT (und möglicherweise nicht inline)?
Und löst es wirklich das Problem, vor dem die Warnung warnen will?

Ist diese Warnung etwas, worüber ich mir Sorgen machen sollte, oder wäre es am besten, sie im Rahmen dieser Konstrukte zu deaktivieren?
Die Clients und die DLL werden immer mit denselben Bibliotheken und Compilern erstellt, und das sind reine Header-Klassen …

Ich verwende Visual Studio 2003 mit der Standard-STD-Bibliothek.


Aktualisieren

Ich möchte Sie jedoch mehr ansprechen, da ich sehe, dass die Antworten allgemein sind und wir hier über Std-Container und -Typen (wie z std::string) – vielleicht ist die Frage wirklich:

Können wir die Warnung für Standardcontainer und -typen deaktivieren, die sowohl dem Client als auch der DLL über dieselben Bibliotheksheader zur Verfügung stehen, und sie genauso behandeln, wie wir eine behandeln würden int oder irgendein anderer eingebauter Typ? (Auf meiner Seite scheint es richtig zu funktionieren)
Wenn ja, sollten die Bedingungen sein, unter denen wir dies tun können?

Oder sollte die Verwendung solcher Container vielleicht verboten oder zumindest besonders darauf geachtet werden, dass keine Zuweisungsoperatoren, Kopierkonstruktoren usw. in den DLL-Client eingebettet werden?

Im Allgemeinen würde ich gerne wissen, ob Sie der Meinung sind, dass das Entwerfen einer DLL-Schnittstelle mit solchen Objekten (und zum Beispiel deren Verwendung, um Dinge als Rückgabewerttypen an den Client zurückzugeben) eine gute Idee ist oder nicht und warum, würde ich gerne haben eine “hohe” Schnittstelle zu dieser Funktionalität …
Vielleicht ist die beste Lösung, was Neil Butterworth vorgeschlagen hat – das Erstellen einer statischen Bibliothek?

  • Empfohlene verwandte Lektüre: stackoverflow.com/q/5347355/103167

    – Ben Voigt

    6. Januar 2021 um 17:39 Uhr

1646353212 414 Exportieren von Klassen die std Objekte Vektor Karte etc aus einer
Christoph

Wenn Sie vom Client aus ein Mitglied Ihrer Klasse berühren, müssen Sie eine DLL-Schnittstelle bereitstellen. Eine DLL-Schnittstelle bedeutet, dass der Compiler die Funktion in der DLL selbst erstellt und importierbar macht.

Da der Compiler nicht weiß, welche Methoden von den Clients einer DLL_EXPORTED-Klasse verwendet werden, muss er erzwingen, dass alle Methoden dll-exportiert werden. Es muss erzwingen, dass alle Mitglieder, auf die von Clients zugegriffen werden kann, auch ihre Funktionen per DLL exportieren müssen. Dies geschieht, wenn der Compiler Sie vor nicht exportierten Methoden warnt und der Linker des Clients Fehler sendet.

Nicht jedes Mitglied muss mit dll-export markiert werden, zB private Mitglieder, die von Clients nicht berührt werden können. Hier können Sie die Warnungen ignorieren/deaktivieren (Vorsicht vor vom Compiler generierten dtor/ctors).

Andernfalls müssen die Mitglieder ihre Methoden exportieren. Die Vorwärtsdeklaration mit DLL_EXPORT exportiert die Methoden dieser Klassen nicht. Sie müssen die entsprechenden Klassen in ihrer Kompilationseinheit als DLL_EXPORT kennzeichnen.

Worauf es hinausläuft … (für nicht dll-exportierbare Mitglieder)

  1. Wenn Sie Mitglieder haben, die nicht von Clients verwendet werden/können, schalten Sie die Warnung aus.

  2. Wenn Sie Mitglieder haben, die von Clients verwendet werden müssen, erstellen Sie einen DLL-Export-Wrapper oder erstellen Sie Umleitungsmethoden.

  3. Um die Anzahl der extern sichtbaren Mitglieder zu reduzieren, verwenden Sie Ansätze wie die PIMPL-Idiom.


template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;

Dadurch wird eine Instanziierung der Vorlagenspezialisierung in der aktuellen Übersetzungseinheit erstellt. Das erstellt also die Methoden von std::allocator in der dll und exportiert die entsprechenden Methoden. Dies funktioniert nicht für konkrete Klassen, da dies nur eine Instanziierung von Vorlagenklassen ist.

  • Danke Chris – in diesem Fall gibt es das zusätzliche Problem, dass es sich um Standardcontainer handelt, die nur Header sind – also bin ich mir nicht sicher, ob die Funktionen über die DLL auf diesen Mitgliedern aufgerufen werden müssen, solange diese Funktionen in beiden identisch sind der Client und die DLL – lange Rede kurzer Sinn, meine sind den Clients NICHT direkt ausgesetzt, also werde ich die Warnungen deaktivieren 🙂 – aber sind Sie sicher, dass es weh tun würde, sie zu deaktivieren, selbst wenn sie unter den gegebenen Umständen offengelegt würden?

    – RnR

    20. April 2009 um 11:00 Uhr

  • Übrigens – es gibt ein Problem bei der Definition, dass Klassen privat sind, sodass sie nicht berührt werden können – dies berücksichtigt nicht das Kopieren/Erstellen/Zerstören dieser Objekte – also können wir sagen, dass wir die Warnung nur deaktivieren können, wenn diese Operationen nicht direkt sind für den Auftraggeber an unserem Hauptobjekt möglich. In diesem Fall bin ich mir jedoch fast sicher, dass wir überhaupt kein Problem haben, da die Funktionen für den Client genauso verfügbar sind wie für die DLL – über die Standard-Header des Compilers – richtig? 🙂

    – RnR

    20. April 2009 um 11:24 Uhr

  • Ich denke, dass Vorlagenklassen diese Probleme möglicherweise nicht aufdecken, da die Methoden in den Implementierungsmodulen des Clients instanziiert werden. Dies funktioniert auch für Inline-Funktionen. Ich weiß aber nicht, ob damit alle Probleme weg sind, denn manche Templates rufen nicht-Template-Code auf (zB std::exception::what).

    – Christoph

    20. April 2009 um 11:27 Uhr

  • “Kopieren/Erstellen/Zerstören wird dabei nicht berücksichtigt” ja, wie erwähnt. Aber das Deklarieren dieser Operationen umgeht die Probleme.

    – Christoph

    20. April 2009 um 11:29 Uhr

Diese Warnung teilt Ihnen mit, dass Benutzer Ihrer DLL keinen Zugriff auf Ihre Container-Member-Variablen über die DLL-Grenze hinweg haben. Durch explizites Exportieren werden sie verfügbar, aber ist das eine gute Idee?

Im Allgemeinen würde ich vermeiden, std-Container aus Ihrer DLL zu exportieren. Wenn Sie absolut garantieren können, dass Ihre DLL mit derselben Laufzeit- und Compilerversion verwendet wird, sind Sie auf der sicheren Seite. Sie müssen sicherstellen, dass der in Ihrer DLL zugewiesene Speicher mit demselben Speichermanager freigegeben wird. Andernfalls wird bestenfalls zur Laufzeit behauptet.

Stellen Sie Container also nicht direkt über DLL-Grenzen hinweg bereit. Wenn Sie Containerelemente verfügbar machen müssen, tun Sie dies über Zugriffsmethoden. Trennen Sie in dem von Ihnen bereitgestellten Fall die Schnittstelle von der Implementierung und machen Sie die Schnittstelle auf DLL-Ebene verfügbar. Ihre Verwendung von Standardcontainern ist ein Implementierungsdetail, auf das der Client Ihrer DLL nicht zugreifen muss.

Alternativ können Sie tun, was Neil vorschlägt, und eine statische Bibliothek anstelle einer DLL erstellen. Sie verlieren die Möglichkeit, die Bibliothek zur Laufzeit zu laden, und Benutzer Ihrer Bibliothek müssen jedes Mal neu verknüpfen, wenn Sie Ihre Bibliothek ändern. Wenn dies Kompromisse sind, mit denen Sie leben können, würde eine statische Bibliothek Sie zumindest an diesem Problem vorbeibringen. Ich werde immer noch argumentieren, dass Sie Implementierungsdetails unnötig offenlegen, aber es könnte für Ihre spezielle Bibliothek sinnvoll sein.

Es gibt andere Probleme.

Einige STL-Container können “sicher” exportiert werden (z. B. Vektor), andere nicht (z. B. Karte).

Map zum Beispiel ist unsicher, weil es (jedenfalls in der MS STL-Distribution) ein statisches Element namens _Nil enthält, dessen Wert in Iterationen verglichen wird, um das Ende zu testen. Jedes mit STL kompilierte Modul hat einen anderen Wert für _Nil, und daher kann eine in einem Modul erstellte Map nicht von einem anderen Modul iteriert werden (sie erkennt nie das Ende und explodiert).

Dies würde auch dann gelten, wenn Sie statisch auf eine Bibliothek verlinken, da Sie niemals garantieren können, welchen Wert _Nil haben wird (es ist nicht initialisiert).

Ich glaube, STLPort tut dies nicht.

  • Danke für die Details – persönlich habe ich immer STLPort bevorzugt und jetzt habe ich einen anderen Grund dafür: D

    – RnR

    5. August 2010 um 22:31 Uhr

Der beste Weg, den ich gefunden habe, um mit diesem Szenario umzugehen, ist:

Erstellen Sie Ihre Bibliothek und benennen Sie sie mit den Compiler- und STL-Versionen, die im Bibliotheksnamen enthalten sind, genau wie die Boost-Bibliotheken.

Beispiele:

– FontManager-msvc10-mt.dll für die dll-Version, spezifisch für den MSVC10-Compiler, mit der Standard-stl.

– FontManager-msvc10_stlport-mt.dll für die dll-Version, spezifisch für den MSVC10-Compiler, mit dem stl-Port.

– FontManager-msvc9-mt.dll für die dll-Version, spezifisch für den MSVC 2008-Compiler, mit der Standard-stl

– libFontManager-msvc10-mt.lib für die statische Lib-Version, spezifisch für den MSVC10-Compiler, mit der Standard-stl.

Wenn Sie diesem Muster folgen, vermeiden Sie Probleme im Zusammenhang mit verschiedenen STL-Implementierungen. Denken Sie daran, dass sich die STL-Implementierung in vc2008 von der STL-Implementierung in vc2010 unterscheidet.

Sehen Sie sich Ihr Beispiel mit der Bibliothek boost::config an:

#include <boost/config.hpp>

#ifdef BOOST_MSVC
#  pragma warning( push )
#  pragma warning( disable: 4251 )
#endif

class DLL_EXPORT FontManager
{
public:
   std::map<int, std::string> int2string_map;
}

#ifdef BOOST_MSVC
#  pragma warning( pop )
#endif

Eine Alternative, die anscheinend nur wenige in Betracht ziehen, besteht darin, überhaupt keine DLL zu verwenden, sondern statisch mit einer statischen .LIB-Bibliothek zu verknüpfen. Wenn Sie das tun, verschwinden alle Probleme beim Exportieren/Importieren (obwohl Sie immer noch Probleme mit der Namensverfälschung haben werden, wenn Sie verschiedene Compiler verwenden). Sie verlieren natürlich die Features der DLL-Architektur, wie z. B. das Laden von Funktionen zur Laufzeit, aber dies kann in vielen Fällen ein geringer Preis sein.

  • Sie können sowieso keine Compiler mischen, wenn Ihre DLL-Schnittstelle C++-Klassen verwendet – insbesondere wenn Sie Standardbibliothekstypen verwenden.

    – MSalter

    20. April 2009 um 10:55 Uhr

  • Es gibt immer eine Reihe von Möglichkeiten, eine Katze zu häuten, und wenn dies eine Option ist, stimme ich zu, dass sie ergriffen werden sollte.

    – RnR

    20. April 2009 um 11:02 Uhr

Gefunden Dieser Beitrag. Kurz gesagt, Aaron hat oben die „echte“ Antwort; Stellen Sie keine Standardcontainer über Bibliotheksgrenzen hinweg bereit.

  • Sie können sowieso keine Compiler mischen, wenn Ihre DLL-Schnittstelle C++-Klassen verwendet – insbesondere wenn Sie Standardbibliothekstypen verwenden.

    – MSalter

    20. April 2009 um 10:55 Uhr

  • Es gibt immer eine Reihe von Möglichkeiten, eine Katze zu häuten, und wenn dies eine Option ist, stimme ich zu, dass sie ergriffen werden sollte.

    – RnR

    20. April 2009 um 11:02 Uhr

1646353212 490 Exportieren von Klassen die std Objekte Vektor Karte etc aus einer
TheBigW

Obwohl dieser Thread ziemlich alt ist, habe ich kürzlich ein Problem gefunden, das mich dazu gebracht hat, erneut darüber nachzudenken, Vorlagen in meinen exportierten Klassen zu haben:

Ich habe eine Klasse geschrieben, die ein privates Mitglied vom Typ std::map hatte. Alles funktionierte ganz gut, bis es im Release-Modus kompiliert wurde, selbst wenn es in einem Build-System verwendet wurde, das sicherstellt, dass alle Compiler-Einstellungen für alle Ziele gleich sind. Die Karte war vollständig verborgen und nichts war den Clients direkt ausgesetzt.

Infolgedessen stürzte der Code im Release-Modus einfach ab. Ich schätze, weil verschiedene binäre std::map-Instanzen für die Implementierung und den Client-Code erstellt wurden.

Ich denke, der C++-Standard sagt nichts darüber aus, wie dies für exportierte Klassen gehandhabt werden soll, da dies ziemlich Compiler-spezifisch ist. Ich denke, die größte Portabilitätsregel besteht darin, nur Schnittstellen offenzulegen und das PIMPL-Idiom so oft wie möglich zu verwenden.

Danke für jede Aufklärung

  • Dies ist in der Tat die beste Methode, die ich beim Entwerfen meiner Schnittstellen verwendet habe, mit dem größten zusätzlichen Vorteil, dass wir auch keine Abhängigkeit zwischen den Modulen erstellen und jedes von ihnen jede gewünschte Containerbibliothek verwenden kann.

    – RnR

    10. April 2015 um 14:36 ​​Uhr

928970cookie-checkExportieren von Klassen, die `std::`-Objekte (Vektor, Karte etc.) aus einer DLL enthalten

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy