005
26.06.2005, 14:25 Uhr
~HumeSikkins
Gast
|
Hallo, das Problem tritt nur auf, wenn der entsprechende Code teil einer statischen Bibliothek ist. Beim hinzulinken einer statischen Bibliothek wird nur solcher Code verwendet, der ein "external symbol" auflöst. Da deine Anwendung kein Element aus Foo.o referenziert, Foo.o damit nicht zur Auflösung eines Symbols benötigt wird, wird Foo.o auch nicht hinzugelinkt. Mit dem beschriebenen Ergebnis. Wichtig ist hierbei, dass die Anwendung die Referenz erzeugt. Es hilft also nicht, wenn nur andere Teile deiner Lib Elemente aus Foo referenzieren.
Lösungen gibt es verschiedene. Alle basieren darauf die Abhängigkeit explizit zu machen. Leider gibt es keine portable und gleichzeitig vollautomatische Möglichkeit dafür. Das Ziel "automatische Registrierung beliebiger Klassen einer statischen Bibliothek in einer Factory ohne explizite Referenzierung dieser Klassen in einer Anwendung die gegen die Lib linkt" lässt sich portabel nicht erreichen.
Variante 1: Explizites Linken gegen die obj-Dateien
Wenn du deine Anwendung statt gegen eine statische Lib einfach explizit gegen alle obj-Dateien linkst (die aus der Lib) tritt das Problem nicht auf. Diese Variante lässt sich wohl am Einfachsten automatisieren, da man ja einfach gegen *.o eines speziellen Unterverzeichnisses linken kann, man also nicht alle Dateien explizit benennen muss.
Variante 2: Die unportable Lösung:
Compiler/Linker bieten in der Regel eine Möglichkeit bestimmte Symbole zu erzwingen. Beim Visual Studio gibt es dafür z.B. eine Pragma-Direktive. Mit
C++: |
#pragma comment(linker, "/include:__meinSymbol")
|
kannst du dort eine Symbolreferenz für meinSymbol erzwingen. Blöd ist hierbei, dass der Symbolnamen bereits ein "mangled name" sein muss. Du kannst also nicht einfach
C++: |
#pragma comment(linker, "/include:Foo")
|
schreiben. Am einfachsten ist es wohl, wenn du dir vom Compiler eine Map-Datei (mit allen Symbolen) erstellen lässt. Dann suchst du dir z.B. das Symbol für den Default-Ctor (in der Art: ??0Foo@@QAE@XZ) und packst am Ende alle pragma-Direktiven in eine Datei die du dann in der Anwendung inkludierst.
Hier braucht man für die Automatisierung mindestens ein kleines Script, dass die Dateien der zu registrierenden Klassen durchgeht und eine passende pragma-Direktive extrahiert.
Variante 3: Die Naive:
Die einfachste (und imo auch schlechteste) Lösung besteht darin die Registrierung einfach in der cpp-Datei der Factory zu zentralisieren und damit auf die Automatik zu verzichten.
Variante 4: Die obligatorische die da wo Templates verwendet.
Variante 3 lässt sich mit ein paar Tricks auch so realisieren, dass nicht eine Datei alle Klassendefinitionen kennen muss. Vielmehr kann man die Abhängigkeiten auf Vorwärtsdeklarationen beschränken.
Das Ganze geht so:
1. In einer Datei (z.B. factory_init.h) deklarieren wir eine Template-Funktion namens registerType:
C++: |
/////////////////////////////////////////////////////////////////////////////// // specialize this function for each object type in the type's cpp-file /////////////////////////////////////////////////////////////////////////////// template <class T> void registerType();
|
2. In der selben Datei definieren wir eine Templateklasse die in ihrem Konstruktor die passende registerType-Instanziierung aufruft:
C++: |
/////////////////////////////////////////////////////////////////////////////// // ignore this class /////////////////////////////////////////////////////////////////////////////// template<class T> class RegisterType { public: RegisterType() { registerType<T>(); } };
|
3. In der cpp-Datei jeder zu registrierenden Datei spezialisieren wir nun registerType und implementieren dort alles was zur Registrierung notwendig ist:
C++: |
// foo.cpp #include <foo.h> #include <factory.h> #include <factory_init.h> ...
// an explicit speicalization is not a template and can be defined in a cpp-file template <> void registerType(Foo*) { Factory::instance().registerObj("Foo", ...); ... }
|
4. Nun müssen wir noch irgendwo für jeden zu registrierenden Typ ein RegisterType-Objekt anlegen. Dazu eignet sich z.B. eine Klasse mit vielen RegisterType-Membern.
C++: |
/////////////////////////////////////////////////////////////////////////////// // add an instance of RegisterType to InitFactory for each object type // you want to register. Use a forward declaration as the template argument. // // Finally, instantiate an object of this class either in your application's main function // or in the factory's cpp-file. /////////////////////////////////////////////////////////////////////////////// class InitFactory { RegisterType<class Foo> fooReg; RegisterType<class Bar> barReg; ... };
|
5. Als letztes muss noch irgendwo eine InitFactory-Instanz angelegt werden. Eine Möglichkeit wäre z.B. in der main-Funktion der Anwendung. Alternativ kann man aber z.B. auch eine globale Instanz in der cpp-Datei der Factory anlegen. Da die Factory von der Anwendung referenziert wird, wird ihre obj-Datei garantiert gelinkt und damit auch file-statische Variablen initialisiert.
C++: |
// factory.cpp namespace { InitFactory registerKnownTypes; }
|
Hier wird jetzt auch klar, warum registerType bzw. RegisterType Templates sein müssen. Als Template-Argumente können auch Vorwärtsdeklarationen verwendet werden.
Diese Lösung hat also den Vorteil, dass die Registrierungslogik dezentral (jeweils in der cpp-Datei des zu registrierenden Typs). Außerdem sind die Abhängigkeiten alle nur "by name". Factory, InitFactory und Co sind also nicht von der Definition der zu registrierenden Klassen abhängig. Es bleibt aber natürlich nach wie vor der Nachteil, dass die zu registrierenden Typen irgendwo einmal explizit benannt werden müssen.
Letztlich lässt sich mit dieser Technik Variante 2 portabel implementieren.
Abschließend: Warum Templates? Nur Templates ermöglichen beliebig viele Implementation für ein und den selben Namen. Außerdem erlauben Templates als Argumente vorwärtsdeklarierte Typen.
Warum sowohl eine Funktion registerType und eine Klasse RegisterType? Weil's hübscher ist. Man könnte auch registerType weglassen und stattdessen jeweils den Ctor von RegisterType spezialisieren. |