Créer un client de Service Web en C++ avec les API WWSAPI (Windows Web Services API)

 

Dans cet article, je me propose de vous montrer, comment utiliser WWSAPI dans une application Win32 , pour consommer les services de traduction automatique de  Microsoft Translator

L’architecture de notre application sera constituée de deux projets:

· Un projet Traducteur, de type bibliothèque de classe qui utilise et implémente les API Microsoft Translator, via les nouvelles API de Windows 7 WSSAPI (Windows Web Service API) et qui masquera au client, l’implémentation aux appels du service Microsoft Translator.

· Un projet de type Win32 ClientTraducteur, le client qui consommera notre bibliothèque Traducteur.

Prérequis

Pour réaliser les exemples de cet article il vous faut :

· Visual Studio 2010 C++ Express

· Kit de développement Windows 7

· Créer une clé Microsoft Translator

Microsoft Translator

Microsoft Translator est un outil qui permet de traduire et de synthétiser à la volée du texte dans différent langages.

Plusieurs interfaces sont disponibles, comme AJAX, HTTP, ou SOAP. Dans notre exemple, nous utiliserons l’interface SOAP, qui est la plus naturelle pour une application Windows développée avec Visual Studio 2010 C++Express, si vous souhaitez en savoir plus n’hésitez pas à aller à l’adresse https://msdn.microsoft.com/en-us/library/ff512435.aspx

Pour manipuler Microsoft Translator, il faut obtenir une clé (gratuite) à cette adresse
https://www.bing.com/developers/createapp.aspx que nous réutiliserons avec les API Microsoft Translator par la suite.

Rappel sur les Services Web

Les services Web, sont un moyen simple pour que deux systèmes totalement incompatibles au départ, puissent quand même dialoguer entre eux à distance. Ces services Web sont basés sur un protocole standard du W3C SOAP (Simple Object Access Protocol) qui facilite l’envoi et le traitement de messages. Pour consommer un Service Web, il suffit de créer un proxy client, basé sur une description standard du W3C, au format WSDL (Web Service Description Language). Dans notre cas de figure pour Microsoft Translator, nous pouvons obtenir cette description, avec le point d’entrée suivant : https://api.microsofttranslator.com/v2/soap.svc?wsdl

Si vous tapez cette url dans un navigateur, vous obtenez, une description comme illustré sur la figure suivante.

image

Pour les développeurs sur la plate-forme .NET, la création d’un proxy client se fait directement à partir de Visual Studio. Pour les développeurs natifs en C ou C++, il faut soit, passer par le SOAP Toolkit et le générer manuellement, soit comme nous allons le voir dans la section suivante, utiliser de nouveaux outils livrées avec le kit de développement Windows 7 et les nouvelles API WWSAPI pour consommer un service ou pour en créer un.

Pour de plus amples informations sur WWSAPI, n’hésitez pas à visiter la documentation MSDN https://msdn.microsoft.com/en-us/library/dd323309(v=VS.85).aspx

Néanmoins, WWSAPI, permettra aux développeurs natifs d’implémenter :

· Des clients ou des services Web sans adhérence avec la plate-forme .NET.

· Inter-opérer avec les standards des services Web, WCF, Web Services ASP.NET et même d’autres services qui tournent sur des OS autres que Windows.

Création du proxy client Microsoft Translator.

La création d’un proxy client pour consommer un service Web ce fait en deux étapes.

La première étape, consiste à récupérer les métadonnées du service Web. Métadonnées qui seront au format WSDL et XSD. Nous utiliserons l’outil SVCUTIL.EXE de la manière suivante :

svcutil /t:metadata https://api.microsofttranslator.com/v2/soap.svc

A la suite de l’exécution de cette commande, des fichiers avec extensions WDSL et XSD sont créés et prêt à être utilisés dans la seconde étape. Ces fichiers sont les suivants :

· api.microsofttranslator.com.V2.wsdl

· tempuri.org.wsdl

· api.microsofttranslator.com.V2.xsd

· Microsoft.MT.Web.Service.V2.xsd

· schemas.microsoft.com.2003.10.Serialization.Arrays.xsd

