Ein wenig kompliziert: C++ Templates und DLLs


Thomas Wölfer
Thomas Wölfer

01. Mai 2005


C++ Templates können sehr kompliziert werden, sind aber in einfachen Fällen keine große Sache. Allerdings wird es ein wenig kompliziert, wenn man Templates so implementieren will, das die Implementierung in einer DLL vorliegt. Ganz so dramatisch ist das aber auch nicht. Hier ein Beispiel.

Fürs Beispiel muss eine Vektor-Klasse mit einem Template-Parameter herhalten: Der Parameter ist ein Integer und gibt die Größe des Vektors an. Dazu braucht man zunächst ein Headerfile. Das muss jedermann der die DLL bzw. das Template nutzen will zugänglich sein. (Warum überhaupt eine DLL? - Zum Beispiel darum, weil man den nativen Template-Code von managed Code aus verwenden will.....)

Das Headerfile wird sowohl von Clients der Template-Klasse als auch bei der Implementierung verwendet. Das bedeutet, im einen Fall muss der Template-Code aus der DLL exportiert werden (bei der Implementierung), im anderen Fall muss er importiert werden (bei der Verwendung). Dazu legt man sich am besten ein Macro an, das die passenden Statements enthält:

#ifdef _TEMPLATE_EXPORT
   #define _TEMPLATE_API __declspec( dllexport)
#else
   #define _TEMPLATE_API __declspec( dllimport)
#endif

Im Projekt das das Template implementiert definiert man nun _TEMPLATE_EXPORT, beim benutzen nicht. Das führt dazu, das bei der Implementierung __declsepec( dllexport) und bei der Benutzung __declspec( dllimport) definiert ist. Will man ausserdem noch, das die passenden Importlibrary vom Linker automatisch gesucht wird, hinterlässt man einen entsprechenden Library-Search Record:

#ifdef _TEMPLATE_EXPORT
   #define _TEMPLATE_EXPORT_API __declspec( dllexport)
#else
   #define _TEMPLATE_EXPORT_API __declspec( dllimport)
   #pragma comment( lib, "NameDerImportLibrary.lib")
#endif

Die (vereinfachte) Variante des Templates selbst sieht dann so aus:

template< int DIMENSINON> class _TEMPLATE_API Vector
{
public:
   Vector( DIMENSION d);
   // .... und natuerlich die gewuenschte Funktionalitaet
};

Bei der Implementierung im CPP File lässt man das _TEMPLATE_API Macro weg:

template<int DIMENSION>
Vector<DIMENSION>::Vector()
{
   /// und funktionalitaet hier
};

Soweit - sogut. Das Projekt das das Template implementiert lässt sich damit prima übersetzen und linken. Das Projekt das die Template-DLL benutzt kann man hingegen nur übersetzen - der Linker bemängelt nicht auflösbare externe Symbole (unresolved external). Grund: Verwendet man im Template-Client zum Beispiel einen Vector<6>, dann muss ja nun irgendwer der Code fürs Template generieren. Zuständig dafür ist der Compiler - doch der kann die spezialisierung des Templates natürlich nur dann anlegen, wenn er auch den Code für die zugehörigen Funktionen kennt.

Der befindet sich aber in der DLL die das Template implementiert. Resultat: Der Compiler sieht diesen Code zum Zeitpunkt des Übersetzen des Clients nicht und kann dementsprechend auch die Spezialisierung nicht erzeugen. Es gibt als 'unresolved externals'.

Das löst man durch eine explizite Instanzierung des Spezialisierung. Mit anderen Worten: Man teilt dem Compiler schon beim Übersetzen des Codes für die DLL mit, welches Spezialisierungen erzeugt werden sollen. (Das läuft zwar dem Sinn von Templates teilweise entgegen, ist aber die einzige Lösung, wenn man den Code fürs Template in einer DLL haben will.)

Das geht mit einem einfachen Statement unter dem Code zum Template im C++ File:

template class _TEMPLATE_API Vector<6>;

Hier ist eines wichtig: Zumindest beim C++ Compiler aus VS 2003 muss man tatsächlich die abgebildete Notation verwenden. Die m.a.n. eigentlich richtige (und auch dokumentierte) mit ' template<> class _TEMPLATE_API Vector<6>;' funktioniert in diesem Fall aus mir unbekannten Gründen nicht.