Articles C++ pour débutter

Ce sont des articles techniques parus dans la revue Programmez:

La programmation orientée objet (OOP) : http://bit.ly/2rhQFYl

Basic – Partie 1 : http://bit.ly/2FT92Nn

Les classes – Partie 2 : http://bit.ly/2FROSDK

Les Templates – Partie 3 : http://bit.ly/2zwZ4fd

STL – Partie 4 : http://bit.ly/2Sp6kAJ

Boost – Partie 5: http://bit.ly/2U7r4OX

Application pratique du C++ pour mes étudiants

Pour mes étudiants en 4ème année, j’ai pris comme modèle d’application 2 styles d’app:

  • Serveur Web REST API avec Microsoft CPPREST SDK
  • Une application Windows graphique MFC

modeler2_shapes_infra

Je leur ai expliqué qu’il faut des couches d’abstractions entre la plomberie de tel ou tel SDK et le code C++ métier.

Pour le serveur Web REST API en moins de 200 lignes de code, c’est bluffant. Pour faire le back-end d’une application mobile, c’est le candidat parfait.

J’ai senti qu’ils étaient très intéressés de voir que C++, c’est concret.

Examen C++ pour mes étudiants ESGI 4ème année AL2

NOM :                                                                    CLASSE :

 Examen noté de C++ Moderne

1° Créer une classe abstraite CShape avec

  • Un constructeur qui prend en paramètre un rectangle (à encapsuler de plusieurs manières)
  • Une méthode virtuelle pure Draw

2° Créer des classes dérivées de CShape comme CRectangle et CEllipse

3° Coder le minimum pour le main() compile :

  • Coder pch.h et y inclure tous les entêtes nécessaires
  • Créer la classe CView et ses méthodes (voir main)
  • Créer la classe CCommandBar et ses méthodes (voir main)
  • Etablir un modèle objet clair et cohérent entre CShape, CRectangle, CElippse, CDeviceContext, CCommandBar et CView et coder l’ensemble de ces classes

Conseils :

  • Ecrire correctement
  • Coder avec style
  • En cas de difficultés, improvisez ! Lancez-vous !

int main()
{
	//CDeviceContext dc;
	//CRectangle r1(10, 10, 50, 50);
	//r1.Draw(dc);

	CView view;
	CCommandBar cmdBar;

	std::shared_ptr shape1 = cmdBar.CreateShape(ShapeType::rectangle);
	view.AddShapeToView(shape1);
	std::shared_ptr shape2 = cmdBar.CreateShape(ShapeType::ellipse);
	view.AddShapeToView(shape2);

	view.DrawView();

	return 0;
}

Le coding graphique est un simple cout dans la console :
void CDeviceContext::Rectangle(CRect rect){
	std::cout << "Rectangle x1:" << rect._x1 << ", y1:" << rect._y1 
<< ", x2:" << rect._x2 << ", y2:" << rect._y2 << std::endl;}
void CDeviceContext::Ellipse(CRect rect){
	std::cout << "Ellipse x1:" << rect._x1 << ", y1:" << rect._y1 
<< ", x2:" << rect._x2 << ", y2:" << rect._y2 << std::endl;}

 

Examen C++ pour mes étudiants ESGI 4ème année AL1

Je donne des cours de C++ à l’ESGI en 4ème année. Voici l’examen du groupe AL1.

Examen noté de C++

1° En quelle année le C++ moderne a-t-il été standardisé ?

2° Quelles sont les versions de C++ que vous connaissez ?

3° Quels sont les éléments de C++ qui sont standardisés ?

4° Citez les en-têtes de la STL que vous connaissez :

5° Décrivez à quoi sert auto :

6° Qu’est-ce qu’un range-for ? Ecrire un exemple :

7° Quelle est la collection container de base de la STL à utiliser ?

8° Ecrire un code qui explique les itérateurs en C++ ; faire un parcours de vector<string> :

9° Ecrire un constructeur par copie :

10° Ecrire un opérateur de copie :

11° Ecrire la ligne de commande pour compiler un programme main.cpp avec Visual C++ ou GCC :