· schemas.microsoft.com.2003.10.Serialization.xsd

Je ne rentre pas dans les détails car leur compréhension n’est pas utile dans notre exemple, mais sachez que les fichiers WSDL, contiennent les opérations et les types de messages à envoyés ou à recevoir, alors que les fichiers XSD contiennent la structure même des messages. Si vous souhaitez en savoir plus sur le format WSDL. Le langage WSDL 1.0 (Web Services Description Language)

La seconde étape consiste à créer à partir des fichiers de métadonnées WSDL et XSD, le code source, ainsi que les fichiers d’entêtes du proxy client que nous utiliserons dans la section suivante. Pour cela on utilise l’outil WSUTIL de la manière suivante :

wsutil.exe *.wsdl *.xsd.

On considère ici que tous les fichiers wsdl et xsd sont dans le répertoire courant. Vous retrouverez d’ailleurs tous ces fichiers dans le répertoire proxy client, de notre solution, livré avec le code source de cet article.

Les fichiers crées sont les suivants :

· api.microsofttranslator.com.V2.wsdl.h

· api.microsofttranslator.com.V2.xsd.h

· Microsoft.MT.Web.Service.V2.xsd.h

· schemas.microsoft.com.2003.10.Serialization.Arrays.xsd.h

· schemas.microsoft.com.2003.10.Serialization.xsd.h

· tempuri.org.wsdl.h

· api.microsofttranslator.com.V2.wsdl.c

· api.microsofttranslator.com.V2.xsd.c

· Microsoft.MT.Web.Service.V2.xsd.c

· schemas.microsoft.com.2003.10.Serialization.Arrays.xsd.c

· schemas.microsoft.com.2003.10.Serialization.xsd.c

· tempuri.org.wsdl.c

Remarque :

Vous trouverez les outils svcutil et wsutil dans le répertoire C:\Program Files\Microsoft SDKs\Windows\v7.0\Bin.

Les deux opérations (méthodes) du Service Microsoft Translator que nous voulons utiliser sont Translate et Speak. Leurs définitions se trouvant dans les deux fichiers WSDL. Néanmoins, l’outil WSUTIL, a transformé leurs noms en BasicHttpBinding_LanguageService_Translate et BasicHttpBinding_LanguageService_Speak. Gardez-les à l’esprit, car c’est ceux-là que nous utiliserons dans la suite de notre article

Création de la librairie d’appel à Microsoft Translator.

Maintenant nous allons créer un nouveau projet de type bibliothèque de classe, nommé Traducteur, qui fera le pont entre les interfaces SOAP de Microsoft Translator et notre client Windows. Nous pourrions bien évidement éviter cette étape, et faire appel directement dans notre client à Microsoft Translator. Mais en règle générale, il est toujours préférable de découper son application en couche, afin d’éviter les adhérences entre les différents niveaux d’une application et arriver à des applications monolithiques, difficiles à maintenir. C’est également utile si vous souhaitez la réutiliser dans un autre projet.

Dans cette bibliothèque de classe, nous implémenterons les méthodes Traduire et Synthetiser qui ne feront rien de particulier, si ce n’est que d’appeler les méthodes correspondantes du service Microsoft Translator. Nous notifierons le client par l’intermédiaire d’une méthode de rappel SurOperationTerminee, que les demandes de traductions ou de synthèses ont abouti ou ont échoué.

Création de la librairie Traducteur
  1. Lancez Visual Studio 2010 C++ Express
  2. Choisissez le modèle Projet Win32 et nommé-le Traducteur

image

3 Choisir comme type d’application DLL, et cochez la case Exporter des symboles

 

image

4 La structure vide de la classe CTraducteur est créée comme illustré sur la figure suivante :

image

5 Avant de poursuivre, vous allez ajouter dans la solution générée, tous les fichiers d’entêtes et tous les fichiers sources « c », générés à l’étape création du proxy, comme illustré sur la figure suivante :

image

Pour éviter les problèmes avec les entêtes précompilés, sélectionnez tous les fichiers extensions « c », cliquez sur le bouton droit de la souris, et choisissez dans les options avancées de C++, l’option /TP.

