Der richtige Weg DLLs zu erstellen



  • Moin,

    ich habe mal eine Frage zum erzeugen von DLLs unter Windows und zwar habe ich jetzt schon sehr viele Artikel zu dem Thema gelesen, wie man Code aus DLLs richtig exportiert. Ohne auf den C Style und die Interfacevariante einzugehen komme ich zum dllexport.

    Ich möchte gerne meine kompletten Klassen anderen Programmen zugänglich machen. Die Header bzw. der Code ist onehin zugänglich, jedoch weiß ich nicht ob ich das so ohne weiteres linken kann (ich vermute mal ganz stark dass es nicht geht und kein lib file erzeugt wird.)

    nun habe ich aber auch gelesen, dass es ein ganz schlechter Stiel sei eine ganze klasse per dllexport zu exportieren.

    Wie sieht hier die Expertenmeinung dazu aus?




  • Mod

    Auch Klassen kann man exportieren. Interfaces/COM benutzen auch das Klassen Design.

    Die Frage ist wie man die Allokation dieser Klassen erlaubt. Welche Objekte getauscht werden dürfen. Wie überhaupt Speicher zwischen DLL und Exe verwaltet wird. Das sind entscheidende Design-Kriterien. Wenn man die löst, bzw. Regeln dafür hat, spricht nichts gegen den Export von Klassen.

    Ich habe genug DLLs die immer im Verbund mit den EXE Dateien kompiliert und immer in der gleichen Runtime Umgebung benutzt werden. Hier kann ich dann natürlich auch machen was ich will, weil ich dann z.B. die CRT (MFC) niemals statisch linke.



  • Interfaces/COM benutzen auch das Klassen Design.

    COM ist reines C! Schau dir einfach mal an, was dein Midl compiler so baut ^^
    COM -Schnittstellen sind nicht die ATL/MFC wrapperklassen !

    Die Frage ist wie man die Allokation dieser Klassen erlaubt. Welche Objekte getauscht werden dürfen. Wie überhaupt Speicher zwischen DLL und Exe verwaltet wird. Das sind entscheidende Design-Kriterien. Wenn man die löst, bzw. Regeln dafür hat, spricht nichts gegen den Export von Klassen.

    Dafuer gibts eigentlich Regeln.

    Ob man Klassen ueber dll's Exportiert, ist ne reine binaerkompatiblitaetsfrage.
    Und das gibts in 3 stufen:

    - new/delete auf exportierte Klassen
    - Inline/template code von beiden seiten aus auf klassen angewand
    - binaerzugriff auf member daten ....

    folgt, das exe und dll 100% binaercompatibel sein muessen. eine oder die klasse muss mit allen details im Speicher von beiden seiten gleich abgebildet werden. wenn nicht, krachts.
    Folge, nicht mal der gleiche compiler garantiert das, es muss nur ne andere runtime gelinkt sein, oder paar compilerflags unterschiedlich, und schon kanns rumsen.

    - nur 100% abstracte Interface ueber dll schicken

    der interface teil einer abgeleiteten klasse von so nem ding besteht nur aus der vtable. die vtable ist ne tabelle auf funktionspointer, deren call convention man setzen /beeinflussen kann. Ansonsten intressieren keine Daten.
    Fazit, nur der vtable aufbau die typen und die call conventionen bestimmen die binaerkompatiblitaet.
    Hier ist meist ueber kompilerklassen/Hersteller die binaerkompatiblitaet gegeben ....
    Also nen (Microsoft) VC 8 und nen VC 10 arbeiten auf der ebene zusammen.
    VC und nen gcc nicht, bzw. nicht adhoc (keine ahnung ob der gcc flags hat, wie man die vtable beeinflussen kann)

    - Reines c interface (auch in C kann man objektorientiert programmieren)
    - dll interface besteht nur aus funktionen, die durch typen und callconventionen bestimmt werden.

    da halten sich alle compiler drann, sofern du keine "exotischen" typen benutzt
    ist auch die einzige möglichkeit, mit anderen programmiersprachen prozessintern zu kooperieren. also andere sprachen (python, visual basic, java ... ) koennen C interfaces bedienen.

    DIe frage, welches Interface du verwendest, ist ne aufwand / Nutzen Kalkulation.

    wenn du deine Dll und exes immer selber unter kontrolle hasst, und das immer als packet selber auch compilierst, spricht nix gegen Punkt 1.
    Wobei du dann auch entscheiden solltest, ob dll oder lib. dll lohnt sich an der stelle nur, wenn das ding auch dynamisch laedst. wenn die mit import lib laedsts, kannst auch gleich ne richtige lib draus machen.

    Wenn du z.b. externe zulieferer hasst, bzw. dein Interface ueber laengere zeit Stabil und binaer abwaertskombatibel sein soll, ist punkt 3 fast pflicht. Ausserdem haben andere die freie compilerwahl.

    Punkt 2 ist halt nen zwischending. man vermeidet zu grosse abhaengigkeiten, vermeidet aber auch 100%ige C interfaces, welche von programmierern meist nicht gern gern geschrieben werden.

    Diese Entscheidung ist fast tiefgreifender wie designentscheidungen und sollte von daher mit Bedacht gewählt werden.

    In unserem Project leiden wir zum Beispiel unter solchen Fehlentscheidungen. WIr haben module extern entwickeln lassen, die interfaces vorgegeben, und dabei war auch schmutz drinne (trotz warnungen).
    Compiler und Bibiothek-versionen wurden "festgeschrieben"
    Nun koennen wir nicht den Compiler und die Bibliotheken wechseln, weil die externen ihre module fuer uns neu compilieren muessten, und die wuerden die Hand kraeftig aufhalten ...

    Bye Bye schoener c++11 Standard 😡

    Ciao ...


  • Mod

    [quote="RHBaum"]

    Interfaces/COM benutzen auch das Klassen Design.

    COM ist reines C! Schau dir einfach mal an, was dein Midl compiler so baut ^^
    COM -Schnittstellen sind nicht die ATL/MFC wrapperklassen !

    [quote]

    Habe ich behauptet die Wrapper Klassen wären COM?
    Was mein MIDL baut sind ganz klar Interface Klassen und "KEIN C"! Meine IDE/Compiler benutzt C++Interface Klassen und kein C!

    Die benutzt auch mein Compiler. COM ist nur ein geniales Beispiel wie weit Abstraktion gehen kann mit einem eigenen Typensystem.



  • philiosphie hin oder her aber so richtig zweckdienlich ist das alles nicht.
    Was spricht eigentlich dagegen einfach an die entsprechenden Klassen ein dllexport dranzuhängen?

    Edit:

    der quellcode ist ja eh offen


  • Mod

    Nichts. Das hatte ich geschrieben.

    Die Frage ist eben wie "portabel" für andere Programmierer das sein soll, und ob Du möchtest das auch andere (neuere/ältere) Compiler die gleiche DLL benutzen dürfen/sollen.



  • Was mein MIDL baut sind ganz klar Interface Klassen und "KEIN C"!

    dann hasst du nen anderen midl und nen anderes COM als ich ^^

    der Editor baut bei mir unter anderem ne .idl. das ist aber kein code den der (c/c++) Compiler compiliert, sondern den packt er maximal in die tlb um das dispatchen auf die schnittstellen zu ermöglichen.

    Dann erzeugt mein midl "*_if.h" und "_i.c"
    Die brauche ich zum kompilieren, und daraus wird code erzeugt ...

    wenn dir die mal anschaust, in den _i.c stehen die ganzen UUIDs die brauchst um die Interfaces zu identifizieren, und und die _if.h erzeugen dir die stubs, die die zugriffsfunktionen definieren.
    Dort wirst sehen, dort kommt statt class immer nur struct vor, die structs selber haben keine methoden, sondern nur member in form von funktionspointern, und alles ist schoen in klassichen C-Stil mit Preprozessor-Makros gewürzt.

    Ne COM Schnittstelle ist aus (c++) compiler sicht nichts anderes als ne struct mit zig funktionspointern wo jegliche c++ spezialitäten abgeschalten werden.

    Anders wuerde COM auch nicht funktionieren, weil c++ klassen eben nicht binaer spezifiziert sind (ABI). Sonst waer es gar nicht möglich, das der gcc (mingw) auf COM zugreifen kann (DirectX ist z.b. COM) oder Visual basic, oder java ...
    COM ist binaer definiert, hat also ein ABI, deshalb koennens keine C++ klassen sein ^^
    Nicht alles was sich wie ne klasse anfühlt, hat auch nen class davor stehen ^^
    Erst die ATL / MFC macht daraus richtige C++ Klassen.

    Aehnliches Thema ist auch Swig z.b.
    Da bekommst von dem C-gerödel auch gar nix mit, trotzdem hasst nen c interface 🙂

    Ciao ...



  • COM ist weder C noch C++ sondern ein ABI...es ist aber so, dass Klassen in MSVC zufälligerweise Kompatibel mit dem COM ABI sind...was das wohl für einen Grund haben mag... 😉



  • COM ist weder C noch C++ sondern ein ABI

    COM ist definitiv ein ABI

    dieses ABI ist mit jedem unter windows lauffähigen C Compiler ansprechbar.
    da windows auch nach Microsofts eigenen Berichten viel auf C Code aufbaut, hab ich vereinfachender Weise das mal als C-Schnittstelle bezeichnet.
    Äquivalent zur WINAPI, welche oft auch als C-Schnittstelle bezeichnet wird.
    Wobei die WINAPI eher prozeduralen Aufbau hat, COM hingegen einen ObjectOrientieren Ansatz.

    dass Klassen in MSVC zufälligerweise Kompatibel mit dem COM ABI sind

    Welche meinst du da ?
    Ich kenne keine einzige "c++ Standard klasse", welche in binaerer Form als COM Interface auftreten kann.
    Ich kenn nur MFC und ATL Klassen, welche COM Funktionalitaeten anbieten, und das sind alles Adapter, die das ObjectOrientierte Binaere Interface von COM auf ein C++ Klasseinterface hieven, ähnlich SWIG Wrapper etc 🙂

    Ciao ...



  • Inwiefern sind MFC und ATL Klassen denn deiner Meinung nach keine C++ Klassen?



  • wiefern sind MFC und ATL Klassen denn deiner Meinung nach keine C++ Klassen?

    Natürlich sind das Klassen, sogar "richtige C++ Klassen" 🙂

    Nur ist Ihr Interface ausschliesslich zu deiner C++ Umgebung binaerkompatibel, und in keinster weisse zum COM Interface.
    Und eigentlich ist diese Bruecke C++ Binaer Interface <-> COM BinaerInterface genau die Aufgabe dieser Mfc/ATL COM Klassen. Nur siehst du ausschliesslich das C++ Interface, die C - COM interfaces werden in deren Implementation versteckt gebaut / benutzt (midl compiler).



  • Hier bitteschön, eine stinknormale C++ Klasse, die ganz ohne MFC, ATL, MIDL oder sonstwas ein COM Interface implementiert:

    class Context : private IDWriteTextRenderer
      {
      private:
        Context(const Context&);
        Context& operator =(const Context&);
    
        ID3D11DeviceContext* context;
    
        com_ptr<ID3D11Buffer> drawing_parameters;
    
        BatchBuffer batch_buffer;
    
        QuadShader::QuadBatch quad_batch;
    
        GlyphAtlas glyph_atlas;
    
        float2 ppp;
    
        ULONG refcount;
    
        STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject);
        STDMETHODIMP_(ULONG) AddRef();
        STDMETHODIMP_(ULONG) Release();
    
        STDMETHODIMP IsPixelSnappingDisabled(void* clientDrawingContext, BOOL* isDisabled);
        STDMETHODIMP GetCurrentTransform(void* clientDrawingContext, DWRITE_MATRIX* transform);
        STDMETHODIMP GetPixelsPerDip(void* clientDrawingContext, FLOAT* pixelsPerDip);
    
        STDMETHODIMP DrawGlyphRun(void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode, const DWRITE_GLYPH_RUN* glyphRun, const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, IUnknown* clientDrawingEffect);
        STDMETHODIMP DrawUnderline(void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE* underline, IUnknown* clientDrawingEffect);
        STDMETHODIMP DrawStrikethrough(void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH* strikethrough, IUnknown* clientDrawingEffect);
        STDMETHODIMP DrawInlineObject(void* clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject* inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown* clientDrawingEffect);
    
      public:
        Context(ID3D11Device* device, ID3D11DeviceContext* context, IDWriteFactory* dwrite_factory, const float2& ppp, const SpriteShader& sprite_shader, const QuadShader& quad_shader, const TexturedShader& textured_paint, const FontShader& font_shader);
        Context(Context&& ctx);
    
        void begin(ID3D11DeviceContext* context, const rectf& viewport);
        void flush();
    
        template <typename Brush>
        void drawQuad(const Brush& brush, const float2& pos, const float2& size)
        {
          drawQuad(brush, pos, size, float2(0.0f, 0.0f), float2(1.0f, 1.0f));
        }
    
        template <typename Brush>
        void drawQuad(const Brush& brush, const float2& pos, const float2& size, const float2& t0, const float2& t1)
        {
          batch_buffer.append(context, QuadShader::Vertex(pos, size, t0, t1), quad_batch, brush);
        }
    
        template <typename F>
        void draw(F f)
        {
          batch_buffer.flush(context);
          f(context);
        }
    
        void draw(const SpriteSheet& sprite_sheet, int sprite, const float2x3& transform);
        void draw(const SpriteSheet& sprite_sheet, int sprite, const float2& pos);
        void draw(IDWriteTextLayout* text, const float2& pos);
      };
    

    funktioniert, weil MSVC mit dem COM ABI kompatibel ist... 😉



  • IDWriteTextRenderer
    ID3D11DeviceContext
    ....

    Du glaubst das sind COM Klassen / Strukturen ?

    MFC, ATL

    Ok, es gibt noch andere Bibliotheken, die COM Wrappen.
    QT kann ganz eingeschränkt in der kommerziellen version auch bissi

    DirectX sind reine COM Dlls
    Die bekommst Du ueber Windows selber, oder indem manuell eine DirectX runtime installierst.
    http://www.microsoft.com/de-de/download/details.aspx?id=35 die zum beispiel.

    Was du hier siehst, ist das DirectXSDK, das ist ne C++ Library.
    Das bietet Dir, voiala Wrapper fuer die directX Runtime aka COM an.
    Deshalb siehst du COM als Klassen 🙂
    das musst du extra installieren, um das uebersetzen zu koennen.
    http://www.microsoft.com/en-us/download/details.aspx?id=6812

    Würdest du direct in com Schreiben, braeuchtest du das SDK nicht. Und das würde furchtbar aussehen. kenn leider kein Beispiel, weils keiner macht 🙂

    Noch nen Hinweis:
    Wenn nen c++ linker Klassen export, erkennst du das meist an dem name mangling
    http://en.wikipedia.org/wiki/Name_mangling
    Nur so sind compiler in der Lage, Klassen mit methoden ueber dll grenzen zu schicken.
    Mach mal mit dem depends
    (http://www.dependencywalker.com/)
    ne Dll auf, die klassen exportiert. Qt dlls zum beispiel.
    Da siehst das sofort !
    Da kann man auch grob erkennen, mit welcher compiler familie die dll erstellt wurde ist manchmal extrem nuetzlich zu wissen. z.b. wenn man selber mit VS compiliert und irgendwoher c++ klassen dlls bekommt die mit dem mingw übersezt wurden, zulinken will, und sich dann wundert warums ned geht (zum glueck scheitert man meist schon dran, das die importlibs .a und ned .lib heissen ^^).

    dann schau dir mal ne typische C dll mit dem depends an.
    WINAPI dlls z.b. also kernel32.dll z.b.
    da schauts ganz anders aus, nix mit name mangling
    So exportieren C Dlls ihre funktionen

    Nun schau dir mal ne typische COM Dll an, directX runtime ist com, wissen wir ja .... also d3dx9_xx.dll ist teil der DX runtime
    schau dir mal die symoble an, erkennst was ?
    Kein name mangling !

    Kleiner Test am Rande, versuch mal das name mangling abzuschalten, und ne Klasse in ner dll zu exportieren. Und nun die preisfrage, warum geht das ned ?

    COM Dlls erkennst ueberigens gut drann, das in den Abhaengigkeiten immer die COM Basis dlls sind, also OLE32.dll, oleaut32,dll und CO ...

    funktioniert, weil MSVC mit dem COM ABI kompatibel ist

    Also in dem Fall funktioniert es, weil das DirectXSDK zu Visual Studio kompatibel ist ^^

    Ciao ...



  • RHBaum schrieb:

    IDWriteTextRenderer
    ID3D11DeviceContext
    ....

    Du glaubst das sind COM Klassen / Strukturen ?

    Es sind COM Interfaces.

    RHBaum schrieb:

    DirectX sind reine COM Dlls
    Die bekommst Du ueber Windows selber, oder indem manuell eine DirectX runtime installierst.
    http://www.microsoft.com/de-de/download/details.aspx?id=35 die zum beispiel.

    Was du hier siehst, ist das DirectXSDK, das ist ne C++ Library.
    Das bietet Dir, voiala Wrapper fuer die directX Runtime aka COM an.
    Deshalb siehst du COM als Klassen 🙂
    das musst du extra installieren, um das uebersetzen zu koennen.

    Das DirectX SDK ist keine Library, sondern ein SDK. Es liefert unter anderem natürlich die entsprechenden Libraries und Header um DirectX unter anderen in C++ verwenden zu können. Ob du das jetzt als Wrapper bezeichnest oder nicht ist eigentlich irrelevant. Wenn du mal einen Blick in die entsprechenden Header wirfst, wirst du feststellen, dass die entsprechenden COM Interfaces in C++ auf stinknormale Klassen mappen (in C auf structs, die einen Pointer auf eine Funktionstabelle enthalten). Und der Grund, wieso das funktioniert, ist wieder mal, dass MSVC eben kompatibel mit COM ist. Von mir aus kannst du's auch so rum sehen, dass COM kompatibel mit MSVC ist, macht eigentlich keinen Unterschied.

    Kleine Anmerkung am Rande: Das DirectX SDK ist deprecated.

    RHBaum schrieb:

    Würdest du direct in com Schreiben, braeuchtest du das SDK nicht. Und das würde furchtbar aussehen. kenn leider kein Beispiel, weils keiner macht 🙂

    COM ist keine Sprache sondern ein Binärstandard; etwas "direkt in COM schreiben" ist eine völlig sinnfreie Aussage.

    RHBaum schrieb:

    Noch nen Hinweis:
    Wenn nen c++ linker Klassen export, erkennst du das meist an dem name mangling
    http://en.wikipedia.org/wiki/Name_mangling
    Nur so sind compiler in der Lage, Klassen mit methoden ueber dll grenzen zu schicken.
    Mach mal mit dem depends
    (http://www.dependencywalker.com/)
    ne Dll auf, die klassen exportiert. Qt dlls zum beispiel.
    Da siehst das sofort !
    Da kann man auch grob erkennen, mit welcher compiler familie die dll erstellt wurde ist manchmal extrem nuetzlich zu wissen. z.b. wenn man selber mit VS compiliert und irgendwoher c++ klassen dlls bekommt die mit dem mingw übersezt wurden, zulinken will, und sich dann wundert warums ned geht (zum glueck scheitert man meist schon dran, das die importlibs .a und ned .lib heissen ^^).

    Danke, aber auch wenn es auf den ersten Blick vielleicht so schein, bin ich mittlerweile wohl doch kein Anfänger mehr. 😉
    Der Grund, wieso das nicht funktioniert, ist, dass es kein ABI für C++ gibt. Klassen in dlls zu exportieren ist ein compilerspezifisches Feature, selbst wenn du für alle dlls und deine Anwendung den selben Compiler verwendest, kann es da bereits zu Problemen kommen, da reicht schon, dass ein einfaches Compilerflag irgendwo falsch gesetzt ist.

    RHBaum schrieb:

    dann schau dir mal ne typische C dll mit dem depends an.
    WINAPI dlls z.b. also kernel32.dll z.b.
    da schauts ganz anders aus, nix mit name mangling
    So exportieren C Dlls ihre funktionen

    C braucht eben kein Name mangling. Dennoch definiert auch C kein ABI und ohne ABI ist auch deine C dll völlig wertlos. Wenn ich dir einfach nur eine, von mir aus in C geschriebene, dll schick, dann wirst du, zumindest ohne etwas Reverse Engineering, nichts damit anfangen können.

    RHBaum schrieb:

    Nun schau dir mal ne typische COM Dll an, directX runtime ist com, wissen wir ja .... also d3dx9_xx.dll ist teil der DX runtime
    schau dir mal die symoble an, erkennst was ?
    Kein name mangling !

    Und?

    Kleiner Test am Rande, versuch mal das name mangling abzuschalten, und ne Klasse in ner dll zu exportieren. Und nun die preisfrage, warum geht das ned ?

    RHBaum schrieb:

    COM Dlls erkennst ueberigens gut drann, das in den Abhaengigkeiten immer die COM Basis dlls sind, also OLE32.dll, oleaut32,dll und CO ...

    COM dlls erkennt man noch besser daran, dass sie die vom COM Standard vorgeschriebenen Symbole exportieren...

    RHBaum schrieb:

    funktioniert, weil MSVC mit dem COM ABI kompatibel ist

    Also in dem Fall funktioniert es, weil das DirectSDK zu Visual Studio kompatibel ist ^^

    Wie oben schon erwähnt ist es eigentlich völlig egal wie herum du's betrachten willst. Der Punkt ist, dass du in MSVC Klassen schreiben kannst, die binärkompatibel zu COM sind. So wie du in MSVC Funktionen schreiben kannst, die binärkompatibel zum Windows ABI sind.



  • Der Punkt ist, dass du in MSVC Klassen schreiben kannst, die binärkompatibel zu COM sind. So wie du in MSVC Funktionen schreiben kannst, die binärkompatibel zum Windows ABI sind.

    Ich verstehs einfach nicht, wie kann eine Klasse binaerkompatibel zu nem ABI sein ?
    Was ich an Deiner Aussage verstehe ist:
    Du schreibst ne Klasse in MSVC, und dann brauchst Du die Klasse nur noch auf den COM Interface pointer runterzucasten, und voila, das ding kannst so als COM Interface rausgeben, und alle anderen Module / Sprachen koennten damit umgehen.

    weil MSVC mit dem COM ABI

    Und funktionieren tut das nur mit dem MSVC, mit anderen c++ compilern nicht ?

    Ist es wirklich das was du meinst ?

    Ciao ....



  • Im Prinzip ja, ganz genau so ist es. Funktioniert aber wohl nicht nur mit MSVC, da jeder vernünftige C++ Compiler unter Windows sich entsprechend an das ABI halten wird...



  • Ich glaub Du übersiehst ne Menge was dazwischen passiert, was aber M$ mit Ihren Tools und SDK's ganz gut verstecken.

    Du kannst zwar von ner struct aus Funktionspointern ableiten, aber dann mappt dir noch keiner die funktionspointer auf die Memberpointer.

    die reihenfolge der Methoden wird in der IDL expliziet festgelegt, musst du Dich im C++ klasseninterface bei M$ dran halten?

    Die ATL z.b. macht da viel mit makros ... Aber das deswegen als kompatibel zu bezeichnen ?
    Dann waer aber nen BSTR auch zu nem char * kompatibel, weil ich brauch ja nur das OLE2A makro 🙂

    Ciao ...



  • Ich glaub du übersiehst bei all der Menge, die dazwischen passiert, einiges. Überleg mal, was der MIDL Compiler dir als Output liefert und was da möglicherweise der Hintergrund sein könnte. MIDL ist nur ein Tool, das Biolerplate Code generieren kann; aber COM funktioniert auch ganz ohne MIDL...


Anmelden zum Antworten