12° Ecrire comment affecter/initialiser un vector<string> en une seule ligne :

13° Considérant la classe

class Item { public: std::string _name;  int _age; };

Comment initializer un vector<Item> en une seule ligne :

14° Ecrire la fonction de surcharge de l’opérateur << pour écrire un Item dans un flux de sortie :

15° Quelle est la méthode qui permet d’ajouter un élément dans un vector

16° Quelle est la routine de <algorithm> qui permet de parcourir un container via une routine callback pour chaque élément ? Ecrire un bout de code avec la routine ou lambda :

17° Qu’est-ce qu’une lambda ?

18° Comment insérer un élément dans une map ? Ecrire un bout de code :

19° Qu’est-ce qu’un smart pointer et décrire son avantage ? Citez les trois templates à connaitre :

20° Quelles sont les 2 fonctions templates utilisées avec les smart pointeurs ?

Articles pour le magazine Programmez

Voici les articles que j’ai écrit pour la revue Programmez depuis fin 2017. Le dernier article est à paraître en Janvier 2019. Les articles sont disponibles ici en téléchargement:

Liste des articles:

  • N°225: Rookit key Logger – PDF
  • N°224: Space Invaders 1978 en C/C++ avec SFML – PDF
  • N°223: Windows Le Multithreading en C/C++ – PDF
  • N°222: Linux Le Multithreading en C++ – PDF
  • N°221: Au coeur d’un Service Windows NoSQL – PDF
  • N°220: Créer un service Windows – PDF
  • N°218: Migrer son code C/C++ en 64 bits – PDF
  • N°217: Les Tests en C++ – PDF
  • N°216: La Programmation Orientée Objet en C++ – PDF
  • N°215: Utiliser shared_ptr<T> en C++ pour la gestion des ressources – PDF
  • N°214: Développez un IDE en C++ Partie II – PDF
  • N°213: Développez un IDE en C++ Partie I – PDF
  • N°212: Un serveur REST Web API en C++ – PDF
  • N°211: Pourquoi C++ en 2017 ? – PDF

La Programmation Orientée Objet en C++

Introduction

Il y a plusieurs façons d’utiliser le C++. On peut être utilisateur de classes ou concepteur de classes. Dans les deux cas, définir, designer et implémenter des classes sont les activités primaires des développeurs C++. Par où on commence ? En général, on démarre avec une abstraction ou un concept. On considère un truc à coder et puis on essaie de voir comment on va gérer son ou ses fonctions, ce qui est interne et ce qui est public et comment on va l’utiliser… Au démarrage de cette réflexion, il faut se mettre la casquette de celui qui va utiliser la classe. Notre exemple de concept pour cet article sera un élément graphique à dessiner comme une ligne, un rectangle, une ellipse, etc. En anglais, c’est un shape.

// C'est une déclaration de classe
class CShape;

// C'est la future manière avec laquelle je veux dessiner des Shapes
bool Draw(const CShape& item);

Ensuite on envisage la classe dans son ensemble :
class CShape
{
public:
	// interface publique

private:
	// implémentation privée
};

Qu’est-ce que l’on met en private/public et comment va-t-elle être structurée.  On commence petit bras et puis la classe prend du volume.

Constructeur et Destructeur

La première question qui vient à l’esprit est comment je vais créer mon objet et ai-je besoin de lui fournir des paramètres. Avec ma classe CShape, on peut se dire, je n’ai pas besoin mais aussi j’en ai besoin. Les deux possibilités existent ! Soit c’est une figure connue -> d’où une énumération pour les types connus et autre possibilité qui est de dessiner une image et là, il me faut un nom de fichier ; tout est ouvert !

enum ShapeType
{
	line,
	circle,
	rectangle,
	left_arrow,
	right_arrow,
	picture
};

L’avantage de l’énumération es que c’est évalué à la compilation. Il ne peut pas y avoir d’erreur sur le nommage car c’est un type explicite contrairement à une simple chaîne de caractères. Voyons sommairement comment pourrait être construite cette fameuse classe CShape…

