PDFlibPas compila con Free Pascal para Windows de 32 bits, y la parte difícil nunca fue el Pascal: fueron los ficheros de objetos. Los objetos de AES y OpenJPEG que enlaza la compilación de Delphi son OMF, el enlazador interno de Free Pascal exige COFF, y convertir entre ambos formatos genera nombres de sección y símbolos de definición de sección que hacen que el enlazador falle con errores internos en vez de dar un diagnóstico
Cualquiera que haya enlazado objetos C en una biblioteca Pascal conoce este territorio. Win64 es comparativamente civilizado: un solo formato de objetos, una sola convención de llamada y nada de decoración de nombres. Win32 conserva todas las capas de historia que la plataforma acumuló, y una biblioteca que enlaza estáticamente código C de terceros se topa con todas a la vez
El directorio del compilador no le dice cuál es el objetivo
Empiece por el punto de entrada de la compilación, porque equivocarse aquí quema horas antes de que intervenga ningún fichero de objetos. El nombre del directorio de instalación de Free Pascal identifica dónde vive el compilador principal, no qué produce. Un compilador anfitrión de 32 bits puede invocar un cross-compiler situado a su lado y emitir código de 64 bits cuando se le pasan los switches de objetivo correctos, así que inferir el objetivo a partir de una ruta es adivinar, algo que funciona de casualidad hasta que alguien reorganiza su toolchain
Lo fiable es preguntárselo al compilador. Consulte el procesador y el sistema operativo objetivo reales a través de los switches de información del propio compilador, y acepte ambos diseños de instalación habituales, el directorio plano de binarios y el anidado por versión, porque distintos instaladores y gestores de toolchain producen formas distintas. Un script de compilación que fija a mano cualquiera de los dos diseños solo funciona en una máquina
¿Por qué un objeto convertido rompe el enlazador interno?
Porque la conversión conserva la convención de nombres de sección de OMF y sintetiza símbolos de definición de sección que no coinciden con lo que el enlazador COFF espera. Convertir los objetos OMF a COFF es necesario pero no suficiente: los ficheros resultantes llevan los nombres de sección clásicos _TEXT, _DATA y _BSS, más los nombres de símbolos de definición de sección derivados de ellos, y dárselos al enlazador interno de Free Pascal produce errores internos del compilador en lugar de un mensaje sobre el nombrado de secciones
Un error interno es el peor modo de fallo para un problema de compilación, porque no dice nada sobre qué estaba mal en la entrada. La solución es una pasada de normalización posterior a la conversión sobre el fichero COFF: reescribir los nombres de sección a la forma esperada y reescribir los símbolos de definición de sección correspondientes para que coincidan, dejando intactos el índice de símbolos, los bytes de código y las reubicaciones. Esa última restricción es toda la dificultad. Una reescritura que renumera símbolos o desplaza offsets produce un objeto que enlaza y luego revienta
Hay un paso previo para uno de los dos juegos de objetos. Los objetos OpenJPEG construidos con el compilador clásico de C++ de 32 bits dependen de rutinas helper privadas de Delphi para enteros de 64 bits, que Free Pascal no proporciona, así que ninguna conversión de formato los hace utilizables. Esos se recompilan primero con el compilador basado en Clang, que no emite esas dependencias, y se convierten después
// Los objetos para el objetivo FPC viven en su propio directorio y no
// sustituyen al juego de objetos de Delphi: ambas toolchains compilan
// desde el mismo árbol fuente y cada una necesita sus propias entradas
//
// Lib\thirdparty\Win32 objetos OMF de Delphi, sin cambios
// Lib\thirdparty\Win32f objetos COFF de FPC, convertidos y normalizados
//
// Puntos de entrada de compilación:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Los helpers privados del compilador no son portables, y sus convenciones tampoco
El runtime de Delphi suministra trampolines en ensamblador para operaciones con enteros de 64 bits en x86 de 32 bits, y los objetos C precompilados construidos para Delphi los llaman. Free Pascal tiene su propio mecanismo, así que esas referencias hay que satisfacerlas de otra manera, no redirigirlas. El detalle que hace imposible la redirección es la convención de llamada: el helper de temporización que emplea el código de tratamiento de imágenes delega en el callee la limpieza de su argumento de cuatro bytes, mientras que el helper de división de 64 bits limpia dieciséis bytes y devuelve el resultado en el clásico par de registros. Dos helpers, dos convenciones, y un trampolín escrito para uno corrompe silenciosamente la pila del otro
La decoración de nombres añade la segunda mitad del problema. En Win32, Free Pascal antepone un guion bajo automáticamente a las importaciones C externas mientras que exporta las declaraciones public name literalmente, así que el lado de importación y el lado de exportación de un mismo puente siguen reglas distintas. El puente al runtime C que OpenJPEG necesita tiene, por tanto, que exportar los nombres de símbolo C exactos, y los puntos de entrada variádicos necesitan un salto indirecto de 32 bits en lugar de uno directo. Nada de esto es exótico una vez enunciado. Todo falla como un error de enlace que menciona un símbolo que nadie escribió
¿Qué hacía que un ejecutable Win32 muriera antes de main?
Una DLL de 64 bits en la ruta de búsqueda, alcanzada porque la unidad zlib de Free Pascal se enlaza dinámicamente en vez de estáticamente. El síntoma era una salida inmediata con el código de estado de imagen no válida, antes de que se ejecutara ningún código Pascal del programa, lo que le manda a revisar el programa que acaba de compilar cuando la culpa está en el loader, que resuelve una importación contra la arquitectura equivocada
La lección va de suposiciones, no de zlib. Una unidad con el nombre de una biblioteca de compresión no tiene por qué contener una; puede ser un binding que espera una biblioteca compartida en tiempo de ejecución, y una dependencia dinámica que usted no pretendía es un pasivo de despliegue incluso cuando por casualidad se resuelve. Pasar a la implementación de stream en Pascal puro da a ambos objetivos una ruta de compresión incluida estáticamente sin ninguna dependencia externa, que es lo que una biblioteca incrustada en la aplicación de otra persona debería tener desde el principio
El mismo instinto se aplica al backend externo del codificador JBIG2. En el objetivo de 32 bits el codificador externo no se enlaza, así que las peticiones recurren al codificador Pascal incorporado, y el test que lo verifica tiene que comprobar el estado de registro del objetivo actual en lugar de dar por presente el backend externo porque una codificación haya tenido éxito. Un fallback que funciona es justo lo que esconde una dependencia ausente, que es el patrón de fallo examinado en el diagnóstico de fallos silenciosos de stubs. El trabajo de enlazado estático en 64 bits se cubre en el enlazado estático de jbig2enc bajo FPC
Aritmética de 32 bits en un stream de memoria
El código que manipula tamaños de buffer con aritmética sin signo del ancho del puntero es correcto en Win64 y está a una imagen grande de desbordarse en Win32. El stream en memoria que alimenta el códec JPEG 2000 crece duplicándose y avanza sumando, y en un objetivo de 32 bits ambas operaciones pueden hacer wrap-around con entradas que son grandes pero perfectamente legítimas
Cada escritura, salto, seek y asignación inicial comprueba antes de calcular, y el techo de capacidad es el valor máximo con signo del ancho del puntero, elegido para coincidir con lo que la rutina de movimiento de bloques y los valores de retorno del callback pueden expresar. El requisito de comportamiento cuando se rechaza una petición es fácil de equivocar: rechazar no debe cambiar la posición del stream ni su longitud. Una mutación parcial seguida de un error deja el stream en un estado sobre el que el llamante no puede razonar, y la siguiente operación lo agrava
// Compruebe antes de calcular. En Win32 ambas cosas hacen wrap con
// entradas que una imagen JPEG 2000 grande produce legítimamente
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // rechazar, dejar posición y tamaño intactos
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // duplicar desbordaría
NewCapacity := NewCapacity shl 1;
end;
Dos trampas de la salida de compilación que sobreviven al port
Separar los ejecutables de test y de ejemplos por arquitectura objetivo en directorios de salida por objetivo es obviamente correcto y rompe inmediatamente todo lo que localizaba sus datos de test contando niveles de directorio hacia arriba. La solución es buscar hacia arriba el directorio de recursos en lugar de asumir una profundidad fija, con una restricción deliberada: el ejemplo de firma solo acepta un certificado de respaldo desde su propio directorio de proyecto, nunca desde un ancestro arbitrario, porque un certificado con el mismo nombre encontrado más arriba en el árbol es una sorpresa de seguridad, no una comodidad
La segunda trampa sobrevive a todos los ports y merece llevarse a cualquier proyecto FPC. Tras una actualización del compilador, no basta con que el compilador rechace los ficheros PPU obsoletos, porque el enlazador sigue prefiriendo los ficheros de objetos sobrantes en la ruta de búsqueda de unidades incluso cuando el PPU que cargó venía del directorio correcto, y añadir una ruta de salida de objetos explícita no anula esa preferencia. La única respuesta fiable es un directorio temporal de unidades fresco en cada ronda de compilación. Cualquier cosa menos que eso produce un binario enlazado desde dos versiones del compilador, que falla de formas que parecen bugs del código fuente
Las condicionales de plataforma son la última pieza, y elegir el eje correcto importa más de lo que parece. La pregunta correcta suele ser si el código es específico de Windows en lugar de si una biblioteca de widgets concreta está presente, como mostró el trabajo de conversión de metafiles en la importación vectorial EMF y las condicionales de plataforma: cambiar ese guard de una condición de biblioteca de controles a una condición de plataforma convirtió una supuesta reescritura en un cambio de una directiva. El soporte de Free Pascal y Lazarus para ambos objetivos Windows se envía con la biblioteca PDF Delphi PDFlibPas, construida desde las mismas fuentes que los paquetes de Delphi y C++Builder