Ce qui suit n'est peut-être pas une question sur les SO ; si cela dépasse les limites, n'hésitez pas à me dire de m'en aller. La question est la suivante : "Est-ce que je comprends correctement la norme C et est-ce que c'est la bonne façon de procéder ?".
J'aimerais demander des éclaircissements, des confirmations et des corrections sur ma compréhension de la gestion des caractères en C (et donc en C++ et C++0x). Tout d'abord, une observation importante :
La portabilité et la sérialisation sont des concepts orthogonaux.
Les choses portables sont des choses comme le C, unsigned int
, wchar_t
. Les choses sérialisables sont des choses comme uint32_t
ou UTF-8. "Portable" signifie que vous pouvez recompiler la même source et obtenir un résultat fonctionnel sur toutes les plateformes supportées, mais la représentation binaire peut être totalement différente (ou même ne pas exister, par exemple le pigeon TCP sur porteur). Les choses sérialisables, d'un autre côté, ont toujours l'aspect mismo représentation, par exemple le fichier PNG que je peux lire sur mon bureau Windows, sur mon téléphone ou sur ma brosse à dents. Les objets portables sont internes, les objets sérialisables s'occupent des E/S. Les choses portables sont typesafe, les choses sérialisables ont besoin de type punning. </preamble>
Lorsqu'il s'agit de la gestion des caractères en C, il existe deux groupes de choses liées respectivement à la portabilité et à la sérialisation :
-
wchar_t
,setlocale()
,mbsrtowcs()
/wcsrtombs()
: Le standard C ne dit rien sur les "encodages". En fait, il est entièrement indépendant des propriétés du texte ou de l'encodage. Il dit seulement "votre point d'entrée estmain(int, char**)
; vous obtenez un typewchar_t
qui peut contenir tous les caractères de votre système ; vous obtenez des fonctions pour lire les séquences de caractères en entrée et les transformer en chaînes de caractères exploitables et vice versa. -
iconv()
et UTF-8,16,32 : Une fonction/bibliothèque pour transcoder entre des encodages bien définis, précis et fixes. Tous les encodages gérés par iconv sont universellement compris et acceptés, à une exception près.
Le pont entre le monde portable et agnostique de l'encodage du C et son système de gestion de l'encodage. wchar_t
type de caractère portable et le monde extérieur déterministe est Conversion iconv entre WCHAR-T et UTF .
Donc, dois-je toujours stocker mes chaînes de caractères en interne dans un wstring agnostique, et m'interfacer avec le CRT par l'intermédiaire de wcsrtombs()
et utiliser iconv()
pour la sérialisation ? Conceptuellement :
my program
<-- wcstombs --- /==============\ --- iconv(UTF8, WCHAR_T) -->
CRT | wchar_t[] | <Disk>
--- mbstowcs --> \==============/ <-- iconv(WCHAR_T, UTF8) ---
|
+-- iconv(WCHAR_T, UCS-4) --+
|
... <--- (adv. Unicode malarkey) ----- libicu ---+
En pratique, cela signifie que j'écrirais deux wrappers de type boiler-plate pour le point d'entrée de mon programme, par exemple pour C++ :
// Portable wmain()-wrapper
#include <clocale>
#include <cwchar>
#include <string>
#include <vector>
std::vector<std::wstring> parse(int argc, char * argv[]); // use mbsrtowcs etc
int wmain(const std::vector<std::wstring> args); // user starts here
#if defined(_WIN32) || defined(WIN32)
#include <windows.h>
extern "C" int main()
{
setlocale(LC_CTYPE, "");
int argc;
wchar_t * const * const argv = CommandLineToArgvW(GetCommandLineW(), &argc);
return wmain(std::vector<std::wstring>(argv, argv + argc));
}
#else
extern "C" int main(int argc, char * argv[])
{
setlocale(LC_CTYPE, "");
return wmain(parse(argc, argv));
}
#endif
// Serialization utilities
#include <iconv.h>
typedef std::basic_string<uint16_t> U16String;
typedef std::basic_string<uint32_t> U32String;
U16String toUTF16(std::wstring s);
U32String toUTF32(std::wstring s);
/* ... */
Est-ce la bonne façon d'écrire un noyau de programme idiomatique, portable, universel, agnostique en matière d'encodage, en utilisant uniquement du C/C++ standard pur, ainsi qu'une interface E/S bien définie vers l'UTF en utilisant iconv ? (Notez que des questions telles que la normalisation Unicode ou le remplacement des diacritiques sont en dehors du champ d'application ; ce n'est qu'après avoir décidé que vous voulez réellement Unicode (par opposition à tout autre système de codage qui vous plairait) est-il temps de s'occuper de ces spécificités, par exemple en utilisant une bibliothèque dédiée comme libicu).
Mises à jour
Suite à de nombreux commentaires très sympathiques, j'aimerais ajouter quelques observations :
-
Si votre application souhaite explicitement traiter du texte Unicode, vous devez faire de l'option
iconv
-conversion partie du noyau et utilisationuint32_t
/char32_t
-les chaînes de caractères en interne avec UCS-4. -
Les fenêtres : Bien que l'utilisation de chaînes de caractères larges soit généralement satisfaisante, il semble que l'interaction avec la console (n'importe quelle console, d'ailleurs) soit limitée, car il ne semble pas y avoir de support pour un encodage multi-octets raisonnable de la console et
mbstowcs
est essentiellement inutile (sauf pour un élargissement trivial). Recevoir des arguments à chaîne large, par exemple d'une chute d'explorateur avecGetCommandLineW
+CommandLineToArgvW
fonctionne (peut-être devrait-il y avoir un wrapper séparé pour Windows). -
Les systèmes de fichiers : Les systèmes de fichiers ne semblent pas avoir de notion de codage et prennent simplement n'importe quelle chaîne à terminaison nulle comme nom de fichier. La plupart des systèmes prennent des chaînes d'octets, mais Windows/NTFS prend des chaînes de 16 bits. Vous devez faire attention lorsque vous découvrez quels fichiers existent et lorsque vous manipulez ces données (par ex.
char16_t
les séquences qui ne constituent pas des UTF16 valides (par exemple, les substituts nus) sont des noms de fichiers NTFS valides). La norme Cfopen
n'est pas en mesure d'ouvrir tous les fichiers NTFS, puisqu'il n'existe aucune conversion possible qui correspondrait à toutes les chaînes de caractères 16 bits possibles. L'utilisation de l'option spécifique à Windows_wfopen
peuvent être nécessaires. En corollaire, il n'existe en général aucune notion bien définie du "nombre de caractères" d'un nom de fichier donné, puisqu'il n'existe aucune notion de "caractère" en premier lieu. Caveat emptor.