class CShape
{
public:
	CShape();
	CShape(int x, int y, int xx, int yy, ShapeType type);
	CShape(int x, int y, int xx, int yy, std::string fileName);
	virtual ~CShape();

private:
	std::string m_fileName;
	ShapeType m_type;
};

J’y vois deux façons de créer un shape. Soit à partir de l’énumération soit à partir d’un fichier pour les images. Pour le moment, je ne sais pas comment organiser le dessin du shape. Dois-je le mettre à l’intérieur de la classe ou à l’extérieur ? Je ne sais pas… Il faut du temps pour considérer quelle sera la façon la plus naturelle de réaliser cette opération. Dans le monde Windows GDI+, pour dessiner un élément il faut un handle particulier qui possède toutes les primitives de dessins.  En pensant comment allait être dessiner un élément, on peut envisager d’inclure une méthode Draw dans la classe CShape. La première pensée, c’est de se dire, je vais implémenter tous les cas possibles dans Draw avec une construction qui ressemble à ça :

	void Draw(const CDrawingContext& ctxt) const
	{
		if (m_type == ShapeType::line)
		{
			ctxt.Line(m_rect.left, m_rect.bottom,
				m_rect.top, m_rect.right);
		}
		if (m_type == ShapeType::rectangle)
		{
			ctxt.Rectangle(m_rect);
		}
		// TODO
	}

Dans la classe, il est important de marquer les méthodes qui ne modifient pas les données private. Pour faire cela, on leur ajoute le mot-clé const à la fin comme c’est indiqué sur la méthode Draw.

De plus, lorsque l’on passe un argument qui n’as pas vocation à être modifié, on le passe en référence const. Comme on peut le voir ci-dessus, la définition d’une classe est un travail itératif dans lequel, les meilleures idées chassent celles d’avant ou les conforte. Si une classe est bien conçue, le lecture de ses membres public est limpide car on va à l’essentiel. Attention, les membres private expliquent aussi beaucoup de choses sur les détails d’implémentation. Maintenant, mettons-nous dans un contexte où il existe plusieurs classes et les principes vu ci-dessus ne suffisent pas à faire un modèle objet. Il nous faut des mécanismes plus sophistiqués pour relier les classes les unes avec les autres.

Les classes possèdent des associations et des comportements et c’est ici que les Jedi se positionnent pour y designer un modèle élégant, facile à utiliser et qui rend les services voulus.

Notre méthode Draw n’est pas OOP car si je rajoute un type dans l’énumération, je dois ajouter un if() dans la méthode Draw() et coder l’implémentation. On peut faire mieux, beaucoup mieux.

Les concepts de OOP

Les deux piliers de l’OOP sont l’héritage et le polymorphisme. L’héritage permet de grouper les classes en famille de types et permet de partager des opérations et des comportements et des données. Le polymorphisme permet de déclencher des opérations dans ces familles à un niveau unitaire. Ainsi ajouter ou supprimer une classe n’est pas trop un problème.

L’héritage définit une relation parent/enfant. Le parent définit l’interface public et l’implémentation private est commune à tous ses enfants. Chaque enfant choisit s’il veut hériter d’un comportement unique ou bien le surcharger. En C++, le parent est appelé « classe de base » et l’enfant « classe dérivée ». Le parent et les enfants définissent une hiérarchie de classes. Le parent est souvent une classe abstraite et les enfants implémentent les méthodes virtuelles pures pour que l’ensemble fonctionne.

Dans un programme OOP, on manipule les classes via un pointeur sur la classe de base plutôt que sur les objets dérivés.

Une classe abstraite

Revenons sur la classe CShape… La prochaine étape dans le design est de créer une classe abstraite pour identifier les opérations propres à chaque item – ce qui implique des opérations avec une implémentation basée sur une classe dérivée. Ces opérations sont des fonctions virtuelles pures de la classe de base. Une méthode virtuelle pure est une méthode abstraite. Il n’y a pas de corps. Cela implique que le classe devient abstraite aussi. Il n’est pas possible de créer un objet à partir d’une classe abstraite. La méthode Draw() doit être définie comme virtuelle pure car il y a plusieurs types de dessin à faire et que chaque dessin est propre à une classe donnée. Le corps de Draw() n’existe pas, c’est la raison pour laquelle on dit que la classe est abstraite ; il y a au moins une méthode virtuelle pure.

