DLL et destructeur de classes

DLL et destructeur de classes - C++ - Programmation

Marsh Posté le 26-01-2005 à 15:50:25    

Bonjour à tous,
 
voici mon problème (je suis sous BCB6 et la DLL est liée statiquement)
 
j'exporte la classe simplifiée suivante:
 

Code :
  1. //---------------------------------------------------------------------------
  2. #ifdef __DLL__
  3. #define IMPORT_EXPORT __declspec(dllexport)
  4. #else
  5. #define IMPORT_EXPORT __declspec(dllimport)
  6. #endif
  7. //---------------------------------------------------------------------------
  8. class IMPORT_EXPORT TVideoAffich
  9. {
  10. private:
  11.         boost::scoped_ptr<DS_Affich_Video> Affich_Video;
  12. public:
  13.         __fastcall DS_Affich_Video(HWND handle_fenetre, const WideString & path);     
  14.         __fastcall ~TVideoAffich();
  15. };


 
Voici maintenant la classe simplifiée DS_Affich_Video:
 

Code :
  1. class DS_Affich_Video : public DS_Graph
  2. {
  3. public:
  4. __fastcall DS_Affich_Video(HWND handle_fenetre, const WideString & path);
  5. __fastcall ~DS_Affich_Video()
  6. };


 
Et les classes dont dérive DS_Affich_Video:
 

Code :
  1. class DS_Base
  2. {
  3. public:
  4.         __fastcall DS_Base();
  5.         __fastcall ~DS_Base();
  6. };
  7. //---------------------------------------------------------------------------
  8. class DS_Graph : public DS_Base
  9. {
  10. public:
  11.         __fastcall DS_Graph();
  12.         __fastcall ~DS_Graph();
  13. };


 
Ma DLL se compile parfaitement bien.
 
Le problème vient du linker, qui me met:
 

[Lieur Erreur] Unresolved external '__fastcall DS_Affich_Video::~DS_Affich_Video()' referenced from D:\BORLAND\CBUILDER6\PROJECTS\TESTS PAQUETS\UNIT1.OBJ
 
[Lieur Erreur] Unresolved external '__fastcall TVideoAffich::~TVideoAffich()' referenced from D:\BORLAND\CBUILDER6\PROJECTS\TESTS PAQUETS\UNIT1.OBJ
 
[Lieur Erreur] Unresolved external '__fastcall DS_Base::~DS_Base()' referenced from D:\BORLAND\CBUILDER6\PROJECTS\TESTS PAQUETS\UNIT1.OBJ
 
[Lieur Erreur] Unresolved external '__fastcall DS_Graph::~DS_Graph()' referenced from D:\BORLAND\CBUILDER6\PROJECTS\TESTS PAQUETS\UNIT1.OBJ


 
Donc qu'il ne trouve pas les destructeurs des classes. Les destructeurs sont pourtant bien implémentés dans les .CPP et les CPP sont bien ajoutés dans le projet de la DLL.
 
Solutions:
1) Par contre si je mets les destructeurs directement dans le .H et que je recompile, tout se passe bien...
2) Egalement si je mets IMPORT_EXPORT pour les classes DS_Affich_Video, DS_Base et DS_Graph, ça compile correctement...
 
Je voudrais que l'on ne puisse importer que la première classe depuis la DLL. Je dois quand même utiliser la solution 2?
 
Merci d'avance!

Reply

Marsh Posté le 26-01-2005 à 15:50:25   

Reply

Marsh Posté le 26-01-2005 à 15:57:05    

peut etre qu'avec des destructeurs virtuels ???

Reply

Marsh Posté le 26-01-2005 à 17:01:50    

non, j'avais essayé, même en mettant les destructeurs de DS_Graph et DS_Base virtuels purs, ça donne rien.
 
Mais quand on utilise des classes pour la création d'une DLL, est-ce que toutes les classes doivent être préfixées IMPORT_EXPORT, ou bien seulement la classe que l'on veut utiliser?

Reply

Marsh Posté le 26-01-2005 à 17:21:05    