Le proxy étant intégré à notre librairie, nous allons maintenant étape par étape ajouter le code qui permettra d’interagir avec notre service de traduction et de synthèse vocale.

Implémentation des API Microsoft Translator

Dans la suite de notre démonstration, nous allons donc implémenter toute la mécanique de connexion au service.

Pour de plus amples informations sur WWSAPI https://msdn.microsoft.com/en-us/library/dd323309(v=VS.85).aspx

Pour se connecter au service Microsoft Translator, à l’aide des API WWSAPI, il faut :

1. Créer un objet proxy client qui prend en charge la communication. Ce proxy a pour rôle de fournir :

a. Le canal de communication. HTTP, TCP, UDP ou autre. Dans notre exemple, nous utiliserons le canal HTTP : WS_HTTP_CHANNEL_BINDING

b. Les caractéristiques de bases du canal de communication. Comme nous utilisons le canal HTTP, le type sera de type WS_CHANNEL_TYPE_REQUEST, c’est-à-dire qu’après l’envoi d’une demande il y aura forcément une réponse.

c. Et un certain nombre de propriétés optionnelles, concernant la sécurité, le protocole SOAP lui-même (version, entête, enveloppe, etc..), ou le format du message.

2. WWSAPI fournit l’API WsCreateServiceProxy pour créer manuellement un objet proxy, mais l’outil WSUTIL, nous simplifie grandement le travail, en créant la méthode helper BasicHttpBinding_LanguageService_CreateServiceProxy qui nous mâchera le travail. Notez le nom de la méthode qui indique par défaut, le type de canal qui sera utilisé.

3. Ensuite il faut ouvrir le proxy à l’aide de WsOpenServiceProxy en lui passant comme paramètre, un point de terminaison (EndPoint). Dans notre cas https://api.microsofttranslator.com/v2/soap.svc

4. Enfin il suffit, d’appeler les méthodes du service Web, BasicHttpBinding_LanguageService_Translate et BasicHttpBinding_LanguageService_Speak sur ce proxy.

Les différentes APIs mentionnées ci-dessus, utilisent pour leur fonctionnement interne, des pointeurs d’objets particuliers qu’il est important de créer. Ces objets sont :

· WS_ERROR (WsCreateError), qui permet à WWSAPI d’enregistrer des informations lorsqu’une erreur survient

· WS_HEAP (WsCreateHeap), qui est un pointeur sur une allocation mémoire qui contiendra les messages. La taille de l’allocation mémoire sera importante en fonction du message passé et du retour attendu. Une taille insuffisante, déclenchera le message, « Un quota a été dépassé »

Remarque : En règle générale, les méthodes, les structures, les objets et les constantes de WWSAPI, commencent toutes par le préfix WS

1. Dans l’entête du fichier Traducteur.h, ajoutez les entêtes suivantes :
#include <tchar.h>
#include <intsafe.h>
#include <stdlib.h>
#include <WebServices.h>
#include "api.microsofttranslator.com.V2.xsd.h"
#include "tempuri.org.wsdl.h"
Les définitions des API WWSAPI se trouvent dans l’entête WebServices.h, et leurs implémentations dans la librairie WebServices.lib, qu’il ne faudra pas oublier d’ajouter à la phase de lien.
La définition de nos méthodes et de nos messages d’appels aux services Microsoft Translator se trouvant dans les deux fichiers tempuri.org.wsdl.h et api.microsofttratnslator.com.v2.xsd.h, comme vous pouvez vous en douter.

2. Toujours dans le fichier d’entête, vous allez ajouter, la définition de la structure suivante :
struct TRADUCTEUR_API UserState
{
public :
BOOL IsSynthetiser;
WCHAR *Resultat;
}state_context;
Cette structure n’est à proprement parlé pas obligatoire, mais elle nous sera utile par la suite car elle sera utilisée par le contexte asynchrone WS_ASYNC_CONTEXT, pour fournir des informations au client. Nous parlerons plus tard du mode asynchrone de WWSAPI, mais sachez qu’un mode synchrone est possible bien évidement, mais qu’il est préférable d’exécuter les opérations en mode asynchrone, afin de ne pas figer l’interface utilisateur, car les temps de latences sur le web sont malheureusement indéterminés.
Cette structure à deux membres, un drapeau IsSynthetiser qui indiquera si l’opération est une demande de synthèse vocale, ou de traduction, et un pointeur *Resultat, contenant soit la traduction du texte, soit le flux de la synthèse vocale. Cette structure n’est pas figée en l’état et peut posséder n’importe quel membre, car comme nous le verrons par la suite, WS_ASYNC_CONTEXT défini un void* pour cette variable.

3. Dans la déclaration de la classe CTraducteur ajoutez les déclarations suivantes :
class TRADUCTEUR_API CTraducteur {
private :
WS_ERROR* _wsError;
WS_HEAP* _wsHeap;
WS_SERVICE_PROXY* _wsServiceProxy;
WCHAR _APPID[78];
HRESULT InitialiserProxyWeb();
void LibererRessourcesServiceWeb();
public:
CTraducteur(void);
~CTraducteur();
HRESULT Traduire(WCHAR *texte,WCHAR *de,WCHAR *vers, WS_ASYNC_CALLBACK cb);
HRESULT Synthetiser(WCHAR *texte,WCHAR *langage,WS_ASYNC_CALLBACK cb);
};

4. Nous allons maintenant implémenter la méthode d’initialisation du proxy web, InitialiserProxyWeb()
Dans cette méthode, vous allez :

a. Créer l’objet WS_ERROR
hr=WsCreateError (NULL,0,&_wsError)
Dans le source code fourni avec cet article vous retrouverez la méthode AfficherWSError qui vous montre la manière d’utiliser cet objet.

b. Allouer de la mémoire pour les messages entrant et sortant
hr=WsCreateHeap (SIZE_T_MAX,0,NULL,0,&_wsHeap,_wsError)
l’argument SIZE_T_MAX, supprime toutes les restrictions propre à la quantité de mémoire à allouer. Cette méthode prend comme paramètre l’adresse de l’objet WS_HEAP, défini dans l’entête.
Notez que nous passons à la méthode l’objet WS_ERROR et que l’objet WS_HEAP sera utilisé plus tard, lors des appels aux méthodes de traduction et de synthèse vocale.

c. Créer le proxy client.
WS_HTTP_BINDING_TEMPLATE templateValue={};
hr=BasicHttpBinding_LanguageService_CreateServiceProxy (
&templateValue,
NULL,0,
&_wsServiceProxy,
_wsError);
Comme je vous l’indiquai
s plus haut, pour nous faciliter la vie, nous utiliserons le helper BasicHttpBinding_LanguageService_CreateServiceProxy (qui en interne appel la méthode WsCreateServiceProxyFromTemplate) et prend en paramètre un modèle de type WS_HTTP_BINDING_TEMPLATE, ainsi que l’adresse de l’objet WS_SERVICE_PROXY

d. Une fois l’objet proxy alloué, il suffit de l’ouvrir, en lui passant l’adresse du service Web, ainsi que l’objet proxy.
WS_ENDPOINT_ADDRESS address={};
WS_STRING Url=WS_STRING_VALUE(L"https://api.microsofttranslator.com/v2/soap.svc");
address.url=Url;
hr=WsOpenServiceProxy (_wsServiceProxy ,&address,NULL,_wsError);
Enfin vous devez copier dans la variable _APPID, la clé que vous avez obtenue plus haut.
wcscpy_s (_APPID,_countof(_APPID),L"<VOTRE CLE MICROSOFT TRANSLATOR>")
Cette clé sera utilisée par les méthodes d’appels au service Web.

Remarque : L’implémentation complète de cette méthode et des autres méthodes d’ailleurs, ce trouvent dans le code source associé à cet article

5. Ajoutez le code suivant pour la libération des ressources
void CTraducteur::LibererRessourcesServiceWeb ()
{
if (_wsError!=NULL)
WsFreeError (_wsError);
if (_wsHeap!=NULL)
WsFreeHeap (_wsHeap);
if (_wsServiceProxy !=NULL)
{
WsFreeServiceProxy (_wsServiceProxy);
}
}
Pour le constructeur ajoutez le code suivant :
CTraducteur::CTraducteur()
{
_wsError =NULL;
_wsHeap =NULL;
_wsServiceProxy =NULL;
InitialiserProxyWeb();
}