Voici donc comment on fait :

class CShapeEx
{
public:
	CShapeEx() {}
	CShapeEx(ShapeType type, RECT rect, const std::string &fileName) 
		: m_type(type), m_rect(rect), m_fileName(fileName) {}
	virtual ~CShapeEx() {}

public:
	virtual void Draw(const CDrawingContext& ctxt) const = 0;
	virtual void DrawTracker(const CDrawingContext& ctxt) const = 0;

private:
	std::string m_fileName;
	ShapeType m_type;
	RECT m_rect;
};

Et nous allons maintenant déclarer des classes dérivées de CShapeEx :

class CRectangle : public CShapeEx
{
public:
	CRectangle() {}
	virtual ~CRectangle() {}

public:
	virtual void Draw(const CDrawingContext& ctxt) const
	{
		// TODO
		std::cout << "Rectangle::Draw" << std::endl;
	}

	virtual void DrawTracker(const CDrawingContext& ctxt) const
	{
		// TODO
	}
};

class CLine : public CShapeEx
{
public:
	CLine() {}
	virtual ~CLine() {}

public:
	virtual void Draw(const CDrawingContext& ctxt) const
	{
		// TODO
		std::cout << "Line	::Draw" << std::endl;
	}

	virtual void DrawTracker(const CDrawingContext& ctxt) const
	{
		// TODO
	}
};

Si on veut rajouter un item à dessiner dans la hiérarchie, il suffit de rajouter une classe dérivée ! Maintenant nous allons voir comment fonctionne ce mécanisme de virtual…

	CDrawingContext ctxt;
	CRectangle * pRect = new CRectangle();
	CLine * pLine = new CLine();
	CShapeEx * ptr = nullptr;
	ptr = pRect;
	ptr->Draw(ctxt);
	ptr = pLine;
	ptr->Draw(ctxt);

On commence par déclarer 2 items à dessiner. Puis le manager, un pointeur sur la classe de base CShapeEx est déclaré. A chaque fois qu’il pointe sur un objet d’une classe dérivée, la fonction virtuelle Draw() appelée est celle de la classe dérivée car cette fonction est définie comme virtuelle.

code5

Maintenant, il nous manque quelque chose de très utile et qui évitera les usines à gaz de construction d’objets dérivés. Il nous faut une factory ! Un mécanisme de création d’objet.

La factory

class CFactory
{
private:
	CFactory();

public:
	static CShapeEx * CreateObject(ShapeType type)
	{
		if (type == ShapeType::line)
		{
			CLine * pObj = new CLine();
			pObj->m_type = type;
			return pObj;
		}

		if (type == ShapeType::rectange)
		{
			CRectangle * pObj = new CRectangle();
			pObj->m_type = type;
			return pObj;
		}

		// TODO

		return nullptr;
	}
};

Cette classe permet via une méthode static de créer un objet en fonction de son type dans l’énumération. Le type est affecté en variable membre de l’objet créé. On notera que le constructeur est private, cela veut dire que l’on ne peut pas déclarer d’objet CFactory. On ne peut qu’utiliser la méthode static CreateObject. Sauf que, la compilation tombe en erreur :

code6

Le membre m_type est inaccessible vue son niveau de protection… On va donc ajouter quelque chose à la classe CShapeEx :

class CShapeEx
{
public:
	CShapeEx() {}
	CShapeEx(ShapeType type, RECT rect, const std::string &fileName) 
		: m_type(type), m_rect(rect), m_fileName(fileName) {}
	virtual ~CShapeEx() {}

public:
	virtual void Draw(const CDrawingContext& ctxt) const = 0;
	virtual void DrawTracker(const CDrawingContext& ctxt) const = 0;

private:
	std::string m_fileName;
	ShapeType m_type;
	RECT m_rect;

	// La classe CFactory peux avoir accès aux membres...
	friend class CFactory;
};