Je suppose que c'est pour une utilisation de type factory (tu renvoie un TVideoAffich dont l'implémentation est cachée par la dll et peut varier selon X ou Y).
Ton problème c'est ta référence à DS_Affich_Video, qui est un détail interne.
Le plus propre c'est sûrement de le virer, un truc du genre:

Code :
  1. // interface exportée (classe abtraite)
  2. class IMPORT_EXPORT TVideoAffich
  3. {
  4. public:
  5.     static TVideoAffich * New();     
  6.     virtual __fastcall ~TVideoAffich() = 0; // rendre abstraite
  7. };
  8. // partie interne à la dll
  9. class TVideoAffichImpl : public TVideoAffich
  10. {
  11. private:
  12.         boost::scoped_ptr<DS_Affich_Video> Affich_Video;
  13. };
  14. TVideoAffich * TVideoAffich::New()
  15. {
  16.     return new TVideoAffichImpl;
  17. }


Mais tu peux faire plus simple:

Code :
  1. class IMPORT_EXPORT TVideoAffich
  2. {
  3. private:
  4.         // forward déclaration de DS_Affich_Video
  5.         boost::scoped_ptr<class DS_Affich_Video> Affich_Video;
  6. public:
  7.         __fastcall DS_Affich_Video(HWND handle_fenetre, const WideString & path);     
  8.         __fastcall ~TVideoAffich();
  9. };


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 26-01-2005 à 17:33:40    

HelloWorld a écrit :

Je suppose que c'est pour une utilisation de type factory (tu renvoie un TVideoAffich dont l'implémentation est cachée par la dll et peut varier selon X ou Y).
Ton problème c'est ta référence à DS_Affich_Video, qui est un détail interne.
Le plus propre c'est sûrement de le virer, un truc du genre:

Code :
  1. // interface exportée (classe abtraite)
  2. class IMPORT_EXPORT TVideoAffich
  3. {
  4. public:
  5.     static TVideoAffich * New();     
  6.     virtual __fastcall ~TVideoAffich() = 0; // rendre abstraite
  7. };
  8. // partie interne à la dll
  9. class TVideoAffichImpl : public TVideoAffich
  10. {
  11. private:
  12.         boost::scoped_ptr<DS_Affich_Video> Affich_Video;
  13. };
  14. TVideoAffich * TVideoAffich::New()
  15. {
  16.     return new TVideoAffichImpl;
  17. }



 
C'est un peu à la sauce COM ça non?
 

HelloWorld a écrit :

Mais tu peux faire plus simple:

Code :
  1. class IMPORT_EXPORT TVideoAffich
  2. {
  3. private:
  4.         // forward déclaration de DS_Affich_Video
  5.         boost::scoped_ptr<class DS_Affich_Video> Affich_Video;
  6. public:
  7.         __fastcall DS_Affich_Video(HWND handle_fenetre, const WideString & path);     
  8.         __fastcall ~TVideoAffich();
  9. };



 
Pourquoi ajouter "class" ici: boost::scoped_ptr<class DS_Affich_Video> Affich_Video;
 
Et peux-tu confirmer mon idée, selon laquelle uniquement les classes à exporter dans une DLL doivent avoir IMPORT_EXPORT, les autres étant des détails de l'implémentation?

Reply

Marsh Posté le 26-01-2005 à 18:18:22    

Citation :

C'est un peu à la sauce COM ça non?


C'est peut être la notion d'interface qui te fait dire ça. C'est surtout le principe de la factory. Ici comme TVideoAffich est dérivée, son destructeur doit être virtuel. Et comme on veut forcer l'utilisation de la factory via New(), on la rend abstraite en rendant le destruteur virtuel pur.
 

Citation :

Pourquoi ajouter "class" ici: boost::scoped_ptr<class DS_Affich_Video> Affich_Video;  


ça revient à faire ça:

Code :
  1. // déclaration anticipée
  2. class DS_Affich_Video;
  3. class IMPORT_EXPORT TVideoAffich 
  4. private
  5.         // forward déclaration de DS_Affich_Video  
  6.         boost::scoped_ptr<DS_Affich_Video> Affich_Video; 
  7. public
  8.         __fastcall DS_Affich_Video(HWND handle_fenetre, const WideString & path);       
  9.         __fastcall ~TVideoAffich(); 
  10. };