6. Pour le destructeur ajoutez le code suivant
CTraducteur::~CTraducteur()
{
if (_wsServiceProxy!=NULL)
WsCloseServiceProxy (_wsServiceProxy,NULL,_wsError);
LibererRessourcesServiceWeb();
}

7. Implémentation de la méthode Traduire :
HRESULT Traduire(WCHAR *texte,WCHAR *de,WCHAR *vers, WS_ASYNC_CALLBACK cb);

Dans un 1er temps, vous allez définir le contexte du mode asynchrone
WS_ASYNC_CONTEXT asyncContext;
asyncContext.callback =cb;
UserState *state=new UserState();
state->IsSynthetiser =FALSE;
asyncContext.callbackState =state;

Ce contexte est très simple, il prend en paramètre un pointeur de fonction de type WS_ASYNC_CALLBACK. Ce pointeur sera passé par le client Win32 à notre méthode.
Le second paramètre est l’état que nous souhaitons retourner au client. Dans notre exemple, nous avons défini la structure UserState, que nous affectons au membre callbackstate qui est de type void*, qui accepte donc tout et n’importe quoi.
L’instance de la structure UserState sera retournée au client par l’intermédiaire de la méthode de rappel WS_ASYNC_CALLBACK, sous forme d’un void* , comme il est stipulé dans la déclaration de WS_ASYNC_CALLBACK de la documentation MSDN.

void WS_ASYNC_CALLBACK(
__in HRESULT
errorCode,
__in WS_CALLBACK_MODEL
callbackModel,
__in void *
callbackState
);
Gardez à l’esprit cette déclaration, car nous la réutiliserons dans le client Win32 pour l’implémentation de la méthode de rappel.

a. Enfin nous appelons la méthode de traduction
hr=BasicHttpBinding_LanguageService_Translate(_wsServiceProxy,
_APPID,
texte,
de,
vers,
&state->Resultat ,
_wsHeap,
NULL,
0,
&asyncContext,
_wsError);

Qui prend en autre comme paramètres, notre proxy, la clé, le texte à traduire, le langage du texte à traduire, le langage de destination, un pointeur (sur la donnée membre Resultat), qui recevra le résultat, l’objet heap pour l’allocation mémoire, et notre contexte de synchronisation pour pouvoir rappeler le client avec le résultat obtenu.

b. L’implémentation de la méthode Synthetiser est relativement identique.
HRESULT Synthetiser(WCHAR *texte,WCHAR *langage,WS_ASYNC_CALLBACK cb);
WS_ASYNC_CONTEXT asyncContext;
asyncContext.callback =cb;
UserState *state=new UserState();
state->IsSynthetiser =TRUE;
asyncContext.callbackState =state
A l’exception près que cette fois-ci l’état IsSynthetiser a retourner au client sera à TRUE, et que la méthode du service Web est la suivante :
hr=BasicHttpBinding_LanguageService_Speak (_wsServiceProxy,
_APPID,
texte,
langage,
L"audio/wav",
&state->Resultat,
_wsHeap,
NULL,
0,
&asyncContext,
_wsError);

Les paramètres de la méthode sont légèrement différent par rapport à ceux pour la traduction, il faut passer la clé bien sûr, le texte à synthétiser, le langage qui sera utilisé, et le format qui sera utilisé pour le flux audio. Les autres paramètres sont identiques, y compris le contexte asynchrone.

Voilà notre bibliothèque est désormais prête à l’emploie, nous allons maintenant créer le client qui la consommera.

Création du client Win32

Nous allons créer un client très simple, qui aura pour interface graphique :

· Deux fenêtres de type Edit , l’une pour le texte à traduire, l’autre pour le texte traduit. Cette dernière servira également comme texte de synthèse vocale.

· Deux boutons l’un pour lancer la traduction, l’autre pour la synthèse vocale.

Deux combobox, ou nous pourrons choisir le langage source et destination.
Un exemple de ce que pourrait être l’interface graphique de notre client

image