On ajoute en fin de classe que la classe CFactory est amie de la classe CShapeEx. Et là, il n’y a plus d’erreur de compilation ! Les puristes comme AlainZ, à Redmond, vous diront que cela viole la mécanique objet et que c’est une construction exotique et qu’il vaut mieux passer par le constructeur pour passer le type… Je ne suis pas de cet avis. Je pense que c’est élégant de faire une entorse pour la factory. Voici maintenant le code qui appelle la factory :

	CDrawingContext ctxt;
	CShapeEx * pRect = CFactory::CreateObject(ShapeType::rectange);
	CShapeEx * pLine = CFactory::CreateObject(ShapeType::line);
	CShapeEx * ptr = nullptr;
	ptr = pRect;
	ptr->Draw(ctxt);
	ptr = pLine;
	ptr->Draw(ctxt);

Et le résultat est équivalent.

Compilation sous Linux

Pour ceux qui ne le savent pas, le langage C++ est normalisé ISO. Sur la plateforme Linux, là où tout est fait en C/C++, le langage possède plusieurs compilateurs dont le célèbre GCC. Vous pouvez utiliser Visual Studio Code que fournit Microsoft comme IDE pour compiler sous Linux via le terminal.

Il faut installer l’extension C++. Moi j’ai installé Cygwin et MSYS. Ainsi je dispose de commandes Linux.

code7

Une touche de C++ 11

On va essayer de continuer notre application de dessin en matérialisant la zone de dessiner. Cela ressemble à un ensemble de shapes sur un support de dessin. Première étape quand on pense au concept de la planche à dessin c’est quoi ? Le stockage des éléments graphiques dans un container STL. Oui Monsieur, quand on veut stocker quelque chose, on passe par les containers STL. On va donc utiliser le std::vector de la STL. On va stocker des shapes un peu particulier que sont les shapes partagés. Partagés ? Par qui ? Dans un vrai logiciel de dessin, on affiche les propriétés dans une grille et le dessin est sur le panneau central donc on partage nos objets et ce n’est pas que de l’affichage. Je vous montre le résultat.

code8

Sur cet écran, on voit que le rectangle gris sélectionné a ses attributs directement dans le grille de propriétés mais aussi sur le Ribbon. Comment faire pour partager les données entre le panneau de dessin, le Ribbon et la grille de propriétés ? Est-ce que l’on fait des copies des objets ? Oui, mais on fait des copies de pointeurs et non des copies d’objets. Pour faire cela, une utilise le mécanisme des pointeurs partagés de la STL. Voyons son utilisation. Premièrement, on va designer la zone de dessin.

La zone de dessin

Cette zone contient les objets shape à dessiner. On va donc créer un vector. Le container vector est défini dans . On va avoir une collection d’éléments partagés CShapeEx. Ainsi, on pourra stocker ce pointeur particulier où on veut sans se soucier de la libération de la mémoire. La zone de dessin est une vue fille MDI en jargon Windows. Elle possède deux scrollbars, un verticale et une horizontale. Si on veut faire une gestion de shared_ptr, il faut faire évoluer la factory :

	static std::shared_ptr CreateObject(ShapeType type)
	{
		std::shared_ptr pObj = nullptr;

		if (type == ShapeType::line)
		{
			pObj = std::make_shared();
			pObj->m_type = type;
			return pObj;
		}

		if (type == ShapeType::rectangle)
		{
			pObj = std::make_shared();
			pObj->m_type = type;
			return pObj;
		}

		return nullptr;
	}

On ne fait plus un new() mais un appel à std::make_shared().

De plus, dans l’utilisation des objets, cela donne ça :

	CDrawingContext ctxt;
	std::shared_ptr pRect = CFactory::CreateObject(ShapeType::rectangle);
	std::shared_ptr pLine = CFactory::CreateObject(ShapeType::line);
	std::shared_ptr ptr = nullptr;
	ptr = pRect;
	ptr->Draw(ctxt);
	ptr = pLine;
	ptr->Draw(ctxt);