sauf que là c'est fait direct dans le template.
La déclaration anticipée permet d'utiliser le type déclarer comme pointeur/référence, en gros:

Code :
  1. // déclaration anticipée
  2. class DS_Affich_Video;
  3. class IMPORT_EXPORT TVideoAffich 
  4. {
  5. public:
  6.     // ...  
  7. private
  8.     DS_Affich_Video * Affich_Video; // ça marche
  9. };


comme y'a eu forward declaration de DS_Affich_Video plus haut ça compile. Y'a plus qu'à include "DS_Affich_Video.h" dans le .cpp et c'est réglé. C'est une des techniques possibles pour accélérer la compilation en allégeant la liste des .h inclus (principe du pimpl : pprivate implementation). C'est aussi utile dans ton cas par exemple pour cacher des détails.
 

Citation :

Et peux-tu confirmer mon idée, selon laquelle uniquement les classes à exporter dans une DLL doivent avoir IMPORT_EXPORT, les autres étant des détails de l'implémentation?


Moui... c'est plutot dans l'autre sens : ce que tu veux exporter doit être marqué dllexport. Le problème c'est que là tu veux exporter la classe A qui repose sur la classe B, il te faut donc aussi exporter B sinon ça coince. Solution : faire que A ne repose plus sur B, ou exporter B aussi.


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 27-01-2005 à 03:59:23    

Bon finalement j'ai choisi de créer des bibliothèques statiques plutôt que des DLL.
 
Néanmoins j'ai quelques questions:
 
soit A.lib, dans laquelle on a inclus file1.cpp
soit B.lib, dans laquelle on a inclus file2.cpp, et qui nécessite A.lib
 
soit final1.exe, qui nécessite A.lib
soit final2.exe, qui nécessite A.lib et B.lib
 
Pour final2.exe, je dois inclure au projet A.lib et B.lib ou bien uniquement B.lib (et à ce moment A.lib est ajouté au projet de B.lib)
 
J'espère que c'est clair :)
 
Deuxième question:
 
imaginons un fichier EXE pour lequel on doit inclure une dizaine de bibliothèques statiques. Est-ce obligatoire de renseigner le projet sur tous les chemins des .H utilisés par les bibliothèques?
 
Ex: A.lib a des types dont les déclarations se trouvent dans C:\types1\ et dans C:\Types2\.
 
final.exe, qui utilise A.lib, doit il lui aussi se coltiner les chemins des deux dossiers? Ou bien tout est-il inclus dans la bibliothèque, et je n'ai qu'à ajouter le .lib au projet?
 
Est-ce là la différence avec les DLL qui ne nécessitent pas toutes ces précisions (du moins en utilisation dynamique)?

Reply

Marsh Posté le 27-01-2005 à 10:05:27    

D'après mon expérience, faut surtout pas ajouter de .lib à un .lib car si par malheur dans ton exe tu dois utiliser les 2 ou si une 3° lib utilise l'un des 2 tu as des symboles dupliqués. Donc tu files A.lib et B.lib a final.exe.
Une solution avec Visual C++ c'est d'avoir tes 3 projets (les 2 libs et l'exe) et simplement dans l'explorateur de solution, dans la gestion des dépendances de dire que l'exe dépend des 2 autres. Ca t'ajoutes les .lib au linkage, et ca recompile les .lib et relink l'exe si il le faut (modif d'une lib).
 
Normalement on ne spécifie pas les chemins dans le code source. Ca se règle aussi dans l'include path de ton compilateur. Tjrs sous VC++, typiquement, dans les propriétés de ton projet exe, tu va ajouter à "C/C++->Autres répertoires inclus" les chemin absolu ou relatif à tes 2 libs. Si tu as bien organisé ta solution, normalement ajouter "../A;../B" ça suffit (ou "../types1;../types2" dans ton cas).
C'est pas propre aux lib statiques, c'est le principe général de la compilation (include path, lib path).


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 27-01-2005 à 18:34:53    

HelloWorld a écrit :

