Partager via



Septembre 2016

Volume 31, numéro 9

Cet article a fait l'objet d'une traduction automatique.

C ++ - Conversions de l’encodage Unicode avec des chaînes STL et des API Win32

Par Giovanni Dicanio

Unicode est la norme de facto pour représenter le texte international de logiciels modernes. Selon le site Web du consortium Unicode officiel (bit.ly/1Rtdulx), « Unicode fournit un numéro unique pour chaque caractère, quelle que soit la plateforme, quel que soit le programme, quelle que soit la langue. » Chacun de ces numéros uniques est appelé point de code et est généralement représenté avec le préfixe « U + », suivi de nombre forme hexadécimale écrite dans unique. Par exemple, le point de code associé au caractère « C » est U + 0043. Notez que Unicode est une norme industrielle qui couvre la plupart des systèmes d’écriture du monde, y compris les idéogrammes. Ainsi, par exemple, l’IDÉOGRAMME kanji (japonais) 学, ce qui a « formation » et « connaissances » parmi ses significations, est associé pour le point de code U + 5B66. Actuellement, la norme Unicode définit 1,114,000 plus de points de code.

À partir de Points de Code abstraite bits réelle : Encodages UTF-8 et UTF-16.

Un point de code est un concept abstrait, cependant. Pour un programmeur, la question est : Comment ces points de code Unicode sont représentés concrètement à l’aide de bits de l’ordinateur ? La réponse à cette question nous amène directement à la notion d’encodage Unicode. En fait, un encodage Unicode est un moyen bien défini particulier de représenter des valeurs de point de code Unicode en bits. La norme Unicode définit plusieurs codages, mais les plus importants sont UTF-8 et UTF-16, qui sont tous deux des encodages de longueur variable capables de coder tous les possible de caractères « Unicode » ou, mieux, les points de code. Par conséquent, les conversions entre ces deux encodages sont sans perte : Aucun caractère Unicode ne seront perdue au cours du processus.

UTF-8, comme son nom le suggère, utilise des unités de code de 8 bits. Il a été conçu avec deux caractéristiques importantes à l’esprit. Tout d’abord, il est compatible avec le mode ASCII ; Cela signifie que chaque code de caractère ASCII valide a la même valeur d’octet lorsque encodé à l’aide de UTF-8. En d’autres termes, le texte ASCII valide est valide automatiquement texte encodée en UTF-8.

Ensuite, étant donné que du texte Unicode UTF-8 est simplement une séquence d’unités d’octets de 8 bits, il n’existe aucune complication endianness. L’encodage UTF-8 (contrairement au format UTF-16) est indépendant du poids à la conception. Il s’agit d’une fonctionnalité importante lors de l’échange de texte sur différents systèmes informatiques qui peuvent avoir différentes architectures matérielles avec primauté différents.

Si l'on considère les deux caractères Unicode, je l’ai mentionné précédemment, la lettre majuscule C (point de code U + 0043) est encodé en UTF-8 à l’aide de l’octet unique 0 x 43 (43 hexadécimal), qui correspond exactement au code ASCII associé au caractère C (conformément à la compatibilité descendante de UTF-8 avec le code ASCII). En revanche, le japonais IDÉOGRAMME 学 (point de code U + 5B66) est encodé en UTF-8 en tant que la séquence de trois octets 0xE5 0xAD 0xA6.

UTF-8 est Unicode utilisés le plus de codage sur Internet. En fonction des statistiques récentes de W3Techs disponible à l’adresse bit.ly/1UT5EBC, UTF-8 est utilisé par % 87 de tous les sites Web qu’il analyse.

UTF-16 est essentiellement de fait standard de codage utilisé par API compatibles Unicode de Windows. UTF-16 est Unicode « native » codage dans nombreux autres systèmes logiciels, également. Par exemple, Qt, Java et les composants International pour la bibliothèque Unicode (ICU), n’en citer que quelques-unes, utilisent UTF-16 pour stocker les chaînes Unicode de codage.

