<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>blog.freelan.org &#187; &#187; boost</title>
	<atom:link href="https://blog.freelan.org/tag/boost/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.freelan.org</link>
	<description>De l&#039;informatique, des octets et des poneys.</description>
	<lastBuildDate>Fri, 04 Apr 2014 17:34:59 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.42</generator>
	<item>
		<title>Boost::Python, dates et conversions</title>
		<link>https://blog.freelan.org/2011/08/09/boostpython-dates-et-conversions/</link>
		<comments>https://blog.freelan.org/2011/08/09/boostpython-dates-et-conversions/#comments</comments>
		<pubDate>Tue, 09 Aug 2011 17:54:20 +0000</pubDate>
		<dc:creator><![CDATA[Julien Kauffmann]]></dc:creator>
				<category><![CDATA[Boost]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Développement]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[boost]]></category>
		<category><![CDATA[boost::posix_time]]></category>
		<category><![CDATA[boost::python]]></category>
		<category><![CDATA[c++]]></category>
		<category><![CDATA[conversion]]></category>
		<category><![CDATA[date]]></category>
		<category><![CDATA[datetime]]></category>
		<category><![CDATA[heure]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[time]]></category>

		<guid isPermaLink="false">http://blog.freelan.org/?p=354</guid>
		<description><![CDATA[Ceux qui ont déjà joué avec Boost::Python le savent : cette API est très complète mais malheureusement trop peu documentée. Si elle permet de rendre accessible du code C++ depuis un environnement Python avec parfois une déconcertante simplicité, certaines tâches triviales sont en revanche plus difficiles à réaliser qu'on aurait pu l'imaginer.
Si vous êtes sur cette page, c'est très probablement parce que vous aussi devez jouer avec des dates et des durées, de C++ vers Python, inversement ou bien les deux et que vous vous êtes vous aussi heurté à la complexité de Boost::Python.
L'article qui suit est le résultat de mes recherches en la matière, et la synthèse de la solution à laquelle je suis parvenu.]]></description>
				<content:encoded><![CDATA[<p>Ceux qui ont déjà joué avec Boost::Python le savent : cette API est très complète mais malheureusement trop peu documentée. Si elle permet de rendre accessible du code C++ depuis un environnement Python avec parfois une déconcertante simplicité, certaines tâches triviales sont en revanche plus difficiles à réaliser qu&#8217;on aurait pu l&#8217;imaginer.</p>
<p>Si vous êtes sur cette page, c&#8217;est très probablement parce que vous aussi devez jouer avec des dates et des durées, de C++ vers Python, inversement ou bien les deux et que vous vous êtes vous aussi heurté à la complexité de Boost::Python.</p>
<p>L&#8217;article qui suit est le résultat de mes recherches en la matière, et la synthèse de la solution à laquelle je suis parvenu.</p>
<h1>Le code de base</h1>
<p>Pour l&#8217;ensemble de l&#8217;article, nous partirons du principe que vous utilisez la classe <code>boost::posix_time::ptime</code> côté C++, et <code>datetime.datetime</code> côté Python.</p>
<p><em>Remarque : Si jamais vous utilisiez une autre classe pour vos dates (quelle soit &#8220;maison&#8221; ou issue d&#8217;une autre bibliothèque), vous devriez pouvoir adapter le principe sans trop de problèmes.</em></p>
<p>Le code minimal pour un module Python avec Boost::Python dont nous partirons est le suivant :</p>
<p></p><pre class="crayon-plain-tag">/**
* \file module.cpp
* \author Julien Kauffmann
* \brief The Python module file.
*/

#include &lt;boost/python/module.hpp&gt;

BOOST_PYTHON_MODULE(module)
{
}</pre><p></p>
<p>Rien de fou donc, pour l&#8217;instant.</p>
<p>Ajoutons une fonction qui prend en paramètre et retourne un <code>boost::posix_time::ptime</code> :</p><pre class="crayon-plain-tag">/**
 * \file module.cpp
 * \author Julien Kauffmann
 * \brief The Python module file.
 */

#include &lt;boost/python/module.hpp&gt;
#include &lt;boost/python/def.hpp&gt;

#include &lt;boost/date_time/posix_time/posix_time.hpp&gt;

boost::posix_time::ptime add_five_seconds(boost::posix_time::ptime ptime)
{
  if (!ptime.is_special())
  {
    return ptime + boost::posix_time::seconds(5);
  }

  return ptime;
}

BOOST_PYTHON_MODULE(module)
{
  boost::python::def(&quot;add_five_seconds&quot;, &amp;add_five_seconds, boost::python::args(&quot;ptime&quot;), &quot;Add five seconds to a datetime then return the result&quot;);
}</pre><p>Cette fonction ajoute bêtement 5 secondes à toute date qui lui est passée, sauf si celle-ci n&#8217;est pas une date valide.</p>
<p>Si le code actuel compile et donne une bibliothèque Python valide, l&#8217;appel de <code>add_five_seconds()</code> depuis l&#8217;interpréteur Python provoque la levée d&#8217;une exception :</p><pre class="crayon-plain-tag">Python 2.4.3 (#1, Apr 14 2011, 20:41:59)
Type &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.

In [1]: import datetime, module

In [2]: module.add_five_seconds(datetime.datetime.today())
---------------------------------------------------------------------------
ArgumentError                             Traceback (most recent call last)

/home/ereon/workbench/pythondate/

ArgumentError: Python argument types in
    module.add_five_seconds(datetime.datetime)
did not match C++ signature:
    add_five_seconds(boost::posix_time::ptime ptime)</pre><p>En effet : à aucun moment nous n&#8217;avons indiqué à Boost::Python comment convertir une date Python en date C++, ni même que cette conversion était possible.</p>
<p>Pour que ceci fonctionne, il faut ajouter deux &#8220;converters&#8221; : un de C++ vers Python, et l&#8217;autre de Python vers C++.</p>
<h2>Conversion de C++ vers Python</h2>
<p>Si Boost::Python ne prend pas nativement en charge la conversion de date, il fournit néanmoins des outils puissants pour nous permettre d&#8217;y arriver.</p>
<p>Ajoutons le code de conversion à notre exemple précédent :</p><pre class="crayon-plain-tag">/**
 * \file module.cpp
 * \author Julien Kauffmann &lt;julien.kauffmann@freelan.org&gt;
 * \brief The Python module file.
 */

#include &lt;boost/python/module.hpp&gt;
#include &lt;boost/python/def.hpp&gt;
#include &lt;boost/python/class.hpp&gt;

#include &lt;boost/date_time/posix_time/posix_time.hpp&gt;

#include &lt;datetime.h&gt;

boost::posix_time::ptime add_five_seconds(boost::posix_time::ptime ptime)
{
  if (!ptime.is_special())
  {
    return ptime + boost::posix_time::seconds(5);
  }

  return ptime;
}

struct date_to_python_converter
{
  static PyObject* convert(boost::posix_time::ptime value)
  {
    if (value.is_not_a_date_time())
      return Py_None;

    PyDateTime_IMPORT;

    return PyDateTime_FromDateAndTime(
      static_cast&lt;int&gt;(value.date().year()),
      static_cast&lt;int&gt;(value.date().month()),
      static_cast&lt;int&gt;(value.date().day()),
      static_cast&lt;int&gt;(value.time_of_day().hours()),
      static_cast&lt;int&gt;(value.time_of_day().minutes()),
      static_cast&lt;int&gt;(value.time_of_day().seconds()),
      static_cast&lt;int&gt;(value.time_of_day().total_microseconds() - value.time_of_day().total_seconds() * 1000000L)
      );
  }
};

BOOST_PYTHON_MODULE(module)
{
  boost::python::to_python_converter&lt;boost::posix_time::ptime, date_to_python_converter&gt;();

  boost::python::def(&quot;add_five_seconds&quot;, &amp;add_five_seconds, boost::python::args(&quot;ptime&quot;), &quot;Add five seconds to a datetime then return the result&quot;);
}</pre><p></p>
<h3>Analyse</h3>
<p>Regardons ligne par ligne les changements apportés.</p><pre class="crayon-plain-tag">#include &lt;boost/python/class.hpp&gt;
#include &lt;datetime.h&gt;</pre><p>Le premier include est nécessaire pour utiliser <code>boost::python::to_python_converter</code>; le second pour rendre disponibles les types Python natifs, tels que <code>PyObject</code> ou <code>PyDateTime</code>.</p><pre class="crayon-plain-tag">struct date_to_python_converter
{
  static PyObject* convert(boost::posix_time::ptime value)
  {
    if (value.is_not_a_date_time())
      return Py_None;

    PyDateTime_IMPORT;

    return PyDateTime_FromDateAndTime(
      static_cast&lt;int&gt;(value.date().year()),
      static_cast&lt;int&gt;(value.date().month()),
      static_cast&lt;int&gt;(value.date().day()),
      static_cast&lt;int&gt;(value.time_of_day().hours()),
      static_cast&lt;int&gt;(value.time_of_day().minutes()),
      static_cast&lt;int&gt;(value.time_of_day().seconds()),
      static_cast&lt;int&gt;(value.time_of_day().total_microseconds() - value.time_of_day().total_seconds() * 1000000L)
      );
  }
};</pre><p>Ici nous déclarons un &#8220;converter&#8221;. Au sens de Boost::Python, un &#8220;converter&#8221; est une simple structure ou classe qui contient une méthode statique nommée <code>convert()</code> qui prend un paramètre le type natif C++ à convertir, et qui retourne un <code>PyObject*</code>.</p>
<p>Dans le cas où notre date n&#8217;est pas une date valide, nous choisissons ici de renvoyer <code>None</code>. Libre à vous de modifier ce comportement pour satisfaire vos propres besoins.</p>
<p>Remarque : La valeur retournée <strong>doit</strong> avoir un compteur de référence <strong>strictement positif</strong>.</p><pre class="crayon-plain-tag">boost::python::to_python_converter&lt;boost::posix_time::ptime, date_to_python_converter&gt;();</pre><p>Enfin nous déclarons notre &#8220;converter&#8221; en spécifiant le type natif et la structure/classe à utiliser pour la conversion.</p>
<p>À partir de ce moment là, notre module Python sera capable de convertir implicitement tout <code>boost::posix_time::ptime</code> en <code>datetime.datetime</code> Python.</p>
<h2>Conversion de Python vers C++</h2>
<p>La conversion dans l&#8217;autre sens demande un peu plus de travail :</p><pre class="crayon-plain-tag">/**
 * \file module.cpp
 * \author Julien Kauffmann &lt;julien.kauffmann@freelan.org&gt;
 * \brief The Python module file.
 */

#include &lt;boost/python/module.hpp&gt;
#include &lt;boost/python/def.hpp&gt;
#include &lt;boost/python/class.hpp&gt;

#include &lt;boost/date_time/posix_time/posix_time.hpp&gt;

#include &lt;datetime.h&gt;

boost::posix_time::ptime add_five_seconds(boost::posix_time::ptime ptime)
{
  if (!ptime.is_special())
  {
    return ptime + boost::posix_time::seconds(5);
  }

  return ptime;
}

struct date_to_python_converter
{
  static PyObject* convert(boost::posix_time::ptime value)
  {
    if (value.is_not_a_date_time())
      return Py_None;

    PyDateTime_IMPORT;

    return PyDateTime_FromDateAndTime(
        static_cast&lt;int&gt;(value.date().year()),
        static_cast&lt;int&gt;(value.date().month()),
        static_cast&lt;int&gt;(value.date().day()),
        static_cast&lt;int&gt;(value.time_of_day().hours()),
        static_cast&lt;int&gt;(value.time_of_day().minutes()),
        static_cast&lt;int&gt;(value.time_of_day().seconds()),
        static_cast&lt;int&gt;(value.time_of_day().total_microseconds() - value.time_of_day().total_seconds() * 1000000L)
        );
  }
};

struct date_from_python_converter
{
  static void* is_convertible(PyObject* obj_ptr)
  {
    assert(obj_ptr);

    if (obj_ptr == Py_None)
      return obj_ptr;

    PyDateTime_IMPORT;

    if (PyDateTime_Check(obj_ptr))
      return obj_ptr;

    return NULL;
  }

  static void convert(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    assert(obj_ptr);

    void* const storage = reinterpret_cast&lt;boost::python::converter::rvalue_from_python_storage&lt;boost::posix_time::ptime&gt;*&gt;(data)-&gt;storage.bytes;

    if (obj_ptr == Py_None)
    {
      new (storage) boost::posix_time::ptime();
    } else
    {
      PyDateTime_IMPORT;
      PyDateTime_DateTime* dt_ptr = reinterpret_cast&lt;PyDateTime_DateTime*&gt;(obj_ptr);

      const int year = PyDateTime_GET_YEAR(dt_ptr);
      const int month = PyDateTime_GET_MONTH(dt_ptr);
      const int day = PyDateTime_GET_DAY(dt_ptr);
      const int hour  = PyDateTime_DATE_GET_HOUR(dt_ptr);
      const int minute  = PyDateTime_DATE_GET_MINUTE(dt_ptr);
      const int second = PyDateTime_DATE_GET_SECOND(dt_ptr);
      const int microsecond = PyDateTime_DATE_GET_MICROSECOND(dt_ptr);

      new (storage) boost::posix_time::ptime(boost::gregorian::date(year, month, day), boost::posix_time::time_duration(hour, minute, second, 0) + boost::posix_time::microseconds(microsecond));
    }

    data-&gt;convertible = storage;
  }
};

BOOST_PYTHON_MODULE(module)
{
  boost::python::to_python_converter&lt;boost::posix_time::ptime, date_to_python_converter&gt;();

  boost::python::converter::registry::push_back(&amp;date_from_python_converter::is_convertible, &amp;date_from_python_converter::convert, boost::python::type_id&lt;boost::posix_time::ptime&gt;());  

  boost::python::def(&quot;add_five_seconds&quot;, &amp;add_five_seconds, boost::python::args(&quot;ptime&quot;), &quot;Add five seconds to a datetime then return the result&quot;);
}</pre><p></p>
<h3>Analyse</h3>
<p>Regardons encore une fois, ligne par ligne les modifications apportées :</p><pre class="crayon-plain-tag">struct date_from_python_converter</pre><p>Nous ajoutons une structure qui va contenir les routines de conversions. Contrairement à tout à l&#8217;heure, et comme nous le verrons plus tard, ceci n&#8217;est pas indispensable. Pour effectuer des conversions de Python vers C++, Boost::Python a juste besoin de deux fonctions. Que celles-ci soient statiques au sein d&#8217;une classe ou libres n&#8217;a aucune incidence.</p><pre class="crayon-plain-tag">static void* is_convertible(PyObject* obj_ptr)
  {
    assert(obj_ptr);

    if (obj_ptr == Py_None)
      return obj_ptr;

    PyDateTime_IMPORT;

    if (PyDateTime_Check(obj_ptr))
      return obj_ptr;

    return NULL;
  }</pre><p>La fonction <code>is_convertible()</code> sera utilisée par Boost::Python pour déterminer si l&#8217;instance Python à convertir peut l&#8217;être.</p>
<p>Dans notre cas nous acceptons tout d&#8217;abord <code>None</code> comme valeur &#8220;valide&#8221; pour respecter la symétrie, puis nous testons si l&#8217;instance est de type <code>datetime.datetime</code> grâce à la fonction <code>PyDateTime_Check()</code>.</p>
<p>En cas de succès, nous renvoyons la valeur passée en paramètre telle quelle. En cas d&#8217;erreur, nous renvoyons <code>NULL</code>.</p><pre class="crayon-plain-tag">static void convert(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    assert(obj_ptr);

    void* const storage = reinterpret_cast&lt;boost::python::converter::rvalue_from_python_storage&lt;boost::posix_time::ptime&gt;*&gt;(data)-&gt;storage.bytes;

    if (obj_ptr == Py_None)
    {
      new (storage) boost::posix_time::ptime();
    } else
    {
      PyDateTime_IMPORT;
      PyDateTime_DateTime* dt_ptr = reinterpret_cast&lt;PyDateTime_DateTime*&gt;(obj_ptr);

      const int year = PyDateTime_GET_YEAR(dt_ptr);
      const int month = PyDateTime_GET_MONTH(dt_ptr);
      const int day = PyDateTime_GET_DAY(dt_ptr);
      const int hour  = PyDateTime_DATE_GET_HOUR(dt_ptr);
      const int minute  = PyDateTime_DATE_GET_MINUTE(dt_ptr);
      const int second = PyDateTime_DATE_GET_SECOND(dt_ptr);
      const int microsecond = PyDateTime_DATE_GET_MICROSECOND(dt_ptr);

      new (storage) boost::posix_time::ptime(boost::gregorian::date(year, month, day), boost::posix_time::time_duration(hour, minute, second, 0) + boost::posix_time::microseconds(microsecond));
    }

    data-&gt;convertible = storage;
  }</pre><p>C&#8217;est ici que se passe le gros du travail : lorsque cette fonction est appelée, cela signifie que l&#8217;instance <code>obj_ptr</code> a passé l&#8217;appel à <code>is_convertible()</code> et est prête à être convertie.</p>
<p>Le paramètre <code>data</code> lui contient entre autres l&#8217;adresse de la zone mémoire où nous devons instancier notre résultat de conversion. Notez que pour ce faire, nous utilisons le &#8220;placement new&#8221; qui permet de construire une instance à un emplacement mémoire donné. Le <code>delete</code> correspondant sera automatiquement appelé par Boost::Python au besoin.</p>
<p>Pour finir, nous renseignons le champ <code>convertible</code> du paramètre <code>data</code> pour y indiquer où nous avons alloué notre résultat.</p><pre class="crayon-plain-tag">boost::python::converter::registry::push_back(&amp;date_from_python_converter::is_convertible, &amp;date_from_python_converter::convert, boost::python::type_id&lt;boost::posix_time::ptime&gt;());</pre><p>Comme auparavant, la dernière étape consiste à enregistrer le &#8220;converter&#8221; pour le faire connaître de Boost::python.<br />
On remarque ici que comme énoncé précédemment, l&#8217;appel prend en paramètre deux fonctions qui n&#8217;ont pas nécessairement besoin de faire partie d&#8217;une classe ou d&#8217;une structure.</p>
<h1>Résultat</h1>
<p>Compilez le code ci-dessus (voir le script <em>SConstruct</em> en annexe), puis chargez votre module au sein de l&#8217;interpreteur Python :</p><pre class="crayon-plain-tag">Python 2.4.3 (#1, Apr 14 2011, 20:41:59)
Type &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.

In [1]: import datetime, module

In [2]: d = datetime.datetime.now()

In [3]: print d
2011-08-09 11:30:43.239460

In [4]: print module.add_five_seconds(d)
2011-08-09 11:30:48.239460</pre><p>Ça y est ! La conversion <code>boost::posix_time::ptime</code> &lt;=&gt; <code>datetime.datetime</code> fonctionne parfaitement. <img src="https://blog.freelan.org/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley" /></p>
<h1>Conclusion</h1>
<p>Boost::Python est définitivement une bibliothèque très puissante. On peut certes regretter la qualité de sa documentation, mais fort heureusement les ressources à son sujet sur l&#8217;Internet ne manquent pas. Son extensibilité la rend utilisable dans toutes les situations et facilite grandement la vie du développeur.</p>
<p>J&#8217;espère que cet article vous aura aidé et/ou donné envie de découvrir/utiliser Boost::Python. Comme toujours, n&#8217;hésitez pas à me signaler toute coquille, erreur ou optimisation qui m&#8217;aurait échappé.</p>
<h1>Annexe</h1>
<p>Voici le script SConstruct que j&#8217;ai utilisé pour la compilation :</p><pre class="crayon-plain-tag">import os

env = Environment(ENV = os.environ.copy())
env['CPPPATH'] = ['/usr/include/python2.4']

module = env.SharedLibrary(source = Glob('*.cpp'), target = 'python/module.so', SHLIBPREFIX='', LIBS = ['boost_python', 'python2.4'])</pre><p></p>
<h1>Sources</h1>
<p>Ces pages m&#8217;ont été très utiles lors de la rédaction de cet article :</p>
<ul>
<li>L&#8217;API datetime sur <a href="http://docs.python.org/c-api/datetime.htmlhttp://docs.python.org/c-api/datetime.html">python.org</a> (en Anglais);</li>
<li>l&#8217;API Boost::Posix Time sur <a href="http://www.boost.org/doc/libs/1_47_0/doc/html/date_time/posix_time.html">boost.org</a> (en Anglais);</li>
<li>l&#8217;API de Boost::Python sur <a href="http://www.boost.org/doc/libs/1_46_0/libs/python/doc/index.html">boost.org</a> (en Anglais);</li>
<li>cet <a href="http://misspent.wordpress.com/2009/09/27/how-to-write-boost-python-converters/">article</a> de misspent (en Anglais).</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://blog.freelan.org/2011/08/09/boostpython-dates-et-conversions/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Découverte de libsystools 2.1</title>
		<link>https://blog.freelan.org/2010/12/10/decouverte-de-libsystools-2-1/</link>
		<comments>https://blog.freelan.org/2010/12/10/decouverte-de-libsystools-2-1/#comments</comments>
		<pubDate>Fri, 10 Dec 2010 16:06:51 +0000</pubDate>
		<dc:creator><![CDATA[Julien Kauffmann]]></dc:creator>
				<category><![CDATA[Développement]]></category>
		<category><![CDATA[Systools]]></category>
		<category><![CDATA[boost]]></category>
		<category><![CDATA[c++]]></category>
		<category><![CDATA[iconv]]></category>
		<category><![CDATA[libxml2]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[mac osx]]></category>
		<category><![CDATA[openssl]]></category>
		<category><![CDATA[socket]]></category>
		<category><![CDATA[systools]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[upnp]]></category>
		<category><![CDATA[windows]]></category>
		<category><![CDATA[xml]]></category>
		<category><![CDATA[xmlsec]]></category>

		<guid isPermaLink="false">http://blog.freelan.org/?p=111</guid>
		<description><![CDATA[Cette semaine, j'ai eu l'immense honneur d'effectuer la "release" de la version 2.1  de notre bibliothèque de base : libsystools. Cette version est le fruit de nombreuses heures de travail de l'équipe freelan et apporte un bon lot de nouveautés par rapport à la dernière version.

Cet article va tenter de vous faire découvrir ces nouveautés, d'expliquer certaines décisions de design et bien entendu de vous donner envie de l'essayer !]]></description>
				<content:encoded><![CDATA[<p>Cette semaine, j&#8217;ai eu l&#8217;immense honneur d&#8217;effectuer la &#8220;release&#8221; de la <a href="https://sourceforge.net/projects/libsystools/">version 2.1</a> de notre bibliothèque de base : <strong>libsystools</strong>. Cette version est le fruit de nombreuses heures de travail de l&#8217;équipe <strong>freelan</strong> et apporte un bon lot de nouveautés par rapport à la dernière version.</p>
<p>Cet article va tenter de vous faire découvrir ces nouveautés, d&#8217;expliquer certaines décisions de design et bien entendu de vous donner envie de l&#8217;essayer !</p>
<h1>Présentation</h1>
<p>Il y a 2 ans, le code de <strong>freelan</strong> commençait à être volumineux. Bon nombre de classes outils étaient écrites qui pouvaient intéresser d&#8217;autres projets. Nous avons donc décidé de rendre cet ensemble de classes autonome et c&#8217;est ainsi qu&#8217;est né <strong>libsystools</strong>.</p>
<p><strong>libsystools</strong> propose une interface C++ moderne pour un certain nombre de bibliothèques C mais aussi des classes nouvelles (notamment SmartBuffer).</p>
<p>Ses principaux domaines d&#8217;application sont :</p>
<ul>
<li>La gestion du xml (parsing, génération, modification, signature);</li>
<li>la gestion de la cryptographie (RSA, AES, SHA);</li>
<li>la gestion des certificats;</li>
<li>la gestion des ports série;</li>
<li>la gestion du SSL (TLS, DTLS);</li>
<li>la gestion du réseau (Socket, SocketAddress, SocketAddressResolver) en IPv4 <strong>et</strong> IPv6.</li>
<li>la gestion facilité de la mémoire (SmartBuffer);</li>
<li>une implémentation basique d&#8217;un client UPnP;</li>
<li>une implémentation basique d&#8217;un client HTTP;</li>
<li>une implémentation simple d&#8217;une classe de log;</li>
<li>la gestion de l&#8217;encodage (UTF-8, UTF-16, ISO-8859-1, etc.);</li>
<li>différentes méthodes pour calculer de checksum, compresser des flux, etc.</li>
</ul>
<p><strong>libsystools</strong> a été conçue dès le départ pour être extrêmement portable, qu&#8217;il s&#8217;agisse des systèmes d&#8217;exploitation ou des architectures. Elle est donc utilisable sur Windows (MinGW, Visual Studio 2010), Linux (gcc), Mac OSX (gcc), UNIX (gcc) aussi bien en 32 bits qu&#8217;en 64 bits.</p>
<p>L&#8217;ensemble de ses classes est documenté et différents exemples sont fournis qui traitent de chacun de ses domaines d&#8217;application.</p>
<p><strong>libsystools</strong> se base sur différentes bibliothèques :</p>
<ul>
<li>libxml2</li>
<li>libxmlsec1</li>
<li>openssl</li>
<li>libiconv</li>
<li>boost</li>
</ul>
<h2>Faciliter l&#8217;existant</h2>
<p>Les bibliothèques sur lesquelles se base <strong>libsystools</strong> sont bien écrites et fiables. Cependant, elles ne fournissent qu&#8217;une interface en C.</p>
<p>Lorsqu&#8217;on utilise que peu ces bibliothèques dans un projet, il est acceptable de se servir simplement de ces interfaces C. Cependant, lorsqu&#8217;un projet en fait un usage intensif, le code devient très rapidement très dur à maintenir.</p>
<p>Qui n&#8217;a jamais eu à écrire un code de ce style ?</p><pre class="crayon-plain-tag">#include &amp;lt;iostream&amp;gt;

#include &amp;lt;libfoo&amp;gt;

int main()
{
	FOOCONTEXT ctx = CreateFooContext();

	try
	{
		FOOHANDLER handler = CreateFooHandler(ctx);

		try
		{
			// Code
		}
		catch (...)
		{
			FreeFooHandler(handler);

			throw;
		}

		FreeFooHandler(handler);
	}
	catch (...)
	{
		FreeFooContext(ctx);

		throw;
	}

	FreeFooContext(ctx);

	return EXIT_SUCCESS;
}</pre><p>Bien sûr, un bon programmeur C++ simplifiera l&#8217;écriture de ce genre de code avec l&#8217;utilisation de classes conteneurs qui se chargent de libérer la ressource associée lors de leur destruction. Mais devoir refaire ce travail d&#8217;encapsulation à chaque utilisation est fastidieux, et ne règle qu&#8217;une partie du problème.</p>
<p>C&#8217;est dans cet esprit qu&#8217;on été développées les classes de <strong>libsystools</strong> : des wrappers simples, faciles à utiliser et efficaces.</p>
<p>En utilisant <strong>libsystools</strong>, le code précédent aurait plutôt ressemblé à :</p><pre class="crayon-plain-tag">#include &amp;lt;iostream&amp;gt;

#include &amp;lt;libfoo&amp;gt;

int main()
{
	FooContext ctx;
	FooHandler handler(ctx);

	// Code

	return EXIT_SUCCESS;
}</pre><p>Ce qui est bien plus lisible et maintenable, et présente l&#8217;avantage d&#8217;être &#8220;exception-safe&#8221;.</p>
<h1>Utilisation &amp; Exemples</h1>
<p>Nous pourrions exposer les différents aspects conceptuels qui ont motivé chaque décision pendant des heures et sur plusieurs pages, mais après tout, en programmation, il n&#8217;y a pas plus explicite que le code. Voici donc quelques exemples d&#8217;utilisation commentés.</p>
<h2>SmartBuffer, ou comment gérer la mémoire de façon intelligente</h2>
<p>S&#8217;il ne fallait garder qu&#8217;une seule classe, ce serait celle-ci : SmartBuffer est au coeur de <strong>libsystools</strong>. Utilisée partout, SmartBuffer représente un tableau d&#8217;octets à taille variable et dont les différentes instances peuvent partager leur mémoire.</p><pre class="crayon-plain-tag">#include &amp;lt;iostream&amp;gt;

#include &amp;lt;systools/smartbuffer.hpp&amp;gt;

int main()
{
	using systools::SmartBuffer;

	// On cr&eacute;e un buffer &agrave; partir d'une cha&icirc;ne C
	SmartBuffer buffer(&quot;Hello you&quot;);

	// On copie un buffer
	SmartBuffer copy = buffer; // copy et buffer partagent la m&ecirc;me m&eacute;moire.

	// On r&eacute;duit copy
	copy.resize(6); // copy a juste &eacute;t&eacute; r&eacute;duit, copy et buffer partagent toujours la m&ecirc;me m&eacute;moire.

	copy.append(&quot;me&quot;); // copy a &eacute;t&eacute; modifi&eacute;, copy et buffer ne partagent plus la m&ecirc;me m&eacute;moire.

	// On affiche les r&eacute;sultats
	std::cout &amp;lt;&amp;lt; &quot;buffer: &quot; &amp;lt;&amp;lt; buffer.toStdString() &amp;lt;&amp;lt; std::endl; // buffer: Hello you
	std::cout &amp;lt;&amp;lt; &quot;copy: &quot; &amp;lt;&amp;lt; copy.toStdString() &amp;lt;&amp;lt; std::endl; // copy: Hello me

	return EXIT_SUCCESS;
}

// On peut utiliser un SmartBuffer comme valeur de retour de fonction
systools::SmartBuffer getBuffer()
{
	// La m&eacute;thode-patron &quot;factory&quot; SmartBuffer::from permet de cr&eacute;er un buffer &agrave; partir de n'importe quel type.
	return systools::SmartBuffer::from&amp;lt;int&amp;gt;(4);
}</pre><p>Dans bon nombre de cas, l&#8217;utilisation de SmartBuffer permet de simplifier le code existant en remplaçant les paramètres de type :</p><pre class="crayon-plain-tag">void fonction(const char* inbuf, size_t inbuflen, char** outbuf, size_t&amp;amp; outbuflen);</pre><p>Par un simple :</p><pre class="crayon-plain-tag">SmartBuffer fonction(const SmartBuffer&amp;amp; inbuf);</pre><p>La lecture et la maintenance s&#8217;en trouvent tous deux grandement facilités.</p>
<h2>XML, les wrappers autour de libxml2</h2>
<p>Avec l&#8217;engouement de tous les domaines pour le web, il est rare aujourd&#8217;hui d&#8217;avoir du code à produire qui ne nécessite pas de manipuler du XML. En C++, plusieurs solutions existent.</p>
<p>Nous avons opté pour la libxml2 pour plusieurs raisons :</p>
<ul>
<li>C&#8217;est une bibliothèque mature, complète et quotidiennement maintenue;</li>
<li>elle est portable sur plusieurs architectures et plateformes;</li>
<li>elle s&#8217;interface très bien avec OpenSSL au sein de libxmlsec1 pour le support des signatures XML.</li>
</ul>
<p>Le module XML de libsystools se décompose en trois parties :</p>
<ul>
<li>Les éléments DOM, qui représentent de façon statique un arbre XML;</li>
<li>les &#8220;writers&#8221; qui permettent de générer du XML;</li>
<li>les objets XPath, qui permettent d&#8217;effectuer des requêtes dans les éléments DOM.</li>
</ul>
<p>Voyons un exemple d&#8217;utilisation :</p><pre class="crayon-plain-tag">#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cstdlib&amp;gt;
#include &amp;lt;cstring&amp;gt;

#include &amp;lt;boost/shared_ptr.hpp&amp;gt;
#include &amp;lt;boost/foreach.hpp&amp;gt;

#include &amp;lt;systools/xml.hpp&amp;gt;
#include &amp;lt;systools/xml_document.hpp&amp;gt;
#include &amp;lt;systools/xml_node.hpp&amp;gt;
#include &amp;lt;systools/xml_xpath.hpp&amp;gt;
#include &amp;lt;systools/xml_buffer_writer.hpp&amp;gt;
#include &amp;lt;systools/xml_document_writer.hpp&amp;gt;

int main(int, char**)
{
	using namespace systools;

	// Initialization
	xml::Initializer xml;

	std::cout &amp;lt;&amp;lt; &quot;Initializing...: &quot; &amp;lt;&amp;lt; &quot;OK&quot; &amp;lt;&amp;lt; std::endl;

	try
	{
		// Generation

		xml::XmlDocumentWriter writer;

		writer.startDocument();
		writer.startElement(&quot;configuration&quot;);
		writer.writeAttribute(&quot;xmlns&quot;, &quot;namespace&quot;);
		writer.writeElement(&quot;key&quot;, &quot;value&quot;);
		writer.startElement(&quot;parent&quot;);
		writer.writeElement(&quot;child&quot;, &quot;anna&quot;);
		writer.writeElement(&quot;child&quot;, &quot;bobby&quot;);
		writer.endElement();
		writer.endDocument();

		xml::XmlDocument document = writer.getResultAsDocument();
		std::cout &amp;lt;&amp;lt; &quot;Generating XML document: &quot; &amp;lt;&amp;lt; &quot;OK&quot; &amp;lt;&amp;lt; std::endl;
		std::cout &amp;lt;&amp;lt; document.toString().toStdString() &amp;lt;&amp;lt; std::endl;

		// XPath

		xml::XPath xpath = document.xpath();

		if (!xpath.registerNamespace(&quot;fl&quot;, &quot;namespace&quot;))
		{
			std::cerr &amp;lt;&amp;lt; &quot;Warning ! Failed to register the namespace !&quot; &amp;lt;&amp;lt; std::endl;
		}

		std::list&amp;lt;xml::XmlNode&amp;gt; list = document.xpath().evaluate(&quot;/fl:configuration/fl:parent&quot;);

		if (list.size() &amp;gt; 0)
		{
			list = list.front().xpath().evaluate(&quot;fl:child&quot;);

			std::cout &amp;lt;&amp;lt; &quot;Results:&quot; &amp;lt;&amp;lt; std::endl;

			BOOST_FOREACH(const xml::XmlNode&amp;amp; node, list)
			{
				std::cout &amp;lt;&amp;lt; node.name().toStdString() &amp;lt;&amp;lt; &quot;: &quot; &amp;lt;&amp;lt; node.content().toStdString() &amp;lt;&amp;lt; std::endl;
			}
		}
	}
	catch (Exception&amp;amp; ex)
	{
		std::cout &amp;lt;&amp;lt; &quot;Exception caught: &quot; &amp;lt;&amp;lt; ex.toString() &amp;lt;&amp;lt; std::endl;
	}

	return EXIT_SUCCESS;
}</pre><p>Reprenons cet exemple point par point :</p>
<ul>
<li>Ligne 20 : nous déclarons un xml::Initializer qui existera pour toute la portée du main. Cet objet permet d&#8217;<strong>initialiser</strong> la libxml2 et de <strong>garantir sa libération</strong> au moment opportun, de façon automatique.</li>
<li>Ligne 28 : nous créons un XmlDocumentWriter. Cet objet permet une construction facilité d&#8217;un arbre XML en construisant l&#8217;un après l&#8217;autre ses différents éléments.</li>
<li>Lignes 30 à 38 : Nous créons chacun des noeuds XML. Les différentes fonctions prennent des systools::String en paramètre et supportent donc parfaitement la gestion des différents encodages.</li>
<li>Ligne 40 : Nous récupérons un arbre XML complet à partir du writer.</li>
<li>Ligne 46 : Nous récupérons l&#8217;instance XPath associé à l&#8217;arbre XML (cette instance est partagée par tous les noeuds d&#8217;un même arbre)</li>
<li>Ligne 48 : Nous associons un nom court au namespace nommé &#8220;namespace&#8221; dans l&#8217;instance XPath.</li>
<li>Ligne 53 : Nous effectuons une requête XPath sur la racine du document.</li>
<li>Ligne 57 : Nous effectuons une requête XPath sur un sous-noeud du document.</li>
</ul>
<p>La manipulation du XML est grandement facilitée. On voit que seules quelques instructions suffisent à construire un arbre, à y naviguer et à en extraire les informations utiles.</p>
<h2>Le module réseau</h2>
<p>Une autre partie importante de libsystools est son module réseau. La manipulation des différentes fonctions et structures réseau d&#8217;un système a toujours été la bête noire des programmeurs novices. Entre les spécificités propres à chaque système d&#8217;exploitation, et les fonctions dépréciées suite au prochain passage à IPv6 (ça approche, si si, croyez-moi !), il n&#8217;est pas toujours évident de produire un code propre et robuste.</p>
<p>Le module réseau de libsystools a été conçu pour résoudre cette problématique. Il propose :</p>
<ul>
<li>Une classe qui permet représenter une adresse de socket (IPv4, IPv6, etc.);</li>
<li>une classe pour effectuer des résolutions de nom ou d&#8217;adresse sur le réseau;</li>
<li>des classes Socket et SecureSocket qui encapsulent respectivement une socket classique et une socket SSL (en TLS ou DTLS);</li>
<li>une classe &#8220;Select&#8221; qui encapsule de façon objet les appels à la méthode &#8220;select()&#8221;;</li>
<li>une classe pour représenter les adresses Ethernet;</li>
<li>des outils de conversion entre adresse IP et représentation numérique.</li>
</ul>
<p>Démontrons la simplicité de son utilisation en montrant le code d&#8217;un example qui va successivement :</p>
<ol>
<li>Chercher l&#8217;adresse de socket associée à 127.0.0.1:34000 en TCP.</li>
<li>Créer une socket</li>
<li>Connecter cette socket sur le serveur TCP situé à l&#8217;adresse recherchée en 1.</li>
<li>Attendre la réception d&#8217;un message.</li>
<li>Se déconnecter et libérer la socket et toutes les ressources associées.</li>
</ol>
<p></p><pre class="crayon-plain-tag">#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cstdlib&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;string&amp;gt;

#include &amp;lt;list&amp;gt;

#include &amp;lt;systools/network.hpp&amp;gt;
#include &amp;lt;systools/socket.hpp&amp;gt;
#include &amp;lt;systools/socket_address.hpp&amp;gt;
#include &amp;lt;systools/socket_address_resolver.hpp&amp;gt;

int main(int, char**)
{
	using namespace systools;

	network::Initializer network;

	SocketAddressResolver sar;

	try
	{
		sar.getAddrInfo(&quot;127.0.0.1&quot;, &quot;34000&quot;, IPPROTO_TCP, AF_UNSPEC);

		if (sar.size() &amp;lt;= 0)
		{
			std::cout &amp;lt;&amp;lt; &quot;No address information was returned.&quot; &amp;lt;&amp;lt; std::endl;
		} else
		{
			// Usually, one could choose to iterate trough the results and to keep only the first one to which a connection can be established.
			Socket socket(sar.front());
			SocketAddress address(sar.front());

			std::cout &amp;lt;&amp;lt; &quot;Connecting to: &quot; &amp;lt;&amp;lt; address.toNumericString() &amp;lt;&amp;lt; std::endl;

			socket.connect(address);

			SmartBuffer message;

			size_t recv_bytes = socket.recv(message);

			if (recv_bytes &amp;gt; 0)
			{
				std::cout &amp;lt;&amp;lt; &quot;Receiving message: &quot; &amp;lt;&amp;lt; std::string(message.constData(), message.size()) &amp;lt;&amp;lt; std::endl;
			}
		}
	}
	catch (Exception&amp;amp; ex)
	{
		std::cout &amp;lt;&amp;lt; &quot;Exception: &quot; &amp;lt;&amp;lt; ex.toString() &amp;lt;&amp;lt; std::endl;

		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}</pre><p>Pas si mal pour un code d&#8217;une cinquantaine de lignes qui gère correctement toutes les erreurs potentielles et la libération des ressources, non ?</p>
<h1>Pour conclure</h1>
<p>Vous n&#8217;avez eu ici qu&#8217;un très bref aperçu des possibilités offertes par la <strong>libsystools</strong>. Pour découvrir plus avant la bibliothèque, je vous invite à consulter sa <a href="http://www.freelan.org/doc-2.1">documentation en ligne</a>.</p>
<p>En tout cas, j&#8217;espère sincèrement que cette présentation aura attisé votre curiosité et vous aura donné envie d&#8217;essayer la bibliothèque lors de vos prochains développements. Bien entendu, si vous avez des questions générales sur la bibliothèque et des interrogations concernant son évolution future, n&#8217;hésitez pas à commenter cet article ou directement contacter l&#8217;équipe sur la <a href="mailto:libsystools-users@lists.sourceforge.net">mailing-list</a> (en anglais).</p>
<p>Bon code ! <img src="https://blog.freelan.org/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley" /></p>
]]></content:encoded>
			<wfw:commentRss>https://blog.freelan.org/2010/12/10/decouverte-de-libsystools-2-1/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
