VI. La dynamisation▲
Ma première DLL :)
Le gros problème pour le développement côté utilisateur lorsqu'il utilise ce genre de bibliothèque, est qu'en restant statique, ce sera à lui de compiler les classes Template. Nous en possédons un paquet, par conséquent son temps de compilation rendra la production... improductive.
En proposant notre gestion via une DLL, notre code sera déjà compilé dedans, mais cela nous pose deux problèmes :
- Comment ne proposer que des objets "simples", sans Template, dans l'interface de la DLL ?
- Comment compiler les fonctions Template, de manipulation ?
Pour répondre à la première question, nous avons deux solutions. Soit proposer une interface de donnée, d'où dérivera la donnée concrète, produisant ce genre de code :
class
CBookImpl;
class
CAuthor;
class
CBook
{
public
:
typedef
CBookImpl TImpl;
virtual
unsigned
_int64 GetIdent() const
=
0
;
virtual
const
std::
string&
GetTitle() const
=
0
;
virtual
void
SetTitle(const
std::
string&
rstrTitle) =
0
;
virtual
const
std::
string&
GetDescription() const
=
0
;
virtual
void
SetDescription(const
std::
string&
rstrDescription) =
0
;
virtual
unsigned
int
GetYear() const
=
0
;
virtual
void
SetYear(unsigned
int
uiYear) =
0
;
virtual
CAuthor*
GetAuthor() =
0
;
virtual
void
SetAuthor(CAuthor*
pAuthor) =
0
;
}
;
class
CAuthorImpl;
class
CBookImpl : public
CBook, public
CData<
CBookImpl>
{
MAKE_DATA(CBookImpl)
public
:
CBookImpl();
virtual
unsigned
_int64 GetIdent() const
;
virtual
const
std::
string&
GetTitle() const
;
virtual
void
SetTitle(const
std::
string&
rstrTitle);
virtual
const
std::
string&
GetDescription() const
;
virtual
void
SetDescription(const
std::
string&
rstrDescription);
virtual
unsigned
int
GetYear() const
;
virtual
void
SetYear(unsigned
int
uiYear);
virtual
CAuthor*
GetAuthor();
virtual
void
SetAuthor(CAuthor*
pAuthor);
private
:
TUInt64Property m_ulIdent;
TStringProperty m_strTitle;
TStringProperty m_strDescription;
TUInt32Property m_uiYear;
TUInt64Property m_ulIdentAuthor;
CSmartDataPtr<
CBookImpl, CAuthorImpl>
m_pAuthor;
}
;
const
char
*
SDataTrait<
CBookImpl>
::
CODE =
"BOOK"
;
Soit utiliser le pImpl Idiom. Cette technique définit une implémentation (notre CBook devient CBookImpl) et la donnée encapsule cette implémentation (CBook contient un pointeur vers CBookImpl). Dans le fichier proposé à l'utilisateur (CBook.h), une simple pré déclaration de CBookImpl suffit. Ainsi, le temps de compilation du projet utilisateur s'en trouve réduit.
Nous allons utiliser la première technique, plus adaptée.
Pour répondre à la seconde question, la problématique repose sur "Comment compiler les fonctions GesMgr<MaClass>::XXX(...) ? L'utilisateur ne peut pas connaître GesMgr directement, cela lui imposerait de compiler la hiérarchie éparpillée et donc tous les gestionnaires. Donc nous proposerons des fonctions, toujours Template, et compilerons leur implémentations qui appellerons les fonctions de GesMgr. Voici un exemple :
#define KIN_EXPORT __declspec(dllexport)
/*!< Définition de l'exportation. */
#define KIN_IMPORT __declspec(dllimport)
/*!< Définition de l'importation. */
template
<
class
T>
KIN_EXPORT unsigned
long
kinGetCount(); // Fonction exposé dans un fichier d'entête (.h exposé)
template
<>
KIN_EXPORT Class*
kinGetAt<
CBook>
(unsigned
long
ulIndex) {
\ // Implémentation, caché dans un fichier d'implémentation (.cpp) de la DLL
return
CGesManager::
GetInstance().GetAt<
CBook::
TImpl>
(ulIndex); }
Ainsi, l'utilisateur ne manipule qu'une interface, son implémentation est cachée et il n'a pas à la compiler. Puis il appelle GesMgr par une fonction dont seul l'entête est disponible.
Ca paraît fastidieux de devoir déclarer toutes les fonctions qu'offre GesMgr, pour chaque type de donnée. Nous passerons alors par une macro :
// Des entêtes
template
<
class
T>
KIN_EXPORT unsigned
long
kinGetCount();
template
<
class
T>
KIN_EXPORT T*
kinGetAt(unsigned
long
ulIndex);
template
<
class
T>
KIN_EXPORT IProperty*
kinGetProperty(T*
poData, const
std::
string&
rstrPropertyName);
template
<
class
T>
KIN_EXPORT unsigned
int
kinGetPropertyCount();
// Puis leur implémentation
#define KIN_IMPL_CLASS(Class) \
template<> KIN_EXPORT Class* kinGetAt
<Class>
(unsigned long ulIndex) { \
return CGesManager::GetInstance().GetAt<Class::TImpl>(ulIndex); } \
template<> KIN_EXPORT unsigned long kinGetCount
<Class>
() { \
return CGesManager::GetInstance().GetCount<Class::TImpl>(); } \
template<> KIN_EXPORT IProperty* kinGetProperty
<Class>
(Class* poData, const std::string& rstrPropertyName) { \
return CGesManager::GetInstance().GetProperty<Class::TImpl>((Class::TImpl*)poData, rstrPropertyName); } \
template<> KIN_EXPORT unsigned int kinGetPropertyCount
<Class>
() { \
return CGesManager::GetInstance().GetPropertyCount<Class::TImpl>(); }
Il n'y a plus qu'à appeler la macro sur un type de donnée pour compiler les implémentation des fonctions ainsi que leurs dépendances.
// Dans un .cpp de la DLL
KIN_DLL_IMPL_CLASS(CBook)
KIN_DLL_IMPL_CLASS(CAuthor)
Ainsi, pour chaque type, le travail est minimisé, et le programme utilisateur compile rapidement.
Dans le code source disponible, j'ai exposé la plupart des méthodes du GesMgr, les relations, ainsi que des méthodes non Template comme le positionnement du fichier de configuration IO par exemple.