Simuler des évènements de touche sur Android

Dans cet article nous allons voir comment simuler des évènements de touche sur Android dans une application FireMonkey codée en C++. Pour faire cela nous allons utiliser la classe Instrumentation de l’API Android. Malheureusement cette classe n’est pas exposée dans la RTL. La bonne nouvelle est qu’il existe un outil pour générer les fichiers qui nous sont nécessaires. C’est à l’aide de Java2OP.exe que nous allons y arriver. Cette application par ligne de commande sert à générer des fichiers Delphi à partir de bibliothèques Java, d’où le nom Java To Object Pascal (Java2OP).

Donc avec cette commande nous obtiendrons le fichier Androidapi.Instrumentation.pas :

Java2OP.exe -classes android.app.Instrumentation -unit Androidapi.Instrumentation

La première fois que j’ai exécuté la commande, une erreur est survenue me disant que JDK était manquant. En cas de problème, je vous conseille de vous référer à l’aide où il y a une section Dépannage.

Pour commencer, on doit démarrer RAD Studio et créer un nouveau projet multi-périphériques C++Builder. Nous allons insérer un TMemo dans la Form. C’est dans ce contrôle que les touches de clavier simulées apparaitront. On doit bien sûr ajouter le fichier Androidapi.Instrumentation.pas au projet. À la compilation le fichier Androidapi.Instrumentation.hpp sera créé.

La simulation de touche de frappe doit se faire dans un thread, donc il faut aller dans le menu File / New / Other… et choisir Thread Object dans la section C++Builder Files. Vous pouvez donner le nom TInjectInput à la classe. Vous pouvez enregistrer le fichier et le nommer InjectInput.cpp. Dans ce fichier, il faut ajouter les en-têtes suivantes:

#include <Androidapi.JNI.GraphicsContentViewText.hpp>
#include "Androidapi.Instrumentation.hpp" // Fichier généré par Java2OP.exe

Dans l’évènement Execute on ajoute le code suivant:

    while(Terminated == false)
    {
        try
        {
            _di_JInstrumentation Instrumentation = TJInstrumentation::JavaClass->init();

            Graphicscontentviewtext::_di_JKeyEvent KeyEventDown = TJKeyEvent::JavaClass->init(
                TJKeyEvent::JavaClass->ACTION_DOWN, TJKeyEvent::JavaClass->KEYCODE_A);
            Instrumentation->sendKeySync(KeyEventDown);

            Graphicscontentviewtext::_di_JKeyEvent KeyEventUp = TJKeyEvent::JavaClass->init(
                TJKeyEvent::JavaClass->ACTION_UP, TJKeyEvent::JavaClass->KEYCODE_A);
            Instrumentation->sendKeySync(KeyEventUp);
        }
        catch(const Exception &)
        {
        }

        Sleep(5000);
    }

Cela aura comme effet de générer un événement de frappe avec la touche a enfoncée et ensuite un évènement avec la même touche relâchée. La méthode injectKeyEvent aurait aussi pu être meilleure, mais je voulais utiliser KeyEvent. Il est important de mettre le code dans un try/catch car lorsqu’on quitte l’application l’exception suivante est lancée:

java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission

Il faut savoir que même si la permission INJECT_EVENTS est accordée, l’exception sera quand même lancée si l’application n’est pas signée.

Dans le fichier .h de la Form on ajoute le code suivant:

//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <FMX.Controls.hpp>
#include <FMX.Forms.hpp>
#include <FMX.Controls.Presentation.hpp>
#include <FMX.Memo.hpp>
#include <FMX.ScrollBox.hpp>
#include <FMX.Types.hpp>
#include "InjectInput.h"
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE-managed Components
    TMemo *Memo1;
private:	// User declarations
    TInjectInput* FInjectInput;
public:		// User declarations
    __fastcall TForm1(TComponent* Owner);
    virtual __fastcall ~TForm1();
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

À la ligne 12 on ajoute l’en-tête. À la ligne 19 on ajoute une propriété privée. Finalement à la ligne 22 c’est le destructeur de Form.

Dans le fichier .cpp de la Form on ajoute le code suivant:

//---------------------------------------------------------------------------
#include <fmx.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    FInjectInput = new TInjectInput(true);
    FInjectInput->Start();
}
//---------------------------------------------------------------------------

__fastcall TForm1::~TForm1()
{
    delete FInjectInput;
}
//---------------------------------------------------------------------------

Dans le constructeur on va créer le thread et le démarrer. On ajoute le destructeur pour libérer la mémoire.

Maintenant, il suffit de choisir le Target Android, compiler et déployer le code. On doit cliquer sur le contrôle TMemo et des a devraient apparaître à toutes les 5 secondes.

Traduction avec l’API MyMemory et CCAN/JSON

Dans cet article nous allons voir comment traduire du texte avec l’API web MyMemory. Je voulais utiliser celui de Google, mais il est payant. MyMemory n’a pas la même qualité de traduction que Google, mais le but est de faire une démonstration et non un produit commercial.

Pour analyser la réponse en JSON, au lieu d’utiliser la classe TJSONObject de la RTL, c’est la bibliothèque CCAN/JSON qui sera utilisée. Cette bibliothèque à code source ouvert est codée en C. Je l’ai choisie car dans une étude comparative sur 41 bibliothèques JSON elle semblait bien classée.

JSON Parsing Time
Temps d’analyse des bibliothèques JSON

J’ai dû exclure toutes celles en C++11, car j’utilise encore principalement le compilateur bcc32. Le facteur principal était simplement la facilité d’intégration. Si ça ne compilait pas dans les minutes suivant le téléchargement, je passais à la prochaine dans la liste.

La première étape est de créer un nouveau projet FireMonkey. Dans la Form il faut insérer un TRESTClient, un TRESTRequest, un TRESTResponse, trois contrôles TEdit, un TComboBox et un TButton.Vous pouvez donner comme texte à votre bouton le mot « Traduire ».

Voici ce à quoi l’interface devrait ressembler:

Il faut ajouter le fichier json.c qui se trouve trouve dans le dossier \ccan\json au projet pour qu’il soit compilé. Je n’ai pas eu de problème pour l’utiliser avec les plateformes Win32, Win64 et OS X.

Dans votre fichier cpp voici l’en-tête à utiliser.

extern "C"
{
    #include "json.h"
}

Il ne faut pas oublier qu’il s’agit de code en C et non C++, c’est pourquoi il faut ajouter un peu plus de code.

Voici le code à ajouter dans votre constructeur pour initialiser les contrôles:

    TListBoxItem* ListItems;

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Anglais";
    ListItems->TagString = L"en";

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Chinois (simplifié)";
    ListItems->TagString = L"zh-CN";

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Russe";
    ListItems->TagString = L"ru";

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Latin";
    ListItems->TagString = L"la";

    // On sélectionne le premier item par défaut
    ComboBox1->ItemIndex = 0;

    // Message à afficher quand la propriété Text est vide
    Edit1->TextPrompt = L"Texte à traduire";

    // Le nombre de match par défaut est mis à zéro
    Edit3->Text = L"0";

    // On n'écrit pas dans les champs de résultat
    Edit2->ReadOnly = true;
    Edit3->ReadOnly = true;

Voici le code à ajouter dans l’évènement OnClick du bouton:

    if(Edit1->Text.IsEmpty() == true || ComboBox1->ItemIndex < 0)
    {
        Edit2->Text = "";
        Edit3->Text = "0";
        return;
    }

    String LQuery = Edit1->Text;
    String LTarget = ComboBox1->ListItems[ComboBox1->ItemIndex]->TagString;

    RESTClient1->BaseURL = "http://api.mymemory.translated.net/get" \
        "?q=" + Edit1->Text +
        "&langpair=fr|" + LTarget;

    RESTRequest1->Execute();

    // Conversion en UTF-8
    TBytes LUTF8Array = TEncoding::UTF8->GetBytes(RESTResponse1->JSONText + "0");
    LUTF8Array[LUTF8Array.High] = '\0'; // Le caractère nul de terminaison est requis

    JsonNode* LRootNode = json_decode((char *)&LUTF8Array[0]);
    if(LRootNode != NULL)
    {
        JsonNode* LResponseNode = json_find_member(LRootNode, "responseData");
        if(LResponseNode != NULL && LResponseNode->tag == JSON_OBJECT)
        {
            JsonNode* LTextNode = json_find_member(LResponseNode, "translatedText");
            if(LTextNode != NULL && LTextNode->tag == JSON_STRING)
            {
                TBytes LUTFAnswer;
                LUTFAnswer.Length = strlen(LTextNode->string_);
                memcpy(&LUTFAnswer[0], LTextNode->string_, LUTFAnswer.Length);
                Edit2->Text = TEncoding::UTF8->GetString(LUTFAnswer);
            }
            JsonNode* LMatchNode = json_find_member(LResponseNode, "match");
            if(LMatchNode != NULL && LMatchNode->tag == JSON_NUMBER)
            {
                Edit3->Text = LMatchNode->number_;
            }
        }
        json_delete(LRootNode);
    }

