delphi utf-8

¿Hay una forma fácil de solucionar una falla del archivo Delphi utf8?



utf-8 (4)

Descubrí (de la manera más difícil) que si un archivo tiene una lista de materiales UTF-8 válida pero contiene codificaciones UTF8 no válidas y es leído por cualquiera de los métodos habilitados para codificación Delphi (2009+) como LoadFromFile , entonces el resultado es un archivo completamente vacío sin indicación de error. En muchas de mis aplicaciones, preferiría simplemente perder unas pocas codificaciones incorrectas, incluso si tampoco recibo un informe de error en este caso.

La depuración revela que se llama a MultiByteToWideChar dos veces, primero para obtener el tamaño del búfer de salida y luego para realizar la conversión. Pero TEncoding.UTF8 contiene un valor privado FMBToWCharFlags para estas llamadas, y este se inicializa con un valor MB_ERR_INVALID_CHARS . Entonces, la llamada para obtener el contador devuelve 0 y el archivo cargado está completamente vacío. Llamar a esta API sin la bandera "arrojaría silenciosamente puntos de código ilegales".

Mi pregunta es cuál es la mejor forma de pasar por el nido de clases en el área de codificación para evitar el hecho de que este es un valor privado (y debe serlo, porque es una clase var para todos los hilos). Creo que podría agregar una codificación UTF8 personalizada, siguiendo las instrucciones del libro Delphi 2009 de Marco Cantu. Y opcionalmente podría generar una excepción si MultiByteToWideChar ha devuelto un error de codificación, después de volver a llamar sin la bandera. Pero eso no resuelve el problema de cómo usar mi codificación personalizada en lugar de Tencoding.UTF8 .

Si pudiera configurar esto como predeterminado para la aplicación en la inicialización, tal vez modificando realmente la clase var para Tencoding.UFT8 , esto probablemente sería suficiente.

Por supuesto, necesito una solución sin esperar a presentar un informe de control de calidad que pida un diseño más sólido, que sea aceptado y que lo cambien.

Cualquier idea sería muy bienvenida. ¿Y alguien puede confirmar que esto sigue siendo un problema para XE4, que aún no he instalado?


Esto se puede hacer bastante simple, al menos en Delphi XE5 (no se han comprobado las versiones anteriores). Simplemente ejemplifique su propio TUTF8Encoding :

procedure LoadInvalidUTF8File(const Filename: string); var FEncoding: TUTF8Encoding; begin FEncoding := TUTF8Encoding.Create(CP_UTF8, 0, 0); // Instead of CP_UTF8, MB_ERR_INVALID_CHARS, 0 try with TStringList.Create do try LoadFromFile(Filename, FEncoding); // ... finally Free; end; finally FEncoding.Free; end; end;

El único problema aquí es que la propiedad IsSingleByte para la TUTF8Encoding recién instanciada se establece incorrectamente en False , pero esta propiedad no se usa actualmente en ninguna parte de las fuentes Delphi.


Me encontré con el problema MB_ERR_INVALID_CHARS cuando primero actualicé Indy para admitir TEncoding , y terminé implementando una clase derivada de TEncoding personalizada para el manejo de UTF-8 para evitar especificar MB_ERR_INVALID_CHARS . No pensé usar un ayudante de clase.

Sin embargo, este problema no solo se limita a UTF-8. Cualquier falla de decodificación de cualquiera de las clases de TEncoding dará como resultado un resultado en blanco, no una excepción. Por qué Embarcadero eligió esa ruta, cuando la mayoría de RTL / VCL usa excepciones en su lugar, me supera. No presentar una excepción por error causó una gran cantidad de problemas en Indy que tuvieron que solucionarse.


Su enfoque "global" no es realmente global; se basa en la suposición de que todo el código solo usaría una y la misma instancia de TUTF8Encoding . La misma instancia en la que pirateaste el campo de banderas.

Pero no funcionaría si uno obtiene TUTF8Encoding objeto (s) por otros medios que TEncoding.GetUTF8 , por ejemplo en XE2 otro método - TEncoding.GetEncoding(CP_UTF8) - crearía una nueva instancia de TUTF8Encoding lugar de reutilizar FUTF8 compartida . O alguna función puede ejecutar TUTF8Encode.Create directamente.

Entonces sugeriría dos enfoques más.

Enfoque con parches en la implementación de la clase, un tanto hacky. Presenta su propia clase para obtener un nuevo cuerpo constructor de "soluciones".

type TMyUTF8Encoding = class(TUTF8Encoding) public constructor Create; override; end;

Este constructor sería el imitador de la TUTF8Encoding.Create() de TUTF8Encoding.Create() , excepto para configurar el indicador como lo desee (en XE2 se hace llamando a otro Create(x,y,z) heredado para que no necesite un acceso a el campo privado) en su lugar.

A continuación, puede aplicar parche a la acción TUTF8Encoding VMT sobreescribiendo su constructor virtual a ese nuevo constructor suyo.

Puede leer la documentación de Delphi sobre "formatos internos", etc., para obtener el diseño de VMT. También necesitaría llamar a VirtualProtect (u otra función específica de la plataforma) para eliminar la protección del área de memoria VMT antes de parchear y luego restaurarla.

Ejemplos para aprender de

  • Cómo cambiar la implementación (desvío) de una función declarada externamente
  • https://.com/a/1482802/976391

O puede intentar usar la biblioteca Delphi Detours , con suerte puede parchear constructores virtuales. Entonces ... podría ser una exageración usar esa lib bastante compleja para ese único objetivo.

Después de hackear la clase TUTF8Encoding , llame a TEncoding.FreeEncodings para eliminar las instancias compartidas ya creadas (si las hay) si las hubiera y, por lo tanto, desencadene la recreación de las instancias UTF8 con sus modificaciones.

Luego, si compila su programa como un single monolithic EXE , sin usar los módulos BPL de tiempo de ejecución, puede copiar las fuentes SysUtils.pas a su carpeta de aplicaciones y luego incluir esa copia local en su proyecto explícitamente.

Cómo aplicar un parche a un método en Classes.pas

Allí cambiarías la implementación de la TUTF8Encoding como mejor te parezca en las fuentes y Delphi la usaría.

Este enfoque simplista y mortal (por lo tanto, igualmente confiable) no funcionaría si sus proyectos se construyeran para reutilizar el paquete de tiempo de ejecución rtlNNN.bpl lugar de ser monolíticos.


Una solución parcial consiste en obligar a la codificación UTF8 a suprimir MB_ERR_INVALID_CHARS globalmente. Para mí, esto evita la necesidad de plantear una excepción, porque me parece que hace que MultiByteToWideChar no sea "silencioso": en realidad inserta caracteres $fffd (Unicode ''carácter de reemplazo'') que luego puedo encontrar en los casos donde esto es importante. El siguiente código hace esto:

unit fixutf8; interface uses System.Sysutils; type TUTF8fixer = class helper for Tmbcsencoding public procedure setflag0; end; implementation procedure TUTF8fixer.setflag0; {$if CompilerVersion = 31} asm XOR ECX,ECX MOV Self.FMBToWCharFlags,ECX end; {$else} begin Self.FMBToWCharFlags := 0; end; {$endif} procedure initencoding; begin (Tencoding.UTF8 as TmbcsEncoding).setflag0; end; initialization initencoding; end.

Una solución más útil y basada en principios requeriría cambiar las llamadas a MultiByteToWideChar para no usar MB_ERR_INVALID_CHARS , y hacer una llamada inicial con esta bandera para que se pueda hacer una excepción después de que se complete la carga, para indicar que los caracteres se habrán reemplazado.

Existen informes relevantes de control de calidad sobre este tema, incluidos 76571, 79042 y 111980. El primero se ha resuelto ''como se diseñó''.

(Editado para trabajar con Delphi Berlin)