Normalement on ne spécifie pas les chemins dans le code source. Ca se règle aussi dans l'include path de ton compilateur. Tjrs sous VC++, typiquement, dans les propriétés de ton projet exe, tu va ajouter à "C/C++->Autres répertoires inclus" les chemin absolu ou relatif à tes 2 libs. Si tu as bien organisé ta solution, normalement ajouter "../A;../B" ça suffit (ou "../types1;../types2" dans ton cas).
C'est pas propre aux lib statiques, c'est le principe général de la compilation (include path, lib path).


 
Le problème est que certaines de mes lib utilisent les même fichiers qui se trouvent dans un autre répertoire.
Ex: A.lib utilise toto.h dans ../divers, B.lib aussi mais en plus utilise foo.h dans ../autres
 
Donc pour l'EXE je dois spécifier à chaque fois tous les répertoires et c'est un peu gonflant...

Reply

Marsh Posté le 27-01-2005 à 18:41:44    

A toi de voir ce qui est le + gonflant entre mettre le path 1 fois dans les settings projet ou a chaque fois dans les #include.


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 27-01-2005 à 18:41:44   

Reply

Marsh Posté le 01-02-2005 à 19:05:11    

Encore une question:
 
comment instancier une classe quand la DLL est chargée dynamiquement? Pour une fonction, c'est pas dur c'est avec GetProcAddress.
 
Est-ce que je dois créer une fonction retournant une instance de la classe?
Et une autre se chargeant de la détruire, car je pense que création et destruction doivent se faire sur le même lieu d'appel (ici dans la DLL)?

Reply

Marsh Posté le 01-02-2005 à 19:33:51    

J'ai fait ceci, qui a l'air de fonctionner, mais qui a l'air de provoquer une fuite mémoire (sur base des classes que j'ai mentionné plus haut):
 
DLL
 

Code :
  1. int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
  2. {
  3.         return 1;
  4. }
  5. namespace
  6. {
  7. TVideoAffich * Video;
  8. }
  9. //---------------------------------------------------------------------------
  10. extern "C" __declspec(dllexport) TVideoAffich * __stdcall Create_Video_Affich(TComponent* Owner, HWND hwnd)
  11. {
  12. Video = TVideoAffich::Create_From_Null(Owner,hwnd);
  13. return Video;
  14. }
  15. //---------------------------------------------------------------------------
  16. extern "C" __declspec(dllexport) void __stdcall Delete_Video_Affich()
  17. {
  18. delete Video;
  19. Video = NULL;
  20. }


 
Utilisation

Code :
  1. namespace
  2. {
  3. typedef TVideoAffich* (__stdcall *MYDLLFUNC) (TComponent* Owner, HWND hwnd);
  4. typedef void* (__stdcall *MYDLLFUNC2) ();
  5. MYDLLFUNC ImpFuncDLL;
  6. MYDLLFUNC2 ImpFuncDLL2;
  7. }
  8. //---------------------------------------------------------------------------
  9. __fastcall TForm1::TForm1(TComponent* Owner)
  10. : TForm(Owner)
  11. {
  12. CoInitialize(NULL);
  13. hinstDLL = LoadLibrary("Video_Affichage.dll" );
  14. }
  15. //---------------------------------------------------------------------------
  16. void __fastcall TForm1::Button1Click(TObject *Sender)
  17. {
  18. ImpFuncDLL = (MYDLLFUNC)GetProcAddress(hinstDLL, "Create_Video_Affich" );
  19. if (ImpFuncDLL)
  20. {
  21.         TVideoAffich * Video = ImpFuncDLL(this,Application->Handle);
  22.         Video->Show();
  23. }
  24. }
  25. //---------------------------------------------------------------------------
  26. void __fastcall TForm1::FormDestroy(TObject *Sender)
  27. {
  28. ImpFuncDLL2 = (MYDLLFUNC2)GetProcAddress(hinstDLL, "Delete_Video_Affich" );
  29. if (ImpFuncDLL2)
  30.         ImpFuncDLL2();
  31. FreeLibrary(hinstDLL);
  32. CoUninitialize();
  33. }
  34. //---------------------------------------------------------------------------


 