UTF-16 utilise des unités de code 16 bits. Comme UTF-8, UTF-16 peut coder tous les points de code Unicode possibles. Toutefois, alors que UTF-8 encode chaque point de code Unicode valide à l’aide d’unités d’un à quatre octets de 8 bits, UTF-16 est, de manière plus simple. En fait, les points de code Unicode sont encodés en UTF-16 à l’aide d’une ou deux unités de code 16 bits. Toutefois, avec des unités de code supérieurs à un seul octet implique les complications endianness : en fait, il existe à la fois big-endian UTF-16 et little-endian UTF-16 (bien qu’encodage UTF-8 d’endian-neutral seul).

La norme Unicode définit un concept de plan comme un groupe continu de 65 536 (216) points de code. Le premier plan est identifié en tant que plan 0 ou un plan de base multilingue (BMP). Caractères pour presque tous les langages modernes et de symboles se trouvent dans le BMP, et tous les caractères de ces BMP sont représentées en UTF-16 à l’aide d’une unité de code 16 bits unique.

Les caractères supplémentaires se trouvent dans les plans de la BMP ; elles comprennent des symboles PICTOGRAPHIQUE comme Emoji et historiques scripts comme caractères égyptiennes. Ces caractères extérieurs au BMP supplémentaires sont encodés en UTF-16 à l’aide de deux unités de code 16 bits, également appelés des paires de substitution.

C majuscule (U + 0043) est encodé en UTF-16 comme un 0x0043 unité de code unique de 16 bits. L’IDÉOGRAMME 学 (U + 5B66) est encodé en UTF-16 comme le 0x5B66 unité de code 16 bits unique. Pour de nombreux caractères Unicode, il existe une correspondance d’immédiate et directe entre leur représentation « abstract » du point de code (par exemple, U + 5B66) et de leur codage UTF-16 associés au format hexadécimal (par exemple, le mot de 16 bits 0x5B66).

Pour amuser un peu, examinons certains symboles PICTOGRAPHIQUE. Le Unicode caractère « Bonhomme de neige » (U+2603, U + 2603) est encodé en UTF-8 en tant que la séquence de trois octets : 0xE2 0x98 0x83 ; Toutefois, le codage UTF-16 est l’unique 0x2603 d’unité de 16 bits. Unicode caractère « Chope de bière » (U+1F37A, U + 1F37A), qui se trouve à l’extérieur du BMP, est codé en UTF-8 par la séquence de quatre octets 0xF0 0x9F 0x8D 0xBA. L’encodage UTF-16 utilise à la place de deux unités de code 16 bits, 0xD83C 0xDF7A, un exemple d’une paire de substitution UTF-16.

Conversion entre UTF-8 et UTF-16 à l’aide des API Win32

Comme indiqué dans les paragraphes précédents, texte Unicode est représenté dans la mémoire d’un ordinateur à l’aide de bits différents, en fonction de l’encodage Unicode particulier. Le codage devez-vous utiliser ? Il n’existe pas une seule réponse à cette question.

Récemment, la sagesse semble suggérer qu’une bonne approche se compose de stocker du texte Unicode encodé en UTF-8 dans les instances de la classe std::string dans du code C++ interplateforme. En outre, il est accord général que UTF-8 est le codage de choix pour l’échange de texte au-delà des limites de l’application et sur plusieurs ordinateurs différents. Le fait que UTF-8 est un format indépendant d’endian joue un rôle important dans ce. Dans tous les cas, conversions entre UTF-8 et UTF-16 doivent au moins à la limite de l’API Win32, comme API activées Unicode Windows utilisent UTF-16 comme leur codage natif.

Maintenant nous allons vous plonger dans du code C++ pour implémenter ces conversions d’encodage Unicode UTF-8/UTF-16. Il existe deux clé API Win32 qui peut être utilisé à cet effet : MultiByteToWideChar et son WideCharToMultiByte symétrique. Elle peut être appelée pour convertir UTF-8 (chaîne « multi-octets » dans la terminologie d’API spécifique) en UTF-16 (chaîne « largeur de caractère ») ; ce dernier permet l’inverse. Étant donné que ces fonctions Win32 ont des interfaces similaires et modèles d’utilisation, je vais me concentrer uniquement sur MultiByteToWideChar dans cet article, mais j’ai inclus le code compilable de C++ qui utilise l’autres API dans le cadre du téléchargement de cet article.

à l’aide des Classes de chaîne STL Standard à magasin Unicode car il s’agit d’un article de C++, il est une attente de stockage de texte Unicode dans une sorte de classe de chaîne valide. Donc, maintenant la question devient : Quelles sont les classes de chaîne C++ peut être utilisé pour stocker du texte Unicode ? La réponse est basée sur le codage particulier utilisé pour le texte Unicode. Si l’encodage UTF-8 est utilisé, car il est basé sur des unités de code de 8 bits, un caractère simple peut servir à représenter chacun de ces unités de code en C++. Dans ce cas, la classe de std::string STL, qui est de type char, est une bonne option pour stocker du texte Unicode encodée en UTF-8.

En revanche, si le texte Unicode est encodé en UTF-16, chaque unité de code est représentée par des mots de 16 bits. Dans Visual C++, le type wchar_t est exactement 16 bits taille ; Par conséquent, la classe std::wstring STL, qui est basé sur les wchar_t, fonctionne bien pour stocker du texte d’Unicode UTF-16.

Il est important de noter que la norme C++ ne spécifie pas la taille du type wchar_t, pendant qu’il s’élève à 16 bits avec le compilateur Visual C++, autres compilateurs C++ sont libres d’utiliser des tailles différentes. Et, en fait, la taille de wchar_t définis par le compilateur GNU GCC C++ sur Linux est 32 bits. Le type wchar_t ayant différentes tailles sur les plateformes et les différents compilateurs, la classe std::wstring, qui est basée sur ce type, n’est pas portable. En d’autres termes, wstring peut être utilisé pour stocker du texte Unicode encodé en UTF-16 sur Windows avec le compilateur Visual C++ (où la taille de wchar_t est de 16 bits), mais pas sous Linux avec le compilateur GCC C++, qui définit un type wchar_t de 32 bits tailles différentes.

Il est en fait un autre encodage Unicode, qui est moins connue et moins utilisé dans la pratique que ses frères : UTF-32. Comme son nom l’indique clairement, il est basé sur des unités de code 32 bits. Par conséquent, un wchar_t GCC/Linux 32 bits est un bon candidat pour l’encodage UTF-32 sur la plate-forme Linux.

Cette ambiguïté de la taille de wchar_t détermine un manque à la suite de la portabilité du code C++ basé sur (y compris la classe std::wstring elle-même). En revanche, std::string, qui est de type char, est portable. Toutefois, à partir d’un point de vue pratique, il est important de noter que l’utilisation de wstring pour stocker du texte encodé en UTF-16 est parfaitement dans le code C++ de spécifiques à Windows. En fait, ces parties de code déjà interagissent avec les API Win32, qui sont, bien sûr, spécifique à la plateforme par définition. Ainsi, l’ajout à la combinaison wstring ne modifie pas la situation.

Enfin, il est important de noter que, comme UTF-8 et UTF-16 sont les encodages de longueur variable, les valeurs de retour des méthodes string::length et wstring::length en général ne correspondent pas au nombre d’Unicode des caractères (ou points de code) stockées dans les chaînes.

L’Interface de fonction de Conversion nous allons développer une fonction pour convertir du texte Unicode encodé en UTF-8 au texte équivalent encodé à l’aide de UTF-16. Cela peut être pratique, par exemple, lorsque vous disposez du code C++ interplateforme qui stocke les chaînes Unicode encodée en UTF-8 à l’aide de la bibliothèque STL std::string (classe) et que vous souhaitez passer ce texte vers les API Win32 compatibles Unicode, qui utilisent généralement l’encodage UTF-16. Ce code est discuté avec les API Win32, il est déjà non portable, donc std::wstring convient parfaitement pour stocker du texte UTF-16 ici. Un prototype de fonction possible est :

std::wstring Utf8ToUtf16(const std::string& utf8);

