c++ - Infierno Unicode(en Windows)
winapi utf-8 (6)
ICU - ICU . Para los saltos de palabra correctos y la pantalla, Windows incluye Uniscribe y el uso de Windows que no sean de Windows (corrígeme si me equivoco).
Si, lo hago. Pero hasta donde sé, en el momento en que tomaron esa decisión, el UFF-32 no existía y pensaban que 65536 puntos de código "serían suficientes para todos".
No, no es. Además de cuadruplicar el uso de la memoria, el problema es mucho peor de lo que crees. No puede simplemente "modificar una cadena" y "reemplazar algunos caracteres": incluso cuando usa valores de 32 bits, porque un carácter Unicode no significa necesariamente una letra escrita o un glifo que puede eliminar o reemplazar con otra cosa y no esperar nada se rompe Para trabajar con el texto correctamente, tendrá que usar algo como ICU de todos modos, así que no hay mucha diferencia entre usar utf-8 y utf-32, creo.
Hoy me desperté y sentí que algo andaba muy mal con mi código y con todas las bibliotecas que había usado alguna vez, y creo que tenía razón ... (o por favor, señalen dónde está mi razonamiento equivocado)
Comencemos una década o dos atrás en el tiempo, todo estaba bien en el mundo. Hablé con mi vecino y él habló el mismo idioma: simplemente inglés. Para mí, mi vecino y Windows, parecía obvio almacenar nuestra cadena en caracteres de 8 bits porque todos los caracteres que utilizábamos podían almacenarse en las 2 ^ 8 = 256 combinaciones disponibles.
Luego vino el milagroso ser Internet y me permitió hablar con algunos amigos en Europa (que no tenían tiempo para aprender inglés). Esto se puso difícil con nuestro formato de char
, el número de caracteres usados excedió 256 fácilmente, así que en nuestra visión completamente simplista decidimos usar los wchar_t
s de 16 bits. Algo llamado UCS-2 unicode. Tiene 2 ^ 16 = 65.536 combinaciones disponibles ¡y eso debe ser suficiente para todos los idiomas del mundo! Convencidos de nuestra corrección, incluso agregamos funciones Windows API W
16 bits como MessageBoxW
y CreateWindowW
. Convencimos a todos los programadores de nuestra religión y desalentamos el uso de las malas contrapartes de 8 bits ( MessageBoxA
y CreateWindowA
) y asignamos una llamada a MessageBox
automáticamente a MessageBoxW
definiendo _UNICODE
en nuestras compilaciones. Por lo tanto, también deberíamos usar las funciones de wcs
lugar de las antiguas funciones de str
(por ejemplo, strlen
debería ahora ser wcslen
, o usar el _tcslen
asignado automáticamente).
Luego las cosas se pusieron mal, resultó que había otras personas en el mundo que usaban glifos más extraños (sin ofender) que los nuestros: japonés, chino, etc. Se puso malo porque, por ejemplo, el chino tiene más de 70,000 caracteres diferentes. Se produjeron muchos insultos y nos dejaron con un nuevo tipo de Unicode: UTF-16. También utiliza un tipo de datos de 16 bits, pero algunos caracteres requieren dos valores de 16 bits (llamado par suplente ). Lo que significa que no podemos usar índices en estas cadenas de 16 bits (por ejemplo, theString [4] puede no devolver el 5to carácter). Para parchear la API de Windows, se decidió que todas las funciones W
ahora deberían ser compatibles con el formato UTF-16, fue una decisión fácil ya que todas las cadenas antiguas UCS-2 también eran cadenas UTF-16 válidas. Sin embargo, debido a que somos valientes programadores, ahora usamos las funciones de wcs
. Lamentablemente, estas funciones no son conscientes de los suplentes y aún se ajustan al formato UCS-2 ...
Mientras tanto, en un ático oscuro, se desarrolló otra forma más compacta de Unicode: UTF-8. Con un tipo de datos de 8 bits, la mayoría de los idiomas occidentales se pueden almacenar en un único valor de 8 bits, al igual que en los viejos tiempos. Cuando se almacena un glifo más exótico, se usan múltiples valores de 8 bits, para la mayoría de los idiomas europeos 2 serán suficientes. Sin embargo, puede expandir hasta 4 de estos valores, creando esencialmente un tipo de almacenamiento de 32 bits. Al igual que su hermano gordo UTF-16, no podemos usar índices en estas cadenas. Debido a su formato más compacto, UTF-8 ahora se usa ampliamente en cualquier lugar de Internet porque ahorra ancho de banda.
Bien, sobreviviste a mi larga reseña :) Ahora tengo algunas preguntas / puntos de interés:
De acuerdo, estoy bastante satisfecho con el uso de UTF-8 para el almacenamiento. Cuando leo un archivo (desde el disco o la respuesta HTTP) detecto la firma UTF-8
"/xEF/xBB/xBF"
y coloco el contenido a través deMultiByteToWideChar
que me deja con una cadena UTF-16. Puedo usar eso con las funcionesW
API, no hay problema. Pero ahora quiero modificar la cadena, reemplazar algunos caracteres, etc. Las buenas y viejas funciones dewcs
ya no sirven, ¿qué funciones principales de la cadena son conscientes de UTF-16? ¿O hay alguna espléndida biblioteca que no conozco? Editar: Parece que la UCI es una solución bastante buena. También encontré que las funciones dewcs
no son completamente inútiles; por ejemplo, todavíawcsstr
usarwcsstr
para buscar, esencialmente solo comparawchar_t
s. El único problema es la longitud de la cadena.¿No tiene la sensación de que se cometió un feo error cuando nos obligaron a utilizar funciones
W
deficientes de 16 bits? ¿No debería haberse reconocido el problema en una etapa mucho más temprana y permitir que todas las funciones API originales adopten las cadenas UTF-8 e incorporen las rutinas adecuadas de manipulación de cadenas? ¿O eso ya es posible y estoy terriblemente equivocado? Editar: Tal vez esta fue una pregunta tonta, la retrospectiva es de hecho maravillosa, no sirve para bajar a nadie en este momento;)Para un acceso de índice rápido a los caracteres, debemos almacenar cadenas en valores de 32 bits. ¿Es esto común? (Puedo oírte pensar: y luego tocamos un lenguaje extraterrestre que requiere más combinaciones y la diversión comienza de nuevo ...) Parece que la desventaja de este enfoque es que debemos convertir la cadena de nuevo a UTF-16 cada vez que hacemos llamadas a la API de Windows. Editar: Solo para citar a Alf P. Steinbach, un personaje por índice es un sueño sin esperanza , lo veo ahora. Una cosa que me perdí por completo fue la diacritics . También creo que es bueno procesar en la codificación nativa del sistema operativo (para Windows UTF-16). Aunque UTF-8 hubiera sido una mejor opción, ahora estamos atascados con UTF-16, no tiene sentido convertir el código y la API. Como se sugiere a continuación, haré un seguimiento de las partes de una cadena mediante punteros en lugar de un recuento de caracteres.
Creo que te mereciste una buena taza de té luchando a pesar de esta larga pregunta, ve a buscar una antes de contestar;)
Editar: Acepto el hecho de que mi pregunta está cerrada, esta sería una mejor opción para una publicación de blog, pero de nuevo no escribo un blog. ¡Creo que esta cosa de codificación de caracteres es esencial y debería ser el siguiente tema en cualquier libro de programación después del simple ejemplo de hello world! Publicarlo aquí atrae la atención de muchos expertos, esas personas no leen ningún blog al azar y yo valoro mucho su opinión. Así que gracias a todos por contribuir.
ICU es una excelente biblioteca de cadenas Unicode. El concepto general con el manejo de cadenas es analizar cualquier formulario externo en la memoria de manera que cada valor sea un punto de código completo, no una parte de uno, como con UTF-16 y UTF-8. Luego, después de cualquier proceso, al salir del programa, vuelva a serializar la cadena a un formato de transformación adecuado. Aunque los conceptos básicos son fáciles, trate de no desplegar su propia biblioteca Unicode; cosas como la intercalación, la búsqueda y otros asuntos complicados se deben dejar en una biblioteca madura.
Los planos fuera del BMP no se usaron ni se definieron, ya que no se veía una necesidad. Por supuesto, como ha señalado, ciertamente existe una necesidad.
Sí, esto es común, y como se mencionó, es la mejor manera de hacer las cosas, ya que mejora en gran medida todas las operaciones de cadena.
Mi opinión sobre el asunto:
Para la interfaz externa (archivos, argumentos de línea de comandos, variables de entorno, stdin / out) usa UTF-8, porque es un flujo de bytes y todo el lenguaje C y C ++ está diseñado para interactuar con el entorno a través de flujos de bytes. En la mayoría de los sistemas de archivos sensibles, los nombres de archivo son cadenas de bytes (terminadas en nulo) también.
Para repetir el loro, también puede mantener cadenas en UTF-8 internamente, usando
char*
etc. y literales de cadena""
simples o los nuevosu8""
-8 literales".Para la manipulación textual, convierta la cadena en UTC-4 / UTF-32 internamente y trátela como una matriz de
char32_t
. Esa es la única forma en que puedes hablar de una transmisión de personajes .UTF-16 fue un gran error y debe ser fusilado y rechazado. Vea aquí (hice un comentario allí en alguna parte), y tal vez here y here .
No hay nada que le impida crear un caché simple que almacene la ubicación y la longitud de bytes de un punto de código codificado en UTF para que pueda usar el acceso aleatorio. Sin embargo, todas las cosas viejas de C de las que hablas no van a ayudar mucho.
Tampoco me gustaría confiar en que el UTF-8 ''BOM'' esté disponible porque es una tontería y probablemente haya sido eliminado por algunas implementaciones.
No sé a qué te refieres con que las funciones de las wcs
no sean buenas. Por qué no?
¿No tiene la sensación de que se cometió un feo error cuando nos obligaron a utilizar funciones W deficientes de 16 bits? ¿No debería haberse reconocido el problema en una etapa mucho más temprana y permitir que todas las funciones API originales adopten las cadenas UTF-8 e incorporen las rutinas adecuadas de manipulación de cadenas? ¿O eso ya es posible y estoy terriblemente equivocado?
UTF-8 se desarrolló mucho después de que se escribió la interfaz Unicode de Windows. Si hubieran agregado una versión UTF-8, ahora habría 3 versiones de cada función. Estoy seguro de que no usarían UTF-16 si comenzaran de nuevo, la retrospectiva es realmente maravillosa.
Con respecto a UTF-32, casi ningún software lo usa internamente. No lo recomendaría, especialmente no en una plataforma que no tiene soporte para nada. Usar UTF-32 sería crear trabajo para usted.
Por preferencia, debe traducir de UTF- * a UCS-4 mientras lee los datos. Todo el procesamiento se debe hacer en UCS-4, y luego (si es necesario) traducir de nuevo a UTF- * durante la salida.
Sin embargo, eso aún no soluciona todo. Hay un conjunto de marcas de "combinación diacrítica", lo que significa que incluso cuando utiliza UCS-4, la string[N]
no se corresponde necesariamente con el carácter n- ésimo de la cadena. Hay transformaciones en las formas canónicas que intentan ayudar con eso, pero no siempre pueden hacer el trabajo, por lo que si es realmente crítico (para su aplicación), simplemente necesita caminar a través de la cuerda, dividirla en unidades que cada representar un personaje completo (carácter base + y combinación de diacríticos) y tratar a cada uno de ellos como una unidad.