Je suis obligé de faire comme ça?
Est-ce que je suis obligé de détruire l'instance de TVideoAffich dans la DLL?
N'existe-il pas une librairie permettant de simplifier tout ça?
 
Merci d'avance

Reply

Marsh Posté le 02-02-2005 à 01:21:25    

Il faut que tes dll partagent la même CRT (lib standard) sinon va y avoir probleme (chaque dll aura sa propre gestion des new/delete). A part ça new/delete sur une classe importée statiquement ça devrait marcher.
Après pour simplifier, je sais pas si c'est le bon terme, mais on va vite songer à COM, qui est une usine à gaz spécialisée dans la communication entre composants logiciels objets. Mais bon ça se mérite : c'est assez coton.


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 02-02-2005 à 02:23:37    

Mon problème, et je pense que tu l'as bien compris, est que mes Dlls ne servent juste qu'à "stocker" mes classes communes à mes différents projets. Là je tentais de les utiliser dynamiquement à cause d'autres problèmes dus à BCB6.
 
Ce que je veux juste, c'est pouvoir utiliser mes classes comme si je les avais bien incluses dans mon projet, et c'est tout. C'est pour cela que je pense revenir vers des .lib tout simple.
 
Sinon concernant COM, je l'utilise déjà je pense à travers DirectShow, et les librairies ATL. Mais je ne sais qu'utiliser les classes déjà présentes à coup de QueryInterface et compagnie.
 
Je ne sais pas du tout comment créer un client COM (parce que c'est bien de ça dont il s'agit non?)
 
Aurais-tu des liens là dessus en cette fin de soirée?
 
Merci en tout cas pour tes réponses.

Reply

Marsh Posté le 02-02-2005 à 10:38:38    

Créer un .lib ou une dll, si c'est linké en static, pas de problème à priori, sauf pour la dll ou il faut veiller à partager la lib standard (option de compilation). Mais ça marche très bien ensuite, y'a plein de dll qui font ça. Au hasard Qt : des dizaines et des dizaines de classes toutes importées (statiquement) depuis une dll. Idem les MFC, et plein d'autre exemples...
Pour COM, c'est plutot un serveur que tu doit créer. Utiliser un composant COM ça peut être plus ou moins facile selon le langage. Créer un composant c'est forcément plus dur, et j'ai pas d'expérience dans ce domaine (je me suis jamais plongé dans ATL). Si tu cherches juste à découper ton programme mettre dans une dll ou lib statique c'est très bien. COM devient intéressant quand cette dll doit être utilisée depuis plusieurs applis / langages, ou dès que y'a un binding dynamique de classe (plugin, ...).
Une FAQ intéressante sur COM:
http://www.developpez.com/windows/dcom/t1.html


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 02-02-2005 à 12:56:51    

Effectivement partager des classes comme ça, c'est vraiment pas mal, notamment pour les mises à jour.
 
