[gelöst][DLL] definition of dllimport function not allowed - bei templates



  • Folgendes Szenario:

    #ifdef (BUILD_MYLIB)
    #define MYCLASS_DLLINEX _declspec(dllexport)
    #else
    #define MYCLASS_DLLINEX _declspec(dllimport)
    #endif
    
    template <class T, class U>
    class MYCLASS_DLLINEX Foo
    {
      Foo() {};
      virtual ~Foo() = 0;
    };
    
    template <class T, class U>
    Foo::~Foo()
    { //ERROR: definition of dllimport function not allowed
    }
    

    An der gezeigten Stelle spuckt der Compiler die entsprechende Fehlermeldung. Das Problem ist, wenn ich die Definition von ~Foo inline mache, spuckt eines unserer QS-Tools Galle, dass man doch bitte virtuelle Funktionen nicht inline definieren möge, weil das ja soo schlecht für die Performance ist. Gegen das Tool kann ich leider nicht an.
    Was ich mich allerdings frage: ist das überhaupt nötig/sinnvoll, bei Templates die dllimport/export-Spielereien zu machen? IIRC gibts Probleme mit der vtable wenns nicht so ist, oder?



  • So, nach ein bisschen STFW und FDAK (FragDenAllwissendenKollegen) bin ich auf ne Lösung gestoßen:

    #if defined(BUILD_MYLIB)
    #define MYCLASS_DLLINEX _declspec(dllexport)
    #else
    #define MYCLASS_DLLINEX _declspec(dllimport)
    #endif
    
    template <class T, class U>
    class MYCLASS_DLLINEX Foo
    {
      Foo() {};
      virtual ~Foo() = 0;
    };
    
    //definitionen nur innerhalb der DLL mitnehmen
    #if defined(BUILD_MYLIB)  
    template <class T, class U>
    Foo::~Foo()
    { //ERROR: definition of dllimport function not allowed
    }
    #endif
    

    kanns damit irgendwelche Probleme geben? Alle Template-instantiierungen, die in den importierenen Programmteilen benutzt werden, sind in der exportierenden DLL vorhanden.



  • Und ich denke damit hast du genau nichts gekonnt. Afaik koennen templates so nicht exportiert werden. Das wuerde auch garkeinen Sinn machen. Templates stehen doch eh komplett im Header, was soll also spaeter aus der DLL importiert werden in der Anwendung?
    Ich wuerde MYCLASS_DLLINEX komplett bei templates weglassen (und mache es auch selbst so).

    Anders sieht es aus, wenn du eine Instantiierung eines templates exportieren moechtest. Das koennte so aussehen:

    template class MYCLASS_DLLINEX Foo<int, double>;
    


  • templateloser schrieb:

    Templates stehen doch eh komplett im Header, was soll also spaeter aus der DLL importiert werden in der Anwendung?

    Der Sourcecode von Templates steht im Header, richtig. DLLs sind aber kein Sourcecode sondern bereits compiliert. Bei der Template-Instantiierung legt der Compiler in jeder Übersetzungseinheit den selben Code fürs fertig instatiierte Template an. Das würde zwar eigentlich gegen die ODR verstoßen, der Linker sucht sich aber hinterher beim normalen Linken aus, welchen der übersetzten Codes er tatsächlich verwendet. Bei DLLs geht das nicht, da muss man von vornherein festlegen, in welcher Übersetzungseinheit der Templatecode stehen soll. Schließlich will ich von dem Template erben (siehe die virtuellen Funktionen) und ich kann schlecht in Übersetzungseinheit A den vptr der dortigen Instantiierung benutzen und in Übersetzungseinheit B den anderen.



  • Ja, und das faellt dann unter den von mir genannten Punkt zwei.
    Der Unterschied liegt doch darin ob du in der DLL bereits das template instanziierst, dann musst du diese Instanz auch exportieren (s. letzte Zeile meines letzten Posts) oder ob es "einfach nur ein template ist". (was genau du vorhast geht aus deinem Beispiel ja nicht vor, virtuelle Funktionen hin oder her, es sind beide Moeglichkeiten gegeben.)

    Im ersten Fall legt der Compiler in der DLL bereits den Quellcode fuer diese eine Instanziierung ab, und dieser soll dann von der Client-Anwendung genutzt werden.

    Im letzten Fall wuerde dann halt die Anwendung, welche die DLL einbindet, die Instanz erzeugen (und den Header einbinden); da in der DLL keine Instanz (oder zumindest keine mit diesen template Argumenten) erzeugt wurde wird die ODR nicht verletzt. In dem Fall wird in der DLL ja auch garkein Code bezeuglich des templates zu finden sein - warum auch, der Compiler kompiliert templates ja erst bei der Instanziierung.



  • Das Template wird ja eben nicht nur in der DLL isntantiiert. (Sonst hätte es ja auch keinen ZWeck, sie zu exportieren). Ich versuchs mal zu skizzieren:

    //DLL A:
    //#######
    template <class T, class U>
    struct DLLA_DLLINEX FooBase
    {
      FooBase() {};
      virtual ~FooBase() = 0;
      virtual void DoBar() = 0;
      void Bar() { DoBar(); };
    };
    
    #ifdef BUILD_DLLA
    template <class T, class U>
    FooBase::~FooBase()
    {}
    #endif
    
    struct DLLA_DLLINEX Foo1 : public FooBase<T1, U1>
    {
      ~Foo1() {};
      virtual void DoBar();
    };
    Foo1::DoBar() { dosomething(); }
    
    //Foo2, etc.
    
    //DLL B:
    //#######
    #include <foo1.h> //auch das template wird included!
    struct DLLB_DLLINEX FooCreator
    {
      FooBase<T1, U1>* createFoo1() {return new Foo1();}
      //usw.
    };
    
    //DLL C:
    //#######
    #include <foobase.h> //nur das template
    void ItHappens
    {
      FooCreator fc;
      FooBase<T1, U1>* fb1 = fc.createFoo1();
      fb1->Bar();
    }
    

    In DLL A wird also das template durch die verschiedenen Foos instantiiert und der Code hinterlegt (d.h. nur hier die funktionsdefinitionen)
    In DLL B wird der Ctor der abgeleiteten Foos und damit der verschiedenen Templateklassen aufgerufen.
    In DLL C wird FooBase.Bar() der verschiedenen Templateklassen aufgerufen.

    Sowohl in B als auch in C werden nur die Deklarationen der Templatemethoden importiert, dort werden sie instantiiert, aber eben nur als Deklarationen - d.h. der Compiler generiert Aufrufe an Instanzen der Templatefunktionen in DLL A, richtig?



  • Hm, ich denke ich sehe jetzt worauf genau Du hinauswillst. Was Dich stoert ist, dass das template in DLL B und DLL C eingebunden wird (und nach meiner Methode [ohne #ifdef]) wuerde das die ODR verletzen, weil eine Instanz sowohl in der DLL, als auch der eigentlichen Anwendung vorhanden ist, richtig?

    So saehe meine Version aus:

    //DLL A:
    //#######
    template <class T, class U>
    struct FooBase // das template selbst nicht exportieren
    {
      FooBase() {};
      virtual ~FooBase() = 0;
      virtual void DoBar() = 0;
      void Bar() { DoBar(); };
    };
    
    template <class T, class U>
    FooBase::~FooBase()
    {}
    
    // hier wird eine Instanz erzeugt, also diese exportieren/importieren
    template struct DLLA_DLLINEX FooBase<T1, U1>;
    
    struct DLLA_DLLINEX Foo1 : public FooBase<T1, U1>
    {
      ~Foo1() {};
      virtual void DoBar();
    };
    Foo1::DoBar() { dosomething(); }
    
    // Rest identisch zu Deiner Version
    

    Ich denke (-> weiss es nicht mit Bestimmtheit), dass dieses Vorgehen das selbe ist, wie eine explizite template Instaziierung (es ist ja auch nichts anderes, nur wird diese eben noch exportiert/importiert). Oberflaechlich betrachtet koennte man ja behaupten, dass eine explizite Instaziierung eines templates ebenfalls die ODR verletzt: man hat sowohl das template an sich + diese eine Instanz. Der Compiler sieht aber, dass es eben eine explizite Instanz ist und weiss damit, dass er die schon irgendwo hat (oder schaut eben an einer anderen Stelle nach). Das selbe geschieht oben: hier gibt sich der Compiler damit zufrieden, weil er weiss, dass bereits eine Instanz erzeugt wurde und importiert werden kann/muss. Darum wird jeweils der Quellcode aus der DLL genutzt.

    Das sind meine Gedanken dazu. Hoffe das war verstaendlich und auf dein Problem bezogen 😉

    PS: Ich habe mich dazu mal etwas belesen, weil ich auf ziemliche Probleme gestossen bin mit templates und DLLs. Wenn dein Weg zutreffen wuerde, koennte man ja garnicht mit templates aus z.B. der STL ueber DLL Grenzen hinweg arbeiten, denn da duerfte das #ifdef Zeug fehlen. Sehr wohl kannst du aber eine Instanz erzeugen und diese exportieren. Allerdings: mit STL Klassen ist das ganze sowieso noch einen Zacken haerter, ich bin da auf teilweise riessige Makros gestossen um letztlich auch die Internals der Klasse zu exportieren [und in meinem speziellen Fall die daemliche Warnung zu entfernen], diese unterschieden sich aber von VS Version zu VS Version und damit artet das ganze extrem aus.
    Hier mal eine google Suche, die Dir evtl weiterhilft



  • Den Artikel habe ich u.a. damals dazu gelesen. Auch alles nur mehr oder weniger Mutmassung, macht aber imho Sinn.



  • Danke für die Links, werd mich da mal einlesen.


Anmelden zum Antworten