Le texte utilisé par CCAN/JSON utilise l’encodage UTF8, c’est pourquoi il y a des conversions. Sinon le code ressemble étrangement à ce que RAD Studio nous a habitués avec sa classe JSON.

Maintenant il ne reste qu’à exécuter l’application pour la tester.
Traduction macOS

Déboguer sur CyanogenMod avec RAD Studio

Dans un article précédant, datant de novembre 2014, j’expliquais comment déboguer sur la OUYA avec RAD Studio. Plusieurs choses ont changé depuis ce temps. Le plus important étant la vente de OUYA a Razer et le fait que le matériel cessera d’être supporté. Je croyais bien que ma petite boîte grise allait rester pour toujours à l’intérieur de l’un de mes tiroirs, jusqu’à ce que je tombe sur un article expliquant comment faire rouler CyanogenMod sur ma OUYA. CyanogenMod version 11 (CM11) est basé sur Android KitKat 4.4.x. Donc, présentement ma OUYA fonctionne comme Android 4.4.4. C’est ce que l’on peut voir sur la capture d’écran qui suit.

About tablet
Information sur la OUYA et le système d’exploitation

Ma motivation première pour transformer ma OUYA en appareil Android standard n’était pas nécessairement le développement d’applications. Comme beaucoup de gens, c’était de faire fonctionner Kodi version 16.1 (Jarvis). Le reste de l’article va se concentrer par contre sur ma seconde raison.

Pour utiliser RAD Studio avec CM11, il s’agit de faire les étapes habituelles que l’on ferait sur Android. La première étant de faire afficher la section avec les options pour développeur. Pour faire cela on doit aller dans les Settings dans la section About tablet. Ensuite on clique sept fois sur Build number.

Maintenant que la section Developer options des Settings est disponible, il faut s’assurer que les options Android debugging et ADB over network sont activées:

ADB over network
Activation du débogage par le réseau

L’adresse IP et le numéro de port qui se trouve juste en dessous de l’option l’option ADB over network vous seront utiles bientôt. Il faut d’abord se connecter à la OUYA. Pour cela l’application Android Debug Bridge (adb) située dans le dossier /<sdk>/platform-tools sera utilisée. Dans le SDK Manager pour Android on trouve ce chemin dans la section Adb location. Vous devez aller dans ce dossier et taper la ligne de commande suivante:
adb connect 192.168.1.115:5555
Si tout fonctionne le résultat devrait être: connected to 192.168.1.115:5555

Vous pouvez aussi taper cette commande pour voir la liste des dispositifs connectés:
adb devices
Si la liste est vide, c’est que la commande de connexion à échouer.

Ensuite, ouvrez ou créez un nouveau projet de type Multi-Device Application dans RAD Studio. Sélectionnez le Target Android. Dans la section Target de celui-ci, OUYA devrait apparaitre. Si ce n’est pas le cas il faut faire un Refresh.

OUYA Target
Le Target OUYA est sélectionné

Il est maintenant possible de compiler le fichier APK et de tester l’application directement sur la console. Une fois fermée, elle devrait être disponible avec les autres applications.

Si vous voulez simplement installer l’application, vous pouvez utiliser cette ligne de commande:
adb install "C:\Projets\Application\Android\Release\application\bin\application.apk"

Ping avec TIdIcmpClient

Dans cet article, je vais vous donner le code pour faire une méthode qui effectue un Ping. Nous utiliserons la classe TIdIcmpClient de Indy.

Voici la déclaration de la méthode:

    bool __fastcall Ping(const String AIP, int ATimeout = 5000);

Le paramètre ATimeout est facultatif et sa valeur par défaut est 5 secondes.

Pour utiliser TIdIcmpClient, il faut bien sûr ajouter le fichier d’en-tête suivant à votre fichier cpp:

#include <IdIcmpClient.hpp>

Voici le code de la méthode:

bool __fastcall TForm1::Ping(const String AIP, int ATimeout)
{
    bool Result = false;

    TIdIcmpClient* LIcmpClient = NULL;
    try
    {
        LIcmpClient = new TIdIcmpClient(NULL);
        LIcmpClient->Host = AIP;
        LIcmpClient->Port = 0;
        LIcmpClient->ReceiveTimeout = ATimeout;
        LIcmpClient->IPVersion = TIdIPVersion::Id_IPv4;
        LIcmpClient->Protocol = Id_IPPROTO_ICMP;

        try
        {
            const String LToSend = " !\"#$%&'()*+,-./0123456789:;<=>?";
            LIcmpClient->Ping(LToSend, 0);
            if(LIcmpClient->ReplyStatus->ReplyStatusType == TReplyStatusTypes::rsEcho &&
                LIcmpClient->ReplyStatus->BytesReceived > 0 &&
                LIcmpClient->ReplyData.Pos(LToSend) > 0)
            {
                Result = true;
            }
        }
        catch(...)
        {
        }
    }
    __finally
    {
        delete LIcmpClient;
    }

    return Result;
}

Si tout fonctionne, la méthode retourne true. Ce n’est pas plus compliqué que cela. Par contre, il est important de dire que ce code est bloquant. Donc, pour ne pas geler l’interface utilisateur c’est important de le rouler dans un thread séparé.

Utilisation de TWebBrowser avec JavaScript

Dans cet article, je vais interagir avec du contenu HTML à l’intérieur d’un composant TWebBrowser. Le but est de valider une page web avec l’API de Nu Html Checker (v.Nu) et d’afficher le résultat. On aurait pu utiliser les composants REST inclut dans RAD Studio, mais cette fois-ci, j’ai décidé d’utiliser la bibliothèque jQuery. Donc, il va y avoir plus de code JavaScript que de code C++. Cet article sert à démontrer certaines fonctionnalités de FireMonkey et non d’avoir une application parfaitement codée et prête à mettre sur un magasin en ligne.

Tout d’abord, il faut créer une application multi-périphériques dans laquelle il faut ajouter quelques composants. Un TPanel aligné à Top dans lequel on insère un TEdit pour l’entrée du URL et un TSpeedButton pour déclencher le chargement du résultat. On ajoute un TWebBrowser aligné à Top et un TSplitter lui aussi avec le même alignement. Par la suite, on ajoute un TMemo et on met Align à Client. Il va contenir le code HTML et JavaScript. Pour cette raison, le code suivant sera ajouté à la propriété Lines:

<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js" type="text/javascript"></script>
        <script type="text/javascript">
            function go(urlToValidate) {
                $.ajax({
                    type: "GET",
                    url: 'http://validator.w3.org/nu/?out=json&doc=' + urlToValidate,
                    dataType: "jsonp",
                    cache: false,
                    crossDomain: true,
                    success: function (data) {
                        $("#out").append('<ul>');
                        $.each(data.messages, function() {
                            $.each(this, function(k, v) {
                                if(k == 'message') {
                                    $("#out").append('<li>' + v + '</li>');
                                }
                            });
                        });
                        $("#out").append('</ul>');
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        $("#out").html("Erreur!");
                    }
                });
            }
        </script>
    </head>
    <body>
        <div id="out"></div>
    </body>
</html>

Pour ceux qui on déjà utilisé jQuery, ce code n’a rien d’impressionnant. Il ne fait que faire une requête AJAX de type GET à l’API v.Nu. Si l’appel fonctionne, une liste de message sera alors affichée. Si elle échoue, le mot Erreur sera présenté. La fonction JavaScript go ne semble pas appeler. Effectivement, elle sera invoquée par du code C++.

Alors, nous allons débuter la partie C++ du projet. L’encodage du URL aurait pu se faire en JavaScript mais je voulais tester TURLEncoding. Pour cela, il faut ajouter le fichier d’en-tête suivant

#include <System.NetEncoding.hpp>

Dans le constructeur, on ajoute ce code pour définir l’URL par défaut et charger le code HTML dans le navigateur web.

    Edit1->Text = "http://www.google.com";
    WebBrowser1->LoadFromStrings(Memo1->Text, "");

Dans l’évènement OnClick du bouton on ajoute ceci:

    String LUrl = System::Netencoding::TURLEncoding::URL->Encode(Edit1->Text);
    WebBrowser1->EvaluateJavaScript("go('" + LUrl + "');");

Ici on encode l’URL et on l’utilise comme paramètre lors de l’appel à la fonction go JavaScript.

Si tout fonctionne bien, vous devriez obtenir un résultat similaire à cette capture d’écran:
Nu Html Checker

Afficher une image Gravatar

Dans cet article, nous allons voir comment afficher une image Gravatar dans une application conçue avec C++Builder.

Tout d’abord, il faut créer un projet FireMonkey dans lequel il faut ajouter un composant TEdit, un TButton et un TImage.

Ensuite, il faut ajouter les fichiers d’en-têtes suivants:

#include <System.Hash.hpp>
#include <IPPeerClient.hpp>
#include <REST.Client.hpp>

Par la suite, vous pouvez ajouter ce code dans l’évènement OnClick du bouton:

    String LUrl = Edit1->Text.Trim().LowerCase();
    LUrl= System::Hash::THashMD5::GetHashString(LUrl);
    LUrl = "http://www.gravatar.com/avatar/" + LUrl + "?s=80";

    System::Classes::TMemoryStream* LStream = NULL;
    try
    {
        LStream = new System::Classes::TMemoryStream();
        Rest::Client::TDownloadURL::DownloadRawBytes(LUrl, LStream);
        Image1->Bitmap->LoadFromStream(LStream);
    }
    __finally
    {
        delete LStream;
    }

Le fonctionnement est simple: vous devez entrer une adresse de courrier électronique dans la zone de texte et cliquer sur le bouton par la suite. Le Gravatar correspondant à l’adresse va être téléchargé et affiché dans le composant TImage. S’il n’y a pas d’image liées à l’adresse, alors une image par défaut sera chargée.

Dans l’URL qui correspond à l’image à télécharger, on peut spécifier la grandeur de l’image à l’aide du paramètre s= ou size=. Par défaut, la grandeur est de 80 pixels par 80 pixels si elle n’est pas spécifiée. La grandeur peut être de 1 pixel jusqu’à 2048 pixels.

Je vous laisse avec un exemple de ce que à quoi pourrait ressembler l’application:
Gravatar

Couleur d’un pixel dans un TBitmap

Dans cet article, nous allons voir comment changer la couleur des pixels dans un Fmx::Graphics::TBitmap.

Pour cela, on va utiliser une image PNG qui possède seulement deux couleurs: du blanc et du noir.
Cœur

Tout d’abord, il faut créer un projet FireMonkey dans lequel il faut ajouter un composant TImage.

Ensuite, il faut ajouter le fichier d’en-tête suivant pour utiliser TAlphaColorArray:

#include <FMX.Utils.hpp>

Par la suite, vous pouvez ajouter ce code dans le constructeur de la Form.

    Fmx::Graphics::TBitmap* LBitmap = NULL;
    try
    {
        LBitmap = new Fmx::Graphics::TBitmap("C:\\images\\heart.png");

        TBitmapData LBitmapData;
		if(LBitmap->Map(TMapAccess::ReadWrite, LBitmapData))
        {
			try
            {
                TAlphaColorArray* LColorArray;
                for(int y = 0; y < LBitmapData.Height; ++y)
                {
                    LColorArray = static_cast<TAlphaColorArray*>
                        (LBitmapData.GetScanline(y));
                    for(int x = 0; x < LBitmapData.Width; ++x)
                    {
                        void* LPixel = &LColorArray->data[x];
                        const System::Uitypes::TAlphaColor LColor =
                            PixelToAlphaColor(LPixel, LBitmapData.PixelFormat);
                        if(LColor == TAlphaColorRec::White)
                        {   // Change le blanc en rouge
                            AlphaColorToPixel(TAlphaColorRec::Red,
                                LPixel, LBitmapData.PixelFormat);
                        }
                        else if(LColor == TAlphaColorRec::Black)
                        {   // Change le noir en violet
                            AlphaColorToPixel(TAlphaColorRec::Violet,
                                LPixel, LBitmapData.PixelFormat);
                        }
                    }
                }
			}
			__finally
            {
				LBitmap->Unmap(LBitmapData);
			}
		}

        Image1->Bitmap->Assign(LBitmap);
    }
    __finally
    {
        delete LBitmap;
    }

Voilà, maintenant vous devriez obtenir ceci à l’exécution:
Résultat changement de couleur

MD5 et SHA1 avec l’unité System::Hash

Dans un article précédent, j’avais montré comment calculer un hachage MD5 et SHA1 avec Indy. Je me suis rendu compte cette semaine qu’il existait depuis C++Builder XE8 une manière d’effectuer ces mêmes calculs sans avoir de dépendance sur la bibliothèque de communication Indy.

Tout d’abord, il faut ajouter le fichier d’en-tête suivant:

#include <System.Hash.hpp>

Pour MD5, il faut ce code:

String __fastcall TForm1::GetHashMD5(const String AFileName)
{
    System::Hash::THashMD5 LMd5 = System::Hash::THashMD5::Create();
    System::Classes::TFileStream* LFileStream = NULL;
    try
    {
        LFileStream = new System::Classes::TFileStream(AFileName,
            fmOpenRead | fmShareDenyWrite);
        System::DynamicArray<System::Byte> Buffer;
        Buffer.Length = LFileStream->Size;
        LFileStream->Read(&Buffer[0], Buffer.Length);
        LMd5.Update(Buffer);
    }
    __finally
    {
        delete LFileStream;
    }
    return LMd5.HashAsString().UpperCase();
}

Pour SHA1, on utilise ce code:

String __fastcall TForm1::GetHashSHA1(const String AFileName)
{
    System::Hash::THashSHA1 LSha1 = System::Hash::THashSHA1::Create();
    System::Classes::TFileStream* LFileStream = NULL;
    try
    {
        LFileStream = new System::Classes::TFileStream(AFileName,
            fmOpenRead | fmShareDenyWrite);
        System::DynamicArray<System::Byte> Buffer;
        Buffer.Length = LFileStream->Size;
        LFileStream->Read(&Buffer[0], Buffer.Length);
        LSha1.Update(Buffer);
    }
    __finally
    {
        delete LFileStream;
    }
    return LSha1.HashAsString().UpperCase();
}

Une nouvelle version de l’IDE, C++Builder 10 Seattle, est maintenant disponible. L’unité System::Hash a un nouveau membre. Il s’agit de THashSHA2. Il implémente la famille de fonctions de hachage SHA-2:

    enum DECLSPEC_DENUM TSHA2Version : unsigned char { SHA224, SHA256, SHA384, SHA512, SHA512_224, SHA512_256 };

CRC32 avec Zlib et Boost

Dans un article précédent, j’avais montré comment calculer un CRC32 avec Indy. Maintenant, dans celui-ci, je vais vous montrer comment effectuer le même travail avec Zlib et Boost qui sont aussi inclus avec C++Builder.

Tout d’abord, il faut ajouter les fichiers d’en-têtes:

#include <System.Zlib.hpp>
#include <boost/crc.hpp>

Pour la méthode qui utilise Zlib:

String __fastcall TForm1::GetCRC32Zlib(const String AFileName)
{
    String Result;

    System::Classes::TMemoryStream* LMemoryStream = NULL;
    try
    {
        LMemoryStream = new System::Classes::TMemoryStream();
        LMemoryStream->LoadFromFile(AFileName);
        const unsigned __int64 LVal = System::Zlib::crc32(0,
            static_cast<System::Byte*>(LMemoryStream->Memory),
            LMemoryStream->Size);
        Result = System::Sysutils::IntToHex(LVal, 8);
    }
    __finally
    {
        delete LMemoryStream;
    }

    return Result;
}

Pour la méthode qui utilise Boost:

String __fastcall TForm1::GetCRC32Boost(const String AFileName)
{
    String Result;

    System::Classes::TMemoryStream* LMemoryStream = NULL;
    try
    {
        LMemoryStream = new System::Classes::TMemoryStream();
        LMemoryStream->LoadFromFile(AFileName);
        boost::crc_32_type crc_ccitt;
        crc_ccitt.process_bytes(
            static_cast<System::Byte*>(LMemoryStream->Memory),
            LMemoryStream->Size);
        const unsigned __int64 LVal = crc_ccitt.checksum();
        Result = System::Sysutils::IntToHex(LVal, 8);
    }
    __finally
    {
        delete LMemoryStream;
    }

    return Result;
}

Voilà, vous avez maintenant le choix entre plusieurs méthodes pour la prochaine fois où vous aurez à utiliser un contrôle de redondance cyclique sur un fichier.

Hachage d’un fichier avec Indy

J’aime bien utiliser la bibliothèque Crypto++ pour calculer le hachage d’un fichier. Par contre, des fois il m’arrive de vouloir utiliser cette fonctionnalité sans rien télécharger de plus. Heureusement Indy, qui est inclus dans RAD Studio, nous permet de faire cela facilement.

Dans cet article, on verra comment faire un CRC32, MD5 et SHA1.

En premier, on ajoute les fichiers d’en-têtes:

#include <IdHashCRC.hpp>
#include <IdHashMessageDigest.hpp>
#include <IdHashSHA.hpp>

Pour le CRC32 on ajoute cette méthode:

String __fastcall TForm1::GetHashCRC32(const String AFileName)
{
    String Result;

    TIdHashCRC32* LCrc32 = NULL;
    System::Classes::TFileStream* LFileStream = NULL;
    try
    {
        LCrc32 = new TIdHashCRC32();
        LFileStream = new System::Classes::TFileStream(AFileName,
            fmOpenRead | fmShareDenyWrite);
        const unsigned __int64 LVal = LCrc32->HashValue(LFileStream);
        Result = System::Sysutils::IntToHex(LVal, 8);
    }
    __finally
    {
        delete LFileStream;
        delete LCrc32;
    }

    return Result;
}

Pour le MD5 on ajoute cette méthode:

String __fastcall TForm1::GetHashMD5(const String AFileName)
{
    String Result;

    TIdHashMessageDigest5* LMd5 = NULL;
    System::Classes::TFileStream* LFileStream = NULL;
    try
    {
        LMd5 = new TIdHashMessageDigest5();
        LFileStream = new System::Classes::TFileStream(AFileName,
            fmOpenRead | fmShareDenyWrite);
        Result = LMd5->HashStreamAsHex(LFileStream);
    }
    __finally
    {
        delete LFileStream;
        delete LMd5;
    }

    return Result;
}

Pour le SHA1 on ajoute cette méthode:

String __fastcall TForm1::GetHashSHA1(const String AFileName)
{
    String Result;

    TIdHashSHA1* LSha1 = NULL;
    System::Classes::TFileStream* LFileStream = NULL;
    try
    {
        LSha1 = new TIdHashSHA1();
        LFileStream = new System::Classes::TFileStream(AFileName,
            fmOpenRead | fmShareDenyWrite);
        Result = LSha1->HashStreamAsHex(LFileStream);
    }
    __finally
    {
        delete LFileStream;
        delete LSha1;
    }

    return Result;
}

Il est à noter que Indy supporte aussi ces classes:

  • TIdHashSHA224
  • TIdHashSHA256
  • TIdHashSHA384
  • TIdHashSHA512

Pour tester le code, on peut ajouter un contrôle TMemo et mettre le code suivant dans le constructeur.

    const String LFileName = "C:\\monfichier.xyz"; // Le fichier doit exister
    Memo1->Lines->Add("CRC32: " + GetHashCRC32(LFileName));
    Memo1->Lines->Add("MD5  : " + GetHashMD5(LFileName));
    Memo1->Lines->Add("SHA1 : " + GetHashSHA1(LFileName));

Si on veut être certain que le résultat est valide, on peut vérifier avec un logiciel comme HashTab:
HashTab