Mais j'ai une erreur qui me chiffonne quand j'utilise les DLL, c'est le gestionnaire mémoire. En effet j'ai quelques classes singleton qui sont utilisées dans les différentes DLL et dans mon programme (une classe d'options, une classe Language pour les versions traduites du soft...) et chaque classe qui l'utilise récupère l'instance du singleton. Le problème est que CodeGuard (une sorte de vérificateur de fuites mémoire) m'indique une fuite pour chaque instance d'un singleton dans chaque DLL qui l'a utilisé... Alors je suis obligé de détruire le singleton dans chaque sortie de DLL, ce qui ne convient pas du tout.
 
Donc pour l'instant je suis obligé de passer par des libs...

Reply

Marsh Posté le 02-02-2005 à 22:29:47    

Si tu link en static la dll t'as pas à gérer le DllMain normalement.
Tu l'as fait comment ton singleton ? Typiquement:

Code :
  1. Singleton * Singleton::GetInstance()
  2. {
  3.     static Singleton instance;
  4.     return &instance;
  5. }


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 03-02-2005 à 04:20:00    

Code :
  1. template <class T>
  2. class Singleton
  3. {
  4. public :
  5.         static T* getInstance()
  6.         {
  7.         if (!instance) instance = new T;
  8.         return instance;
  9.         }
  10.         static void Release()
  11.         {
  12.         if (instance) delete instance;
  13.         instance = NULL;
  14.         }
  15. protected :
  16.         Singleton() {}
  17.         ~Singleton() {}
  18. private :
  19.         static T* instance;
  20.         Singleton(Singleton& );
  21.         void operator =(Singleton& );
  22. };
  23. template <class T> T* Singleton<T>::instance = NULL;


 
ET je l'utilise comme suit:
 

Code :
  1. class Gestion_BDD : public Singleton<Gestion_BDD>
  2. {
  3. private:
  4.         friend Singleton<Gestion_BDD>;
  5.         __fastcall Gestion_BDD();
  6.         __fastcall ~Gestion_BDD();
  7. public:
  8. };


 
Je confirme le problème... Là où avant je faisais ClasseX::Release(); à la fin de l'exécution de l'EXE (normal quoi) je dois le faire pour chaque destructeur des classes appellées dans mes DLL.
 
Là j'ai recompilé mes DLL en librairies statiques, et plus aucun souci...

Reply

Marsh Posté le 03-02-2005 à 09:55:44    

Ah ben oui si ton Singleton doit être libéré c'est pas top. Pourquoi tu utilises pas un objet statique automatique (c.f mon exemple) au lieu d'un objet alloué dynamiquement ?
Avec une lib statique ton Release sera jamais appelé. Et à quoi setr le

Code :
  1. friend Singleton<Gestion_BDD>;

?
Et pourquoi tu pourris ton code de __fastcall ?


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 04-02-2005 à 00:15:23    

HelloWorld a écrit :

Ah ben oui si ton Singleton doit être libéré c'est pas top. Pourquoi tu utilises pas un objet statique automatique (c.f mon exemple) au lieu d'un objet alloué dynamiquement ?
Avec une lib statique ton Release sera jamais appelé. Et à quoi setr le

Code :
  1. friend Singleton<Gestion_BDD>;

?
Et pourquoi tu pourris ton code de __fastcall ?


 
Cet exemple?
 

Code :
  1. Singleton * Singleton::GetInstance()
  2. {
  3. static Singleton instance;
  4. return &instance;
  5. }


 
Je ne sais pas, j'ai toujours utilisé ce code... Je n'ai jamais pensé à le changer...
 

Code :
  1. friend Singleton<Gestion_BDD>;

c'est parce que Gestion_BDD dérive d'un template, donc il faut une instanciation, qui est protected dans le template, d'où le friend pour que Gestion_BDD puisse y accéder. C'est mieux expliqué ici http://loulou.developpez.com/tutor [...] tie1/#L3.4
C'est là que j'ai choppé l'exemple...
 
Et enfin les __fastcall, c'est pour BCB6. Voici ce que dit la doc:

Description
 
Le modificateur __fastcall permet de déclarer des fonctions attendant que des paramètres soient transmis dans les registres. Les trois premiers paramètres sont transmis (depuis la gauche vers la droite) dans EAX, EDX et dans ECX, s'ils rentrent dans le registre. Les registres ne sont pas utilisés si le paramètre est de type virgule flottante ou structure.


 
Et ils conseillent de l'utiliser partout...
 
Voilà :)

Reply

Marsh Posté le 04-02-2005 à 00:29:12    

__fastcall c'est un bon moyen de rendre le code non portable (tout ce qui commence par un ou deux underscore est à éviter). En général au lieu de faire ça partout tu as une option de ton compilateur pour changer la convention par défaut.
Pour le friend oui j'ai compris après coup. C'est un implémentation possible du singleton, moi j'aime moyen mais bon ça marche. Pour un vrai singleton avec une seule instance, je préfère l'objet statique, t'as pas à gérer sa création / désallocation.


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 04-02-2005 à 11:47:36    

A quoi ressemble la classe complète alors?

Reply

Marsh Posté le 04-02-2005 à 23:34:42    

Reply

Marsh Posté le 05-02-2005 à 00:22:56    

J'vais jeter un oeil, merci

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed