Archive for 21 octobre 2011

Utilisation de GeographicLib sous Visual Studio C++

21 octobre 2011

Je vous livre ici la solution à un problème qui m’a donné pas mal de fil à retordre : l’utilisation de la librairie GeographicLib (une bibliothèque de calcul de coordonnés géographique, notamment du « direct geodesic problem« , qui consiste à calculer une positon sur la Terre en connaissant une positon d’origine, une distance et un azimut(cap). Cette bibliothèque est également capable de faire des conversions entre les différents systèmes géodesiques existants). Je précise que la version utilisées ici est la 1.14

Mes problèmes furent multiples :

  1. Faire fonctionner la bibliothèque sous Visual Studio (2008 pour mon cas, mais les soucis auraient probablement étés les mêmes sous VS2005 ou VS2010)
  2. Comprendre comment elle fonctionne
  3. Réussir à compiler, en l’occurrence, j’avais des erreurs sur l »édition des liens (linker).

1) Installer et faire fonctionner la librairie sous VC2008

Pas de grosses difficultés ici, il faut juste ne pas se faire piéger par la doc qui présente (à ce jour) une erreur. Donc :

  • dans les paramètres du projet (Projet -> Propriété de…),
  • se rendre dans Propriété de configuration ->C/C++->Général->Autres répertoire include et renseigner : C:Program FilesGeographicLib-1.14include.
  • Dans Éditeur de liens->Général-> Répertoires de bibliothèques supplémentaires, renseigner C:Program FilesGeographicLib-1.14lib et non C:Program FilesGeographicLib-1.14include comme indiqué dans la documentation
  • Dans Éditeur de liens->Entrée->Dépendances supplémentaires, ajouter Geographic.lib
Je vais tâcher de les contacter pour leur indiquer cette erreur.

2) Comprendre comment elle fonctionne

Ici, j’ai eu des difficultés, car c’est l’une des premières fois que j’utilise une bibliothèque tierce (hors Qt, superbe doc,  et SDK matériels, qui ont souvent eut aussi une doc assez explicite avec des exemples), et assez peu utilisée. J’ai donc eu un mal terrible à trouver des exemples sur Internet. Lors de mes premiers essais, j’ai ainsi codé ainsi :

double lat1 = 46.908848;
double lon1 = 6.327472;
double azi1 = 59.16;
double s12 = 6216.28;
double lat2;
double lon2;
Geodesic::Direct(lat1, lon1, azi1, s12, &lat2, &lon2);

Ce qui générait à la compilation des erreurs du genre :

.main.cpp(295) : error C2665: 'GeographicLib::Geodesic::Direct' : aucune des 6 surcharges n'a pu convertir tous les types d'arguments
.GeographicLib/Geodesic.hpp(350): peut être 'GeographicLib::Math::real GeographicLib::Geodesic::Direct(GeographicLib::Geodesic::real,GeographicLib::Geodesic::real,GeographicLib::Geodesic::real,GeographicLib::Geodesic::real,GeographicLib::Geodesic::real &,GeographicLib::Geodesic::real &) throw() const'

Je vous livre donc la syntaxe correcte qui est :

double lat1 = 46.908848;
double lon1 = 6.327472;
double azi1 = 59.16;
double s12 = 6216.28;
double lat2;
double lon2;
const Geodesic& g = Geodesic::WGS84;
g.Direct(lat1, lon1, azi1, s12, lat2, lon2);

3) Parvenir à compiler/linker

A la compilation, enfin, à l’édition des liens pour être précis, j’obtenais les erreurs suivantes :

1>Édition des liens en cours...
1>Geographic.lib(Geodesic.obj) : warning LNK4229: directive '/FAILIFMISMATCH:_MSC_VER=1600' non valide rencontrée ; ignorée
1>Geographic.lib(Geodesic.obj) : warning LNK4229: directive '/FAILIFMISMATCH:_ITERATOR_DEBUG_LEVEL=0' non valide rencontrée ; ignorée
1>Geographic.lib(GeodesicLine.obj) : warning LNK4229: directive '/FAILIFMISMATCH:_MSC_VER=1600' non valide rencontrée ; ignorée
1>Geographic.lib(GeodesicLine.obj) : warning LNK4229: directive '/FAILIFMISMATCH:_ITERATOR_DEBUG_LEVEL=0' non valide rencontrée ; ignorée
1>msvcprt.lib(MSVCP90.dll) : error LNK2005: "public: __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::~basic_string<char,struct std::char_traits<char>,class std::allocator<char> >(void)" (??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ) déjà défini(e) dans Geographic.lib(Geodesic.obj)
1>msvcprt.lib(MSVCP90.dll) : error LNK2005: "public: char const * __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::c_str(void)const " (?c_str@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QBEPBDXZ) déjà défini(e) dans Geographic.lib(Geodesic.obj)
1>msvcprt.lib(MSVCP90.dll) : error LNK2005: "public: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > & __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::erase(unsigned int,unsigned int)" (?erase@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAEAAV12@II@Z) déjà défini(e) dans Geographic.lib(Geodesic.obj)
1>msvcprt.lib(MSVCP90.dll) : error LNK2005: "public: __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >(char const *)" (??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@PBD@Z) déjà défini(e) dans Geographic.lib(Geodesic.obj)
1>msvcprt.lib(MSVCP90.dll) : error LNK2005: "public: static unsigned int __cdecl std::char_traits<char>::length(char const *)" (?length@?$char_traits@D@std@@SAIPBD@Z) déjà défini(e) dans Geographic.lib(Geodesic.obj)
1>Geographic.lib(Geodesic.obj) : error LNK2019: symbole externe non résolu "__declspec(dllimport) void __cdecl std::_Xlength_error(char const *)" (__imp_?_Xlength_error@std@@YAXPBD@Z) référencé dans la fonction "public: void __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::_Xlen(void)const " (?_Xlen@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QBEXXZ)
1>Geographic.lib(Geodesic.obj) : error LNK2019: symbole externe non résolu "__declspec(dllimport) void __cdecl std::_Xout_of_range(char const *)" (__imp_?_Xout_of_range@std@@YAXPBD@Z) référencé dans la fonction "public: void __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::_Xran(void)const " (?_Xran@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QBEXXZ)

Après pas mal de lecture sur le net et avoir bidouillé a peu près tous les paramètres de l’éditeur de liens de Visual Studio, je me suis résolu à recompiler moi même la librairie. Pour cela, il faut télécharger le zip du code source de GeographicLib (ici par exemple). Les développeurs ont même eu l’idée de fournir des fichiers projets pour VS2005 (GeographicLib-vc8.sln), 2008 (GeographicLib-vc9.sln) et 2010 (GeographicLib-vc10.sln) (répertoire windows de l’archive). J’ouvre donc le fichier qui correspond à ma version de Visual Studio et j’ai compilé la bibliothèque en mode Release. Il en ressort un beau fichier .lib d’environ 1800 ko (contre 1300 ko pour celui fourni). Après remplacement du fichier Geographic.lib fourni par ce nouveau fichier, la compilation et l’édition des liens se déroule à merveille, et la bibliothèque semble fonctionner.

Dans mon immense bonté, je vous fourni cette librairie compilée avec Visual Studio C++ 2008 (je ne sais pas si elle fonctionne avec VC2005 ou VC2010) : Geographic.lib.

DependencyWalker, explorer les dépendances d’une application

17 octobre 2011

Bien souvent, lorsque l’on développer une application, tout se passe bien jusqu’au moment ou l’on souhaite la faire fonctionner sur une autre machine. Bien souvent, lors de cette étape, on fait le douloureux constat que l’application ne démarre même bien, en général, on obtient juste un message indiquant qu’il y a un problème et que l’application ne peut démarrer, avec pour seule suggestion de remède une réinstallation, ce qui bien sur est inutile.

Dans la plupart des cas, on est face à un problème de dépendances avec des bibliothèques présentes sur la machine de développement (installées au bon endroit lors de l’installation des outils de développement), et absentes des systèmes « classiques ».

Il est parfois aisé de savoir quelles sont les librairies utilisées, en fonction des SDK et autres Framwork utilisés, mais parfois, ça n’est pas si simple.

Dans ces cas, il existe des logiciels capables d’analyser un fichier binaire pour en retirer les dépendances. Dependency Walker fait parti de ceux-ci.

Fenêtre principale de Dependency Walker

Fenêtre principale de Dependency Walker

La simple ouverture d’un exécutable dans ce logiciel vous permettra de voir instantanément de quelles DLL à besoin le logiciel pour fonctionner. Une simple recherche sur la machine de dev devrait vous permettre de mettre la main sur la DLL manquante afin de la joindre à votre programme pour que tout rentre dans l’ordre.

Un certain travail de tri et de sélection des informations restera cependant nécessaire car comme le montre la capture ci-dessus, l’intégralité des DLL et dépendances sont affichées, il faudra donc différencier les DLL systèmes (toujours présentes sous Windows et qu’il ne sera donc pas nécessaire d’importer) des DLL applicatives qu’il faudra fournir.

InnoSetup : ajouter les DLL Visual C++

14 octobre 2011

Si vous développez à l’aide de Microsoft Visual Studio, vos programmes nécessitent, pour fonctionner sur une machine quelconque, de diposer des bibliothèques suivantes (DLL de VC++ 2008) :

  • mfc90.dll
  • msvcr90.dll
  • msvcp90.dll
  • atl90.dll
  • vcomp.dll
L’absence des ces librairies est un cas fréquent de non fonctionnement des programmes développés par vos soins, le syptôme est alors l’apparition d’un message du type : « Cette application n’a pas pu démarrer car la configuration de l’application est incorrecte. Réinstaller l’application pourrait résoudre ce problème.« .

 

La solution est d’installer le package de DLL « Microsoft Visual C++ 2008 Redistribuable Package » (téléchargeable sur le site de Microsoft : vcredist_x86.exe ou vcredist_x64.exe pour la version 64 bits).
Mais voila, si vous distribuez vos applications avec un installeur comme Inno Setup, cela impose à vos utilisateurs de télécharger ce package de DLL en sus, avec le risque qu’ils passent à coté et qu’il ne parviennent donc pas à lancer votre application.
La solution la plus simple est donc d’intégrer vcredist dans l’installeur InnoSetup de façon à ce que son installation soit automatique et transparente pour vos utilisateurs. Voici comment procéder :
  1. Téléchargez vcredist_x86.exe sur le site de Microsoft (voir lien plus haut) et enregistrez le dans un sous dossier ./bin/ de votre projet
  2. Ouvrez votre projet Inno Setup (je suppose ici que celui-ci est d’hors et déjà créé et que l’emplacement des autres fichiers de votre projet sont déjà renseignés)
  3. Dans la section [files] de votre projet, ajoutez ceci :
    Source: "{src}binvcredist_x86.exe"; DestDir: "{app}bin"; Flags: deleteafterinstall
  4. Dans la section [Run], ajoutez :
    Filename: "{app}binvcredist_x86.exe"; Parameters: "/q:a /c:""VCREDI~3.EXE /q:a /c:""""msiexec /i vcredist.msi /qn"""" """; WorkingDir: "{app}bin"; StatusMsg: Installing VCREDIST...
Il faudra bien sur adapter ces commandes si vous intégrez la version 64 bits.
Vous devriez également pouvoir inclure les 2 versions (32 et 64 bits) en jouant avec les options et conditions disponibles dans InnoSetup.

 

Grâce à cette technique, l’installation se fera de façon totallement transparente, l’utilisateur n’ayant même pas besoin de valider les différentes étapes de l’installer du package Visual Studio VC++ Redistribuable (il voit juste une barre de progression qui se remplit sans poser de questions).

 

Note : ce billet traite plus particulièrement de la version 2008 de Visual Studio (celle que j’utilise), mais cette astuce est transposable telle qu’elle pour les autres versions de VS (je pense notamment aux versions 2005 et 2010), il suffit de télécharger les pack vcredist correspondants à ces versions sur le site de Microsoft.