Il existe même une façon de laisser le compilateur déterminer le type de variable utilisée en utilisant auto :

	CDrawingContext ctxt;
	auto pRect = CFactory::CreateObject(ShapeType::rectangle);
	auto pLine = CFactory::CreateObject(ShapeType::line);
	auto ptr = pRect;
	ptr->Draw(ctxt);
	ptr = pLine;
	ptr->Draw(ctxt);

Revenons à notre design de classes. Le container de shapes est juste une collection std::vector de std::shared_ptr :

class CElementContainer : public CObject
{
private:
	
public:
	DECLARE_SERIAL(CElementContainer);
	CElementContainer();
	virtual ~CElementContainer(void);

// ../..
// overridden for document i/o
virtual void Serialize(CElementManager * pElementManager, CArchive& ar);   
// Debuging Operations
public:
	void DebugDumpObjects(CModeler1View * pView);

// Operations MFC like
public:
	std::shared_ptr GetHead();
	int GetCount();
	void RemoveAll();
	void Remove(std::shared_ptr pElement);
	void AddTail(std::shared_ptr pElement);

// Attributes
private:
	vector m_objects;
};

Là, je vous montre un vrai code et les éléments sont matérialisés par la classe CElement mais c’est pareil que CShapeEx. On notera que les fonctions de gestion au vector sont des méthodes helpers dans cette classe. On notera aussi que la fonction Serialize qui permet de faire Load/Save est gérée dans cette classe. En effet, si il existe des donnés à mettre dans un fichier de données, c’est bien cette classe. Il faut maintenant intégrer cette classe dans la mécanique de fenêtrage Windows. Dans un canevas MFC, on possède un Document pour y stocker les données et une View pour y faire les opérations d’affichage.

Abstraction

La couche de dessin est confiée à une classe explicite et les méthodes ne sont pas directement insérées dans la vue. Pourquoi un tel niveau de poupées russes ? Parce que sinon, c’est crado. C’est trop entrelacé dans les couches Windows et le code est spaguetti. J’ai donc un e classe qui se nomme CElementManager qui fait le lien avec la View MFC. De ce fait beaucoup de méthodes prennent en paramètre une View… C’est de la délégation de comportement. Mais dans cette classe on trouve tous les éléments de la zone de dessin :

  • Les objets à afficher
  • Les éléments d’une sélection
  • Les éléments du presse-papier
  • La couleur de fond de la zone
  • Le mode du pointeur de souris
  • Le facteur de zoom
  • Un booléen pour savoir si on est en train de dessiner un gabarit
  • L’élément courant avec son type et son id et son nom

Voici un aperçu du fichier d’entêtes de la classe CElementManager :

code9

Cette classe est juste dépendante d’une View sur laquelle il va y avoir des opérations et des manipulations d’affichage. Exemple de méthode :

void CElementManager::ZoomIn(CModeler1View * pView)
{
	m_fZoomFactor += 0.10f;
	Invalidate(pView);
}

Conclusion

Pour un code orienté objet, il ne faut pas hésiter à faire des constructions, créer des classes et leur donner un minimum de responsabilités. De toute façon, si vous créé une classe et que vous n’y trouvez aucune méthode à mettre dedans, vous devrez faire machine arrière… La construction orientée objet est importante dans le développement moderne et on retrouvera les mêmes principes dans les autres langages comme Java ou C#.

Le code de l’application graphique est disponible ici : https://github.com/ChristophePichaud/UltraFluidModeler

Christophe PICHAUD
Consultant sur les technologies Microsoft

Un serveur REST Web API en C++

Introduction

Imaginez que vous puissiez développer le back-end de votre solution logicielle sur un serveur avec le roi des langages qu’est le C++ ? Sur Linux : oui. Sur Windows : oui. Un seul code portable ? Oui. Le tout sans complexité délirante juste avec des classes et des méthodes de traitement… Et on retourne du JSON pour un front-end en JS ? Chiche ! Suivez-moi, je vous explique…

Casablanca alias C++ REST SDK