1. Ajoutez un nouveau projet de type Projet Win32 | Application Windows à la solution existante

2. Appelez-le Projet ClientTraducteur.

3. Dans l’entête ClientTraducteur.h, ajoutez les déclarations suivantes :
#include <WebServices.h>
#include <Windows.h>
#include <stdlib.h>
#include "resource.h"
#define IDC_BUTTON_TRADUIRE 8001
#define IDC_BUTTON_SYNTHETISER 8002
#define WM_TEXTE_TRADUIT WM_USER + 0x8001
HWND _hEditAtraduire;
HWND _hEditTraduit;
HWND _hCboLangageSrc;
HWND _hCboLangageDest;
HWND _hParent;
WCHAR *_resultatTraduction=NULL;
CMediaPlayer *_player=NULL;
void CreerInterface(HWND);
void Traduire();
void Synthetiser();
void JouerSon(LPWSTR resultat);
void CALLBACK SurOperationTerminee (HRESULT hr,
WS_CALLBACK_MODEL cbModel,
void* cbState);
void AfficherMessageErreur(HRESULT);

Vous noterez que l’on a ajouté l’entête WebServices.h, car il nous sera utile pour la déclaration de la méthode de rappel SurOperationTerminee qui à dans sa signature un le type WS_CALLBACK_MODEL
Les constantes IDC_BUTTON_TRADUIRE et IDC_BUTTON_SYNTHETISER seront utilisées pour réagir aux évènements clic des deux boutons. Evènements qui seront interceptés dans la procédure Windows WndProc de notre client.
La constante WM_TEXTE_TRADUIT est un message utilisateur, que nous utiliserons pour notifier l’application qu’une nouvelle traduction est disponible et pour synchroniser la méthode de rappelle avec le thread principal.
Ensuite nous déclarons des handles de fenêtres globaux HWND que nous utiliserons pour dialoguer avec nos différentes fenêtres, afin de récupérer le texte saisie ou pour y ajouter du texte. Le pointeur *_player de type CMediaPlayer, comme vous vous en doutez sera utiliser pour jouer la synthèse vocale.
Enfin nous déclarons les méthodes CreerInterface pour créer notre interface graphique, les méthodes Traduire et Synthetiser qui feront le pont avec notre bibliothèque, la méthode AfficherMessageErreur pour afficher les messages d’erreurs, la méthode JouerSon, et enfin la méthode de rappel SurOperationTerminee, que nous passerons à notre bibliothèque, pour qu’elle rappelle le client lorsque l’exécution d’une opération asynchrone est terminée.
Passons maintenant à l’implémentation.

4. Ajoutons la méthode de création de l’interface :
void CreerInterface(HWND hParent)
{
HWND hButton=CreateWindow(L"Button",L"Traduire",
WS_CHILD | WS_VISIBLE,
10, 300, 120, 50, hParent,(HMENU)IDC_BUTTON_TRADUIRE,hInst,NULL);
hButton=CreateWindow(L"Button",L"Synthétiser",
WS_CHILD | WS_VISIBLE,
520, 300, 120, 50, hParent,(HMENU)IDC_BUTTON_SYNTHETISER,hInst,NULL);
_hEditAtraduire=CreateWindow(L"EDIT",L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE ,
10, 10, 500, 250,
hParent,NULL,hInst,NULL);
_hEditTraduit=CreateWindow(L"EDIT",L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE ,
520, 10, 500, 250,
hParent,NULL,hInst,NULL);
_hCboLangageSrc =CreateWindow(L"COMBOBOX",L"",
WS_CHILD | WS_VISIBLE | WS_BORDER |
CBS_DROPDOWNLIST | CBS_SORT ,
10, 270, 50, 250,hParent,NULL,hInst,NULL); _hCboLangageDest =CreateWindow(L"COMBOBOX",L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | CBS_DROPDOWNLIST | CBS_SORT ,520, 270, 50, 250, hParent,NULL,hInst,NULL);
ComboBox_AddString (_hCboLangageSrc,L"fr");
ComboBox_AddString (_hCboLangageSrc,L"en");
ComboBox_SelectItemData (_hCboLangageSrc ,0,L"fr");
ComboBox_AddString (_hCboLangageDest,L"fr");
ComboBox_AddString (_hCboLangageDest,L"en");
ComboBox_SelectItemData (_hCboLangageDest ,0,L"en");
}
Rien de bien méchant ici, nous créons nos deux boutons en n’oubliant pas de passer en paramètre nos deux constantes IDC_BUTTON_SYNTHETISER ET IDC_BUTTON_TRADUIRE. Ensuite nous utilisons la macro ComboBox_AddString, pour ajouter les langages sources et destination à nos combo box.
Remarque : Vous noterez ici que nous utilisons par défaut, les langages Français et Anglais. Sachez que vous pouvez obtenir par code les différents langages supportés pour l’une ou l’autre des opérations. En effet Microsoft Translator fournit à ce sujet les méthodes GetLanguagesForTranslate et GetLanguagesForSpeak, que vous pouvez utiliser afin d’éviter de coder en dur ces deux paramètres.

