Delphi Linux64 sigue el modelo de datos LP64, así que allí Longint y LongWord ocupan 8 bytes, mientras que Delphi en Windows (Win32 y Win64) y Free Pascal en todos sus objetivos los mantienen en 4. Portar PDFlibPas, la PDF Library para Delphi, a dcclinux64 choca con esa diferencia de tres formas distintas, además de una trampa de ancho de puntero: un test {$IFDEF WIN64} que elige un entero del tamaño de un puntero escoge en silencio el ancho equivocado en todos los objetivos de 64 bits que no son Windows
Ninguna de estas trampas es exótica. Cada una es un hábito que Windows vuelve seguro por accidente: escribir Integer donde la RTL dice Longint, reflejar una estructura de C con campos Longint, o preguntarse «¿esto es Win64?» cuando la pregunta real es «¿un puntero ocupa 8 bytes?». El código puede arrastrar esos hábitos durante años sin consecuencia, porque en Win32, Win64 y FPC las dos grafías significan lo mismo. En Delphi Linux64 dejan de significar lo mismo, y el compilador es el primero en enterarse
Todo lo que sigue va de lo que el compilador caza. PDFlibrary.pas compila con dcc32, dcc64 y dcclinux64 desde un único árbol de fuentes, y estos son los problemas que se interponen entre los hábitos exclusivos de Windows y ese tercer objetivo
¿Qué cambia cuando Delphi se compila para Linux64?
Los tipos enteros que cambian de ancho en Delphi Linux64 son exactamente Longint y LongWord; Integer y Cardinal se quedan en 4 bytes en todas las plataformas de Delphi, y NativeInt sigue al puntero. Windows es LLP64, donde en 64 bits solo crecen los punteros. Los objetivos POSIX de 64 bits de Delphi (Linux64 y los compiladores de 64 bits de macOS e iOS) son LP64, donde el long de C también crece, y Embarcadero hizo que el Longint de Pascal lo acompañara
| Tipo | Delphi Win32 | Delphi Win64 | Delphi Linux64 | Free Pascal |
|---|---|---|---|---|
Integer / Cardinal | 4 | 4 | 4 | 4 |
Longint / LongWord | 4 | 4 | 8 | 4 en todos los objetivos |
NativeInt / Pointer | 4 | 8 | 8 | el tamaño de puntero del objetivo |
C long (ABI de la plataforma) | 4 | 4 | 8 | sigue la ABI del SO |
La columna de Free Pascal es la trampa para una biblioteca multi-compilador. PDFlibPas ya distribuye una compilación Linux vía FPC, y FPC mantiene Longint en 32 bits en Linux x86-64. O sea que «funciona en Linux» era cierto y no nos decía nada sobre Delphi Linux64. El backend no-Windows de FPC resultó ser la base correcta para el port a Delphi, pero los tamaños de tipo que hay debajo difieren
¿Por qué un check de WIN64 trunca punteros en Linux?
La respuesta corta: WIN64 significa «Windows de 64 bits», no «64 bits», y tratar ambos como sinónimos elige el ancho de entero equivocado. PDFlibDither, un ditherer de difusión de error Floyd-Steinberg que reduce un bitmap de 24 bits a pocos niveles por canal, recorre tres filas de términos de error con aritmética de punteros cruda, y el entero del tamaño de un puntero detrás de esa aritmética tiene que seguir el ancho de la CPU, no el del sistema operativo. El condicional ingenuo se equivoca:
// Ingenuo: el ancho lo elige el SO, así que todos los objetivos de 64 bits no-Windows obtienen Integer
{$IFDEF WIN64}
Type
OSPtr= IntPtr;
{$ELSE}
Type
OSPtr= Integer;
{$ENDIF}
// El barrido serpenteante avanza los punteros de fila de error un término cada vez
Inc(OSPtr(ErrorR), SizeOf(TErrorTerm)); // FPC x86-64: reescribe solo los 32 bits bajos
Dec(OSPtr(ErrorG), SizeOf(TErrorTerm)); // dcclinux64: E2064, left side cannot be assigned to
// Correcto: un tipo del tamaño de un puntero en todas partes
Type
OSPtr= TPLIntPtr; // PDFlibTypes: NativeInt fuera de Windows, IntPtr / Integer en Win64 / Win32
Con ese condicional, OSPtr se resuelve en un Integer de 4 bytes en FPC para Linux x86-64, macOS x64 y ARM64, y Inc(OSPtr(ErrorR), ...) reinterpreta los primeros 4 bytes de un puntero de 8 bytes como un entero y los incrementa. En una CPU little-endian eso es la mitad baja de la dirección. La mitad alta jamás se toca, y ahí está lo bien que se esconde el error: el resultado es correcto justo hasta que los 32 bits bajos dan la vuelta, momento en el que el puntero salta silenciosamente 4 GiB hacia atrás. FPC compila el typecast de variable sin quejarse. Delphi rechaza un typecast de variable a un tipo de tamaño distinto a la izquierda de una asignación, así que dcclinux64 se para en E2064 en el primer Inc
Esa es la asimetría útil aquí: un compilador que rechaza el typecast encuentra un error de ancho de puntero que otro compilador acepta en silencio, antes de que el código haya ejecutado una sola línea en el nuevo objetivo. PDFlibPas usa TPLIntPtr de PDFlibTypes para esto, que comprueba OS_WINDOWS primero y solo después distingue Win64 de Win32, de modo que los objetivos no-Windows siempre obtienen NativeInt
La regla que nos llevamos: nunca infieras el ancho del puntero desde un define de sistema operativo. Si necesitas un entero del tamaño de un puntero, usa NativeInt / NativeUInt (los tienen tanto Delphi como FPC) o un alias de proyecto construido sobre ellos, y córreate el condicional entero. Si de verdad necesitas ramificar por ancho en código Delphi, CPU64BITS es el define que responde la pregunta que estás haciendo de verdad
Overrides de TStream: copia la firma de la RTL, carácter por carácter
Un descendiente de TStream en Delphi Linux64 debe declarar e implementar Read, Write y SetSize con parámetros Longint, exactamente como los declara la RTL, porque Integer ya no es el mismo tipo. PDFlibPas tiene su propio TPDFlibWideFileStream para nombres de archivo Unicode, y su declaración de clase sí usaba Longint. La sección de implementation usaba Integer
Type
TPDFlibWideFileStream= Class(TStream)
Protected
Procedure SetSize(NewSize: Longint); Override;
Public
Function Read(Var Buffer; Count: Longint): Longint; Override;
Function Write(Const Buffer; Count: Longint): Longint; Override;
End;
// Antes: aceptado durante años en Win32, Win64 y FPC, donde Integer = Longint
Function TPDFlibWideFileStream.Read(Var Buffer; Count: Integer): Longint;
// Después: la cabecera de implementación repite la declaración exactamente
Function TPDFlibWideFileStream.Read(Var Buffer; Count: Longint): Longint;
Begin
Result:= FileRead(FHandle, Buffer, Count);
If Result= - 1 Then
Result:= 0;
End;
En todos los compiladores que la biblioteca había conocido, Integer y Longint eran el mismo tipo de 4 bytes, así que una cabecera descuadrada era indistinguible de una que encajaba. En Delphi Linux64 la implementación ya no encaja con su propia declaración y la unidad deja de compilar. Este duele más de lo que su tamaño sugiere, porque PDFlibExtra es una unidad base de la que depende casi toda la biblioteca, así que una sola cabecera de implementación descuadrada deja sin compilar a la mayoría de las unidades
El mismo razonamiento funciona al revés para código nuevo. Cuando sobreescribes un método de la RTL, copia sus tipos de parámetro literalmente, aunque «sepas» que dos tipos son el mismo. Delphi Linux64 es sencillamente el primer objetivo donde ese saber deja de ser cierto
¿Qué registros necesitan Integer en vez de Longint, y cuáles deben conservarlo?
Un registro que refleja un formato de archivo o una estructura de Windows debe usar tipos de ancho fijo (Integer, Cardinal, Int32), mientras que un registro que refleja una estructura de C declarada con long debería conservar Longint / LongWord. Los dos casos se ven idénticos en el fuente y necesitan respuestas opuestas, porque el LONG de Windows siempre es de 32 bits y el long de C es lo que diga la ABI de la plataforma
Type
// Uniscribe GOFFSET: dos LONG de Windows, siempre 8 bytes en total
TGoffset= Packed Record
du: Integer; // era Longint: el registro creció a 16 bytes en Delphi Linux64
dv: Integer;
End;
// EMF LOGCOLORSPACE: cabecera rellenada con un Move fijo de 68 bytes desde los datos del registro
TPLEMFLogColorSpace= Packed Record
Signature, Version, Size, ColorType, Intent: Cardinal;
Endpoints: Array[0..8] Of Integer; // era Longint: 36 bytes se convertían en 72
Gamma: Array[0..2] Of Cardinal;
Filename: Array[0..259] Of WideChar;
End;
// PDFium FPDF_FILEACCESS: m_FileLen es un unsigned long de C, así que LongWord es lo correcto
FPDF_FILEACCESS= Record
m_FileLen: LongWord; // 4 bytes en Windows, 8 en LP64, igual que el lado C
m_GetBlock: TFPDF_GetBlock;
m_Param: Pointer;
End;
El caso EMF muestra lo calladamente que esto falla. El importador copia 68 bytes a TPLEMFLogColorSpace: 20 bytes de cabecera, 36 bytes de endpoints CIE, 12 bytes de gamma. Con endpoints Longint en LP64, esos mismos 68 bytes caerían en un array de endpoints de 72 bytes, los valores de gamma nunca se escribirían y nada lanzaría un error. Ambos registros viven en rutas de código exclusivas de Windows y usan tipos de ancho fijo de todas formas, porque un registro que refleja bytes en disco no debería depender de qué compilador lo compile en cada momento
El binding de PDFium es el contraejemplo. Su m_FileLen refleja un unsigned long de C, que en Windows es de 32 bits y en LP64 de 64 bits, y LongWord sigue eso con exactitud en ambos. Reescribirlo como Cardinal en una pasada de limpieza lo rompería en el momento en que el host de PDFium gane un loader POSIX. El host de PDFium es exclusivo de Windows, así que esto es una nota para el futuro. El lado de convenciones de llamada de los bindings de C tiene trampas propias, cubiertas en el artículo sobre los bindings DLL, ActiveX y dylib de la PDF Library
Una guarda barata para cualquier registro que refleje bytes: verifica su tamaño con un assert al arrancar, por ejemplo Assert(SizeOf(TGoffset) = 8) en una sección de initialization. No cuesta nada y convierte un cambio silencioso de layout en un fallo inmediato en la primera ejecución de un objetivo nuevo
Ajustar los defines condicionales: DELPHI_LINUX, Android y Types
Antes de que pudiera asomar cualquiera de los problemas de tipos, Delphi en Linux tenía que dejar de hacerse pasar por Windows. PDFlibDefines.inc no definía nada para Delphi Linux, así que el compilador caía directo en los valores por defecto de Windows/VCL, y el primer error era que no encontraba Vcl.Graphics. Los defines encaminan Delphi Linux al mismo backend que FPC usa en objetivos no-Windows: renderizado Cairo, fontconfig, OpenSSL y libcurl cargados en tiempo de ejecución
{$IFDEF LINUX}
{$IFNDEF FPC}
{$IFNDEF ANDROID}
{$DEFINE DELPHI_LINUX}
{$DEFINE NOVCL}
{$DEFINE MD5_MAC}
{$DEFINE NOCRYPTAPI}
{$DEFINE USE_Cairo}
{$ENDIF}
{$ENDIF}
{$ENDIF}
Dos detalles de ese bloque se pasan por alto con facilidad. La exclusión de ANDROID está ahí porque Delphi también define LINUX para objetivos Android, y este backend no cubre Android. Y el define nuevo está deliberadamente separado de la rama existente de Delphi para macOS (OS_MAC sin FPC), que depende de unidades Macapi; el define de Linux se construye sobre la ruta no-Windows de FPC
El otro error recurrente era que TPoint y TRect quedaban sin declarar. En Windows esos tipos llegan a casi todas las unidades indirectamente, a través de la unidad Windows. En Delphi Linux no hay unidad Windows en la cláusula uses, así que unidades como PDFlibFont y PDFlibPageTree ahora listan Types explícitamente bajo DELPHI_LINUX. Si tu propio código compila en Windows solo porque Windows arrastra System.Types consigo, espera el mismo muro. El patrón general del código que solo funciona en un compilador por casualidad es el tema de cinco hábitos de Delphi que solo funcionan por accidente hasta que FPC los destapa
Donde el compilador deja de ayudar
Los problemas LP64 de arriba son los que un compilador puede encontrar. Los que no puede encontrar, como un registro del tamaño equivocado que nunca pasa por un typecast, solo aparecen cuando los tests se ejecutan de verdad en el objetivo, que es exactamente para lo que sirven los asserts de tamaño descritos arriba
Si lo que te trajo al código de dithering era la salida a 1 bit, la ruta soportada para páginas renderizadas es otra: la conversión descendente a halftone por GDI fuera del renderer, descrita en renderizar páginas PDF a bitmaps monocromos de 1 bit
El trabajo multi-compilador descrito aquí vive en la PDF Library para Delphi, que hoy se dirige a Delphi y C++Builder en Windows más compilaciones headless con Free Pascal en Linux y macOS; la página del producto detalla la lista completa de compiladores y plataformas soportados