Pour développer une Web API, beaucoup d’entre nous ne connaissent que ASP.NET et C#… Depuis MFC ISAPI et ATL Server, Visual C++ ne fournit plus de templates de projet pour des développements serveur. Un SDK reprend le flambeau : le REST SDK, nom de code « Casablanca ». Ce SDK écrit en C++ moderne (C++ 11) propose de réaliser du code serveur et client en utilisant les derniers patterns à savoir support du http et du JSON.

Disponible sur Github à l’adresse suivante : https://github.com/Microsoft/cpprestsdk . Ce SDK propose aussi des exemples de code pour commencer en douceur.

Un serveur http REST simple

Examinons le code minimum pour créer un serveur Web API qui retourne du JSON. Premièrement, il faut définir une classe qui contient un http_listener.

code1

Ensuite, il faut configurer ce listener en lui fournissant un URI sur laquelle on se met en écoute ainsi que les handlers pour les opérations GET, PUT, POST et DELETE. Mais avant tout, il faut déclarer le serveur comme une variable globale comme suit :

std::unique_ptr g_http;

Ensuite, il faut préparer les éléments indispensables au serveur à savoir son URI d’écoute :

utility::string_t port = U("34568");
    utility::string_t address = U("http://localhost:");
    address.append(port);

    uri_builder uri(port);
    uri.append_path(U("MyServer/Action/"));

    auto addr = uri.to_uri().to_string();
    g_http = std::unique_ptr(new MyServer(addr));
    g_http->open().wait();
    
    ucout << utility::string_t(U("Listening for requests at: ")) << addr << std::endl;

L’appel à open() active l’écoute. Reste à découvrir le code du constructeur de la classe MyServer car il faut enregistrer les handlers sur les méthodes HTTP :

code2

OK le corps du serveur http est là… Regardons le corps des méthodes HTTP. Nous allons en coder une seule mais le système est simple.

code3

Ces méthodes ne sont pas implémentées mais patience, voici ce que nous allons coder. Sur la méthode GET si on nous envoie « get_developers » alors on dump une structure de données en JSON.

Le corps du handler GET

La structure de données JSON retournée est définie comme suit :

{« job »: »Developers », »people »:[{« age »:25, »name »: »Franck »},{« age »:20, »name »: »Joe »}]}

Voyons le corps de cette méthode GET:

void MyServer::handle_get(http_request message)
{
	ucout << U("Message") << U(" ")
		<< message.to_string() << endl;

	ucout << U("Relative URI") << U(" ")
		<< message.relative_uri().to_string() << endl;

	auto paths = uri::split_path(uri::decode(message.relative_uri().path()));
	for (auto it1 = paths.begin(); it1 != paths.end(); it1++)
	{
		ucout << U("Path") << U(" ")
			<< *it1 << endl;
	}

	auto query = uri::split_query(uri::decode(message.relative_uri().query()));
	for (auto it2 = query.begin(); it2 != query.end(); it2++)
	{
		ucout << U("Query") << U(" ")
			<< it2->first << U(" ") << it2->second << endl;
	}

	auto queryItr = query.find(U("request"));
	utility::string_t request = queryItr->second;
	ucout << U("Request") << U(" ") << request << endl;

	if (request == U("get_developers"))
	{
		Data data;
		data.job = U("Developers");
		People p1;
		p1.age = 25;
		p1.name = U("Franck");
		data.peoples.push_back(p1);
		People p2;
		p2.age = 20;
		p2.name = U("Joe");
		data.peoples.push_back(p2);

		utility::string_t response = data.AsJSON().serialize();
		ucout << response << endl;

		message.reply(status_codes::OK, data.AsJSON());
		return;
	}

	message.reply(status_codes::OK);
};

On commence par dumper les éléments reçus en entrée ? On affiche la requête, l’url demandée et les paramètres. Vous remarquerez que uri::split_query retourne une map de string string pour récupérer les paramètres de la requête. Si la requête est « get_developers », alors on déclare une structure de données et on génère du JSON.

code4

La génération du JSON

Voyons le code qui permet de générer du JSON à partir d’une structure de données. Il n’y a rien de magique, regardez :