5. Dans la procédure WndProc, au niveau des messages WM_PAINT et WM_DESTROY, ajoutez le code suivant :

case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
CreerInterface(hWnd);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
if (_player!=NULL)
{
_player->Shutdown ();
_player->Release();
}

6. Toujours dans la procédure WndProc, ajoutez le code suivant :
switch (message)
{
case WM_TEXTE_TRADUIT:
SetWindowText (_hEditTraduit , (LPWSTR)_resultatTraduction);
free(_resultatTraduction) ;
break;
//Code omis pour plus de clarté
Ici nous interceptons le message WM_TEXTE_TRADUIT, afin d’afficher le texte traduit.

7. Ensuite ajoutez-y le code d’appel au méthode Traduire et Synthetiser lorsque l’utilisateur clic sur les boutons
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case IDC_BUTTON_SYNTHETISER :
Synthetiser();
break;
case IDC_BUTTON_TRADUIRE :
Traduire();
break;

8. Dans la méthode InitInstance, ajoutez le code suivant :
//Code omis pour plus de clarté
_hParent=hWnd;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
On sauvegarde le handle de la fenêtre parent dans la variable _hParent, que nous réutiliserons dans la méthode de rappel SurOperationTerminee pour notifier la fenêtre principale.

9. Implémentons maintenant les deux méthodes Traduire et Synthetiser
void Traduire()
{
_resultatTraduction =(WCHAR*)malloc(4096);
CTraducteur traducteur;
WCHAR de[4];
WCHAR vers[4];
int size=GetWindowTextLength (_hEditAtraduire);
size+=1;
size*=2;
LPWSTR texte=(LPWSTR)_malloca(size);
GetWindowText (_hEditAtraduire,texte,size);
int cb=ComboBox_GetText(_hCboLangageSrc,de,_countof(de));
cb=ComboBox_GetText(_hCboLangageDest,vers,_countof(vers));
HRESULT hr=traducteur.Traduire (texte,de,vers,(WS_ASYNC_CALLBACK)SurOperationTerminee);
if (FAILED(hr))
{
AfficherMessageErreur(hr);
}
_freea(texte);
}
void Synthetiser()
{
CTraducteur traducteur;
WCHAR langage[4];
int size=GetWindowTextLength (_hEditTraduit);
size+=1;
size*=2;
LPWSTR texte=(LPWSTR)_malloca(size);
GetWindowText (_hEditTraduit ,texte,size);
int cb=ComboBox_GetText(_hCboLangageDest,langage,_countof(langage)); HRESULT hr=traducteur.Synthetiser (texte,langage,(WS_ASYNC_CALLBACK)SurOperationTerminee);
if (FAILED(hr))
{
AfficherMessageErreur(hr);
}
_freea(texte);
}
On notera ici que ces deux méthodes sont plus ou moins identique, ou nous :

a. Retrouvons la taille du texte à traduire ou à synthétiser (GetWindowTextLengh)

b. Allouons dynamiquement la mémoire nécessaire sur la pile (_malloca)

c. Récupérons le texte à traduire ou à synthétiser dans les fenêtres d’édition (GetWindowText)

d. Récupérons les langages source, et destination à l’aide de la macro Combobox_GetText dans les combobox

e. Appelons les méthodes de notre librairie Traduire et Synthetiser, en n’oubliant pas de passer un pointeur de fonction de type WS_ASYNC_CALLBACK, pour que la librairie nous rappelle sur la méthode SurOperationTerminee.

f. Enfin nous libérons la ressource texte (_freea)

10. La méthode de rappelle SurOperationTerminee s’implémente de la manière suivante :
void CALLBACK SurOperationTerminee (HRESULT hr, WS_CALLBACK_MODEL cbModel, void* cbState)
{
UserState *state=(UserState*)cbState;
if (FAILED(hr))
{
AfficherMessageErreur(hr);
}
else
{
if (state->IsSynthetiser )
{
JouerSon((LPWSTR)state->Resultat);
}
else
{
SIZE_T size=wcslen(state->Resultat)*sizeof(state->Resultat[0]);
size+=1;
size*=2;
_resultatTraduction =(WCHAR*)malloc(size);
wcscpy_s(_resultatTraduction,size,state->Resultat);
PostMessage (_hParent ,WM_TEXTE_TRADUIT, 0,0);
}
}
}
Si une erreur est détectée nous affichons le message d’erreur associé à la variable HRESULT. Notez qu’ici, nous pouvons obtenir plus d’informations sur l’erreur en analysant l’objet WS_ERROR de notre librairie. Vous retrouverez dans le code associé à cet article la méthode AfficherWSError, qui vous en montre un exemple.
Si la variable IsSynthetiser est à faux nous postons un message à la fenêtre parent afin d’afficher la traduction. Notez que nous utilisons l’API Win32, PostMessage() qui poste le message, puis continue son chemin, ce qui permet :

o A la méthode de rappel, de se finir proprement

o Et de synchroniser le message avec le thread principal. En effet, n’oubliez pas que la méthode de rappel SurOperationTerminee, sera rappelée par notre librairie, dans un thread différent de celui qui a créé les fenêtres d’édition. Or avec Windows, il est impossible d’accéder à ces objets autrement que dans le thread qui les a créés, soit le thread principal. Si vous utilisez SendMessage à la place, au mieux l’application ne répond plus.

Si la variable IsSynthetiser est à vraie, c’est que le flux arrivant est un flux audio, que nous devons jouer à l’aide de la méthode JouerSon() en lui passant en paramètre le flux entrant.

11. Pour jouer un son sous Windows, pas la peine de réinventer la roue. Il existe déjà un exemple dans le SDK de Windows 7, à cette adresse, C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\multimedia\audio\DuckingMediaPlayer qui implémente la gestion audio.
Prenez les fichiers MediaPlayer.cpp et MediaPlayer.h, que vous ajoutez à votre projet. puis implémentez la méthode JouerSon() de la manière suivante.
void JouerSon(LPWSTR resultat)
{
HRESULT hr;
if (_player ==NULL)
{
CoInitialize (NULL);
_player=new CMediaPlayer (_hParent);
}
hr=_player->Initialize ();
if (FAILED(hr))
{
AfficherMessageErreur(hr);
return;
}
_player->SetFileName (resultat);
_player->Play ();
}

12. En dernier implémentons la méthode qui affiche les erreurs
void AfficherMessageErreur(HRESULT hr)
{
HLOCAL pBuffer;
DWORD ret = FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM , hInst,
hr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&pBuffer,
100,
NULL);
MessageBox (0,(LPTSTR)pBuffer,L"Erreur",0);
}

13. Compilez l’application, exécutez-la et à vous de jouer.

Conclusion :

Dans cet article, nous n’avons fait qu’aborder une petite partie de Microsoft Translator, mais il existe pléthore d’API Microsoft Translator qui vous permettrons d’enrichir votre application Windows.

Par exemple :

· Il est possible de détecter automatiquement le langage en fonction d’un texte,

· De récupérer toutes les translations possibles en fonction d’un Texte. Différent de la méthode Translate que nous avons utilisé, car elle ne renvoie qu’un seule résultat.

· Et d’autres, que je vous laisse découvrir https://msdn.microsoft.com/en-us/library/ff512435.aspx

 

Eric Vernié