Cette fonction de conversion accepte en tant que chaîne d’entrée une Unicode UTF-8-encodé, qui est stocké dans la classe de std::string STL standard. Comme il s’agit d’un paramètre d’entrée, il est passé par référence const (const &) à la fonction. Comme le résultat de la conversion, une chaîne encodée en UTF-16 est retournée, stockées dans une instance std::wstring. Toutefois, au cours des conversions d’encodage Unicode, problèmes éventuels. Par exemple, la chaîne d’entrée UTF-8 peut contenir une séquence non valide de UTF-8 (qui peut être le résultat d’un bogue dans d’autres parties du code, ou il pourrait finir là à la suite d’une activité malveillante). Dans ce cas, la meilleure chose à partir d’une perspective de sécurité est Échec de la conversion, au lieu de l’utilisation des séquences d’octets potentiellement dangereux. La fonction de conversion peut gérer le cas des séquences en entrée non valides UTF-8 en levant une exception C++.

Définition d’une classe d’Exception pour les erreurs de Conversion quel type de classe C++ peut être utilisé pour lever une exception en cas d’échec de conversion de codage Unicode ? Une option peut utiliser une classe déjà définie dans la bibliothèque standard, par exemple : std::runtime_error. Cependant, je préfère définir une nouvelle classe d’exception C++ personnalisée pour ce faire, dérivant de std::runtime_error. Lors de l’échec de l’API Win32 comme MultiByteToWideChar, il est possible d’appeler GetLastError pour obtenir davantage d’informations sur la cause de l’échec. Par exemple, dans le cas des séquences de UTF-8 non valides dans la chaîne d’entrée, un code d’erreur de type retourné par GetLastErorr est ERROR_NO_UNICODE_TRANSLATION. Il est judicieux d’ajouter cette information à la classe d’exception C++ personnalisée. Il peut être pratique pour le débogage plus tard. La définition de cette classe d’exception peut démarrer comme suit :

// utf8except.h
#pragma once
#include <stdint.h>   // for uint32_t
#include <stdexcept>  // for std::runtime_error
// Represents an error during UTF-8 encoding conversions
class Utf8ConversionException
  : public std::runtime_error
{
  // Error code from GetLastError()
  uint32_t _errorCode;

Notez que la valeur retournée par GetLastError est de type DWORD, qui représente un entier non signé 32 bits. Toutefois, DWORD est typedef non portable Win32 spécifiques. Même si cette classe d’exception C++ est levée à partir des parties spécifiques Win32 de code C++, il peut être interceptée par le code C++ interplateforme ! Par conséquent, il n’est pertinent d’utiliser des typedefs portables au lieu de ceux ; Win32 spécifiques uint32_t est un exemple de ces types.

Ensuite, un constructeur peut être défini pour initialiser les instances de cette classe d’exception personnalisée avec un message d’erreur et le code d’erreur :

public:
  Utf8ConversionException(
    const char* message,
    uint32_t errorCode
  )
    : std::runtime_error(message)
    , _errorCode(errorCode)
  { }

Enfin, un accesseur Get public peut être défini pour permettre l’accès en lecture seule pour le code d’erreur :

uint32_t ErrorCode() const
  {
    return _errorCode;
  }}; // Exception class

Cette classe est dérivée de std::runtime_error, il est possible d’appeler la méthode pour obtenir le message d’erreur est passée dans le constructeur. Notez que seuls les éléments portables standards ont été utilisés dans la définition de cette classe, par conséquent, cette classe est parfaitement utilisable dans des parties de C++ interplateforme du code, même ces trouve loin à partir du point de levée d’exceptions spécifiques à Windows.

Conversion de UTF-8, UTF-16 : MultiByteToWideChar en Action

Maintenant que le prototype de la fonction de conversion a été défini et implémenté par une classe d’exception C++ personnalisée pour représenter correctement les échecs de conversion UTF-8, il est temps de développer le corps de la fonction de conversion. Comme déjà prévu, le travail de conversion UTF-8 et UTF-16 peut être effectué à l’aide de l’API Win32 de MultiByteToWideChar. Les termes « multi-octets » et « caractères » ont des racines dans des raisons historiques. En fait, cette API et son frère symétrique WideCharToMultiByte ont été initialement conçus pour effectuer une conversion entre le texte stocké dans les pages de codes spécifique et texte Unicode, qui utilise l’encodage UTF-16 dans les API Win32 Unicode. Largeur de caractère fait référence à wchar_t, donc elle est associée à une chaîne wchar_t, qui est une chaîne encodée en UTF-16. En revanche, une chaîne multioctet est une séquence d’octets dans une page de codes. Le concept de page de code hérité a été ensuite étendu pour inclure l’encodage UTF-8.

Un modèle d’utilisation typique de cette API se compose de première MultiByteToWideChar appelant pour obtenir la taille de la chaîne de résultat. Ensuite, une mémoire tampon de chaîne est alloué en fonction de cette valeur de taille. Cela est généralement effectué à l’aide de la méthode std::wstring::resize si la destination est une chaîne UTF-16. (Pour plus de détails, vous voudrez lire mon article de juillet 2015, « À l’aide de STL chaînes à limites des API Win32 » à msdn.com/magazine/mt238407.) Enfin, la fonction est appelée une deuxième fois pour effectuer la conversion de codage réelle, MultiByteToWideChar à l’aide de la destination de chaîne mémoire tampon allouée précédemment. Notez que le même modèle d’utilisation s’applique à l’API WideCharToMultiByte symétrique.

Nous allons implémenter ce modèle dans le code C++, dans le corps de la fonction de conversion Utf8ToUtf16 personnalisé. Commencer à gérer le cas d’une chaîne d’entrée vide, les où simplement un wstring sortie vide est retournée :

#include <Windows.h> // For Win32 APIs
#include <string>    // For std::string and std::wstring
std::wstring Utf8ToUtf16(const std::string& utf8)
{
  std::wstring utf16; // Result
  if (utf8.empty())
  {
    return utf16;
  }

Indicateurs de conversion MultiByteToWideChar peut être appelée pour la première fois obtenir la taille de la destination de chaîne UTF-16. Cette fonction Win32 dispose d’une interface relativement complexe, et son comportement est défini en fonction de certains indicateurs. Cette API est appelée deux fois dans le corps de la fonction de conversion Utf8ToUtf16, il est recommandé pour la lisibilité du code et la facilité de gestion pour définir une constante nommée qui peut être utilisée dans les deux appels :

// Safely fails if an invalid UTF-8 character
// is encountered in the input string
constexpr DWORD kFlags = MB_ERR_INVALID_CHARS;

Il est également conseillé de vue de la sécurité pour rendre le processus de conversion échoue si une séquence d’UTF-8 non valide est trouvée dans la chaîne d’entrée. L’utilisation de l’indicateur MB_ERR_INVALID_CHARS est également encouragée dans le livre de Michael Howard et de David LeBlanc, « Writing Secure Code, Second Edition » (Microsoft Press, 2003).

Si votre projet utilise une ancienne version du compilateur Visual C++ qui ne prennent pas en charge le mot clé constexpr, vous pouvez remplacer static const dans ce contexte.

Longueurs de chaîne et des Conversions Safe size_t int MultiByteToWideChar attend le paramètre de longueur de chaîne d’entrée exprimé à l’aide du type int, tandis que la méthode de la longueur des classes STL chaîne renvoie une valeur de type équivalent à size_t. Dans les versions 64 bits, le compilateur Visual C++ émet un avertissement signalant une perte potentielle de données pour la conversion de size_t (dont la taille est de 8 octets) en int (qui est la taille de 4 octets). Mais dans les versions 32 bits, où size_t et int sont définis en tant qu’entiers 32 bits par le compilateur Visual C++, il existe une incompatibilité signé/signé : size_t est non signé, tandis qu’int est signé. Ce n’est pas un problème pour les chaînes de longueur raisonnable, mais pour les chaînes gigantesques de la longueur est supérieure à (231-1), autrement dit, plus de 2 milliards d’octets de taille : la conversion d’un entier non signé (size_t) en un entier signé (int) peut générer un nombre négatif, et les longueurs négatives incompréhensible.

Par conséquent, au lieu de simplement appeler utf8.length pour obtenir la taille de la source de chaîne d’entrée UTF-8 et en le passant à l’API MultiByteToWideChar, il est préférable vérifier la valeur size_t réelle de la longueur, et assurez-vous qu’il peut être converti en toute sécurité et de manière significative en int et passer ensuite à l’API MultiByteToWideChar.

Le code suivant peut être utilisé pour vous assurer que size_t longueur ne dépasse pas la valeur maximale d’une variable de type int, lever une exception dans ce cas :

if (utf8.length() > static_cast<size_t>(std::numeric_limits<int>::max()))
{
  throw std::overflow_error(
    "Input string too long: size_t-length doesn't fit into int.");
}

Notez l’utilisation du modèle de classe std::numeric_limits (à partir de l’en-tête standard < limites > C++) pour interroger la plus grande valeur possible pour le type int. Toutefois, ce code ne peut pas réellement compilé. Comment est-ce ? Le problème se trouve dans la définition de min et maximales macros dans les en-têtes Windows Platform SDK. En particulier, la définition spécifique à Windows de la macro de préprocesseur max est en conflit avec le std::numeric_limits < int > :: max d’appel de fonction membre. Il existe quelques méthodes permettant d’éviter.

Une solution possible consiste à #define NOMINMAX avant d’inclure < Windows.h >. Cela empêchera la définition de min et maximales macros préprocesseur spécifiques à Windows. Toutefois, empêchant la définition de ces macros peut-être réellement entraîner des problèmes avec d’autres en-têtes Windows, tels que < gdiplus.h >, qui ne nécessitent pas les définitions de ces macros spécifiques à Windows.

Par conséquent, une autre option consiste à utiliser une paire de parenthèses autour de l’appel de fonction membre std::numeric_limits::max supplémentaire pour empêcher l’expansion macro mentionnées précédemment :

if (utf8.length() > static_cast<size_t>((std::numeric_limits<int>::max)()))
{
  throw std::overflow_error(
    "Input string too long: size_t-length doesn't fit into int.");
}

En outre, comme alternative, la constante INT_MAX peut être utilisée au lieu du modèle de classe C++ std::numeric_limits.

N’importe quelle approche est utilisé, une fois effectuée la vérification de la taille et la valeur de longueur s’avère approprié pour une variable de type int, le cast de size_t int peut être effectué en toute sécurité à l’aide de static_cast :

// Safely convert from size_t (STL string's length)
// to int (for Win32 APIs)
const int utf8Length = static_cast<int>(utf8.length());

Notez que la longueur de la chaîne UTF-8 est mesurée en unités de 8 bits char ; Autrement dit, en octets.

Premier appel d’API : Obtenir la longueur de la chaîne Destination maintenant MultiByteToWideChar peut être appelée pour la première fois, pour obtenir la longueur de la chaîne de destination UTF-16 :

const int utf16Length = ::MultiByteToWideChar(
  CP_UTF8,       // Source string is in UTF-8
  kFlags,        // Conversion flags
  utf8.data(),   // Source UTF-8 string pointer
  utf8Length,    // Length of the source UTF-8 string, in chars
  nullptr,       // Unused - no conversion done in this step
  0              // Request size of destination buffer, in wchar_ts
);

Notez la façon dont la fonction est appelée en passant zéro comme dernier argument. Ce code indique à l’API MultiByteToWideChar simplement retourner la taille requise pour la chaîne de destination ; Aucune conversion n’est effectuée dans cette étape. Notez également que la taille de la chaîne de destination est exprimée en wchar_ts (pas de caractères 8 bits), ce qui est logique, car la chaîne de destination est une chaîne Unicode encodée en UTF-16, effectuée par des séquences de wchar_ts de 16 bits.

Pour obtenir un accès en lecture seule au contenu de l’entrée std::string UTF-8, la méthode std::string::data est appelée. Étant donné que la longueur de la chaîne UTF-8 est explicitement passée comme paramètre d’entrée, ce code fonctionne également pour les instances de std::string avoir incorporé NULs à l’intérieur.

Notez également l’utilisation de la constante CP_UTF8 pour spécifier que la chaîne d’entrée est encodée en UTF-8.

Le cas d’erreur de la gestion des si l’appel de fonction précédente échoue, par exemple, en présence de séquences de UTF-8 non valides dans la chaîne d’entrée, l’API MultiByteToWideChar retourne zéro. Dans ce cas, la fonction GetLastError Win32 peut être appelée pour obtenir davantage de détails sur la cause de l’échec. Un code d’erreur de type retourné en cas de caractères UTF-8 est ERROR_NO_UNICODE_TRANSLATION.

En cas d’échec, il est temps pour lever une exception. Cela peut être une instance de la classe Utf8ConversionException précédemment conçus de manière personnalisée :

if (utf16Length == 0)
{
  // Conversion error: capture error code and throw
  const DWORD error = ::GetLastError();
  throw Utf8ConversionException(
    "Cannot get result string length when converting " \
    "from UTF-8 to UTF-16 (MultiByteToWideChar failed).",
    error);
}

Allocation de mémoire pour la chaîne de Destination si l’appel de fonction Win32 réussit, la longueur de la chaîne de destination requise est stockée dans la variable locale utf16Length, par conséquent, la mémoire de destination pour la sortie de chaîne UTF-16 peut être alloué. Pour les chaînes UTF-16 stockées dans les instances de la classe std::wstring, un simple appel à la méthode resize serait parfaitement :

utf16.resize(utf16Length);

Étant donné que la longueur de la chaîne UTF-8 d’entrée a été explicitement passé à MultiByteToWideChar (au lieu de simplement en passant la valeur -1 et demander à l’API pour analyser la chaîne d’entrée entière jusqu'à une marque de fin null), l’API Win32 ne va pas ajouter une terminaison NUL supplémentaire à la chaîne résultante : L’API traite simplement le nombre exact de caractères dans la chaîne d’entrée spécifié par la valeur de longueur explicitement transmis. Par conséquent, il n’est pas nécessaire d’appeler std::wstring::resize avec un « utf16Length + 1 » valeur : Car aucune terminaison NUL supplémentaire n’est être griffonne dans par l’API Win32, vous n’êtes pas obligé de laisser la place à l’intérieur du std::wstring destination (plus de détails sur qui figurent dans mon article de juillet 2015).

Deuxième appel d’API : La conversion réelle maintenant que l’instance de wstring UTF-16 possède suffisamment d’espace pour héberger le texte codé en UTF-16, il est temps d’appeler MultiByteToWideChar pour la deuxième fois, pour obtenir les bits convertis réels dans la chaîne de destination :

// Convert from UTF-8 to UTF-16
int result = ::MultiByteToWideChar(
  CP_UTF8,       // Source string is in UTF-8
  kFlags,        // Conversion flags
  utf8.data(),   // Source UTF-8 string pointer
  utf8Length,    // Length of source UTF-8 string, in chars
  &utf16[0],     // Pointer to destination buffer
  utf16Length    // Size of destination buffer, in wchar_ts          
);

Notez l’utilisation de la « & utf16 [0] « syntaxe pour accéder en écriture au tampon de mémoire interne de le std::wstring (, a été déjà abordé dans mon article de juillet 2015).

Si le premier appel à MultiByteToWideChar réussit, il est peu probable que ce deuxième appel échoue. Vérification de la valeur de retour d’API reste certainement une pratique de codage sécurisée, bon :

if (result == 0)
{
  // Conversion error: capture error code and throw
  const DWORD error = ::GetLastError();
  throw Utf8ConversionException(
    "Cannot convert from UTF-8 to UTF-16 "\
    "(MultiByteToWideChar failed).",
    error);
}

Else, en cas de réussite, la chaîne UTF-16 qui en résulte peut enfin renvoyer à l’appelant :

return utf16;
} // End of Utf8ToUtf16

Exemple d’utilisation par conséquent, si vous avez une chaîne Unicode encodée en UTF-8 (par exemple, en provenance de code C++ interplateforme) et que vous souhaitez passer à une API Win32 Unicode compatible, cette fonction de conversion personnalisée peut être appelée comme suit :

std::string utf8Text = /* ...some UTF-8 Unicode text ... */;
// Convert from UTF-8 to UTF-16 at the Win32 API boundary
::SetWindowText(myWindow, Utf8ToUtf16(utf8Text).c_str());
// Note: In Unicode builds (Visual Studio default) SetWindowText
// is expanded to SetWindowTextW

La fonction Utf8ToUtf16 renvoie une instance wstring qui contient la chaîne encodée en UTF-16 et la méthode c_str est appelée sur cette instance pour obtenir un pointeur brut de style C en une chaîne terminée par le caractère NUL, à passer à l’API Win32 Unicode activées.

Un code très similaire peut être écrite pour la conversion inverse de UTF-16 en UTF-8, cette fois l’appel de l’API WideCharToMultiByte. Comme je l’ai indiqué précédemment, les conversions Unicode entre UTF-8 et UTF-16 sont sans perte : aucun caractère ne seront perdues lors du processus de conversion.

Bibliothèque de Conversion de codage Unicode

Exemple de code C++ compilable est inclus dans l’archive téléchargeable associé à cet article. C’est le code réutilisable, compiler correctement au niveau d’avertissement de Visual C++ 4 (/ W4) dans les versions 32 bits et 64 bits. Il est implémenté comme une bibliothèque C++ en-tête uniquement. En fait, ce module de conversion de codage Unicode se compose de deux fichiers d’en-tête : utf8except.h et utf8conv.h. La première contient la définition d’une classe d’exception C++ utilisée pour signaler les conditions d’erreur pendant les conversions d’encodage Unicode. Ce dernier implémente le codage des fonctions de conversion Unicode réelle.

Notez que utf8except.h contient le code C++ uniquement inter-plateformes, en permettant d’intercepter l’exception de conversion de codage UTF-8 n’importe où dans vos projets C++, y compris les portions de code qui ne sont pas spécifiques à Windows et à la place utilisent multiplateforme C++ à la conception. En revanche, utf8conv.h contient le code C++ qui est spécifique à Windows, car il interagit directement avec la limite de l’API Win32.

Pour réutiliser ce code dans vos projets, juste #include les fichiers d’en-tête mentionnées précédemment. L’archive téléchargeable contient un fichier source supplémentaire de l’implémentation des cas de test.

Pour résumer

Unicode est la norme de facto pour représenter le texte international de logiciels modernes. Texte Unicode peut être encodée dans divers formats : Les deux plus importants sont UTF-8 et UTF-16. Dans le code C++ Windows il est souvent nécessaire d’effectuer une conversion entre UTF-8 et UTF-16, car les API Win32 compatibles Unicode utiliser UTF-16 comme leur codage Unicode natif. Texte UTF-8 peut être facilement stockée dans les instances de la classe std::string STL, tandis que std::wstring convient parfaitement pour stocker du texte encodé en UTF-16 dans le code C++ de Windows pour le compilateur Visual C++.

Le Win32 API MultiByteToWideChar et WideCharToMultiByte permet d’effectuer des conversions entre du texte Unicode représentées à l’aide de l’UTF-8 et les encodages UTF-16. J’ai montré une description détaillée du modèle d’utilisation de l’API MultiByteToWideChar, qui l’encapsule dans une fonction d’assistance C++ moderne réutilisable pour effectuer des conversions UTF-8, UTF-16. La conversion inverse suit un modèle similaire, et mettre en œuvre le code C++ réutilisable est disponible dans le téléchargement de cet article.


Giovanni Dicanio est programmeur informatique spécialisé dans C++ et Windows, un auteur de Pluralsight et MVP Visual C++.  Outre la programmation et la création de cours, il aime aider les autres sur les forums et communautés consacré à C++ et peuvent être contactées à giovanni.dicanio@gmail.com. Il écrit également un blog à l’adresse blogs.msmvps.com/gdicanio.

Merci aux experts techniques suivants d'avoir relu cet article : David Cravey et Marc Gregoire
David Cravey est un architecte d’entreprise à GlobalSCAPE entraîne plusieurs groupes d’utilisateurs de C++ et a quatre fois Visual C++ MVP.

Marc Gregoire est ingénieur logiciel senior en Belgique, fondateur du groupe d’utilisateurs C++ belge, auteur de « Professionnel C++ » (Wiley), co-auteur de « C++ Standard Library rapide Reference » (Apress), rédacteur technique sur de nombreux ouvrages et depuis 2007, a reçu la récompense MVP annuelle pour son expertise VC ++. Marc peut être contacté à marc.gregoire@nuonsoft.com.