struct People
{
	utility::string_t name;
	double age;

	static People FromJSON(const web::json::object & object)
	{
		People result;
		result.name = object.at(U("name")).as_string();
		result.age = object.at(U("age")).as_integer();
		return result;
	}

	web::json::value AsJSON() const
	{
		web::json::value result = web::json::value::object();
		result[U("name")] = web::json::value::string(name);
		result[U("age")] = web::json::value::number(age);
		return result;
	}
};

Cette structure représente un développeur. Il y a 2 propriétés : name et age. Le mécanisme de transformation d’une chaîne se fait avec les méthodes object.at().as_string() et web::json::value::string(). Pour un entier, c’est avec object.at().as_integer() et  web::json::value::number(). C’est assez simple. Pour la structure suivante, qui met en œuvre un tableau (vector), il faut utiliser d’autres méthodes :

struct Data
{
	std::vector peoples;
	utility::string_t job;

	Data() {}

	void Clear() { peoples.clear(); }

	static Data FromJSON(const web::json::object &object)
	{
		Data res;

		web::json::value job = object.at(U("job"));
		res.job = job.as_string();

		web::json::value p = object.at(U("people"));
		for (auto iter = p.as_array().begin(); iter != p.as_array().end(); ++iter)
		{
			if (!iter->is_null())
			{
				People people;
				people = People::FromJSON(iter->as_object());
				res.peoples.push_back(people);
			}
		}

		return res;
	}

	web::json::value AsJSON() const
	{
		web::json::value res = web::json::value::object();
		res[U("job")] = web::json::value::string(job);

		web::json::value jPeoples = web::json::value::array(peoples.size());

		int idx = 0;
		for (auto iter = peoples.begin(); iter != peoples.end(); iter++)
		{
			jPeoples[idx++] = iter->AsJSON();
		}

		res[U("people")] = jPeoples;
		return res;
	}
};

Pour générer un tableau en JSON, il faut utiliser la fonction  web::json::value::array. Ensuite on itère sur le vector pour associer les éléments du tableau un par un.

La partie client

Le côté client peut être développé avec n’importe quelle technologie à partir du moment où il est permis de déclencher des requêtes http et de lire du JSON. Ici, le client est écrit aussi en C++ pour vous montrer comme c’est facile d’utiliser le REST SDK :

#ifdef _WIN32
# define iequals(x, y) (_stricmp((x), (y))==0)
#else
# define iequals(x, y) boost::iequals((x), (y))
#endif

int wmain(int argc, wchar_t *argv[])
{
    utility::string_t port = U("34568");
    if(argc == 2)
    {
        port = argv[1];
    }

    utility::string_t address = U("http://localhost:");
    address.append(port);
    http::uri uri = http::uri(address);

	http_client client(http::uri_builder(uri)
		.append_path(U("/MyServer/Action/")).to_uri());

    while (true)
    {
        std::string method;
        ucout << "Enter method:";
        cin >> method;

		http_response response;

		if (iequals(method.c_str(), "get_developers"))
		{
			utility::ostringstream_t buf;
			buf << U("?request=") 
				<< utility::conversions::to_string_t(method) 
				<< U("&city=Paris");
			
			response = client.request(methods::GET, buf.str()).get();

			ucout << U("Response ") << response.to_string() << endl;

			json::value jdata = json::value::array();
			jdata = response.extract_json().get();
			Data data = Data::FromJSON(jdata.as_object());
		}
        else
        {
            ucout << utility::conversions::to_string_t(method) 
				<< ": not understood\n";
        }
    }

    return 0;
}

Conclusion

Le C++ REST SDK est facile à mettre en œuvre et permet d’avoir des fonctionnalités serveur sans difficultés particulières. Le support du JSON n’est pas trop pénible. Les opérations sont simples et la performance est au rendez-vous et ceci en standard. L’avantage de la solution proposée c’est qu’elle ne dépend pas de IIS. Le module serveur est stand-alone. Pour finaliser l’application, il faut lui ajouter une couche de service Windows et là, c’est la fête !

Christophe PICHAUD
Consultant sur les technologies Microsoft