PDFium Component puede abrir un PDF que vive dentro de un búfer más grande directamente desde un rango de bytes. La sobrecarga LoadDocument(const Data: TBytes; Index, Count: Integer; Buffered: Boolean) direcciona una ventana en su lugar, así que no se necesita un Copy preliminar. A cambio te pide entender una regla: cuando Buffered es False, el arreglo subyacente se toma prestado, no se copia
Este es un mecanismo diferente del enfoque impulsado por callback descrito en streaming de PDFs grandes bajo demanda con PDFium VCL, que le entrega a PDFium un lector FPDF_FILEACCESS y le deja extraer bloques del disco según los necesita. Ese es para documentos demasiado grandes para caber en RAM. Este es para documentos que ya están en RAM, en un desplazamiento conocido dentro de otra cosa. Los dos son complementarios, y la última sección explica cuál situación pertenece a cuál
La copia de 40 MB que nadie pidió
El escenario aparece donde sea que los PDFs viajen dentro de otros formatos. Un almacén de correo mantiene cuerpos de mensajes y adjuntos en un registro. Un contenedor de archivo concatena un manifiesto, algunas imágenes y un PDF. Un protocolo de cable personalizado enmarca un documento detrás de un encabezado prefijado con longitud. En cada caso terminas sosteniendo un TBytes grande y sabiendo que el PDF comienza en el byte 1 182 336 y corre por 312 kilobytes
Antes de que existiera la sobrecarga de rango de bytes, la respuesta idiomática era Copy(Data, Index, Count), que asigna un segundo arreglo y hace memcpy de la ventana hacia él. Luego le entregas esa porción a LoadDocument con Buffered = True, que la copia de nuevo al búfer privado del componente. Dos copias de los mismos bytes, una de ellas puro ceremonial, y en un escaneo de buzón grande repetido por cada mensaje. La sobrecarga de rango de bytes elimina la primera copia incondicionalmente y la segunda opcionalmente
Qué hace realmente la sobrecarga de rango de bytes
La sobrecarga es delgada por diseño: valida, calcula un puntero, y delega a la forma de puntero de LoadDocument por la que toda la familia ya se canaliza. Index es base cero, Count es una longitud de bytes, y Buffered por defecto es True exactamente como en las otras sobrecargas. El LoadDocument(const Data: TBytes; Buffered: Boolean) de un solo argumento ahora es en sí una llamada a este con Index = 0 y Count = Length(Data), así que hay una sola ruta de validación en vez de dos
Llamarlo se ve como el código que ya estabas escribiendo, menos la porción
var
Frame: TBytes; // whole container record, tens of megabytes
Offset, Size: Integer;
begin
Frame := LoadContainerRecord('mailbox.dat');
LocateEmbeddedPdf(Frame, Offset, Size); // your container parser
// No Copy(Frame, Offset, Size) here - the window is addressed in place
Pdf.LoadDocument(Frame, Offset, Size, True);
try
RenderPreview(Pdf);
finally
Pdf.UnloadDocument;
end;
end;
¿Por qué Index más Count desborda la verificación de límites?
Porque Index y Count son ambos Integer, y la suma de dos valores Integer positivos grandes no es necesariamente un Integer positivo grande. Este es el núcleo técnico de la sobrecarga, y es el único lugar donde una verificación de apariencia natural es un agujero de seguridad de memoria. La formulación obvia está mal
// WRONG: Index + Count is evaluated in Integer and can wrap negative
if Index + Count <= Length(Data) then
DataPtr := @Data[Index];
// RIGHT: reject signs first, then bound each term separately,
// with the only arithmetic done as a subtraction that cannot wrap
Check(Index >= 0, 'PDF byte range index cannot be negative');
Check(Count >= 0, 'PDF byte range count cannot be negative');
Check(Index <= Length(Data), 'PDF byte range index exceeds data length');
Check(Count <= Length(Data) - Index, 'PDF byte range exceeds data length');
Sigue el caso de fallo. Toma Index = 2000000000 y Count = 2000000000. Su suma real es cuatro mil millones, pero en aritmética con signo de 32 bits el resultado se desborda a exactamente menos 294 967 296. Ese valor está cómodamente por debajo de Length(Data), así que la verificación equivocada pasa, @Data[Index] se toma muy fuera del arreglo, y a PDFium se le entrega un puntero salvaje más una longitud de dos gigabytes. Lo que sigue es una violación de acceso en un buen día y análisis silencioso de memoria de proceso no relacionada en uno malo
El orden correcto arregla esto nunca sumando. Los negativos se rechazan antes de indexar nada, así que @Data[Index] nunca puede tomarse por debajo del arreglo. Luego Index se limita por sí mismo contra Length(Data), lo que garantiza que Length(Data) - Index sea un Integer no negativo. Solo entonces se compara Count contra ese resto. Cada valor intermedio permanece dentro del rango representable, así que ninguna configuración de build puede cambiar el resultado. No te sientas tentado a confiar en la verificación de desbordamiento {$Q+} como la red de seguridad tampoco: los builds de release rutinariamente se envían con ella apagada, e incluso cuando está encendida has convertido un error de seguridad de memoria en un EIntOverflow escapando del medio de una rutina de validación. PDFium Component trata la aritmética de longitud no confiable de la misma manera que trata el resto del límite, una disciplina cubierta más ampliamente en reforzar el ABI de PDFium VCL y la seguridad de memoria en Delphi
¿Por qué una ventana de longitud cero debe pasar nil?
Porque @Data[Index] no es una expresión legal para cada Index que la validación acepta. Index = Length(Data) con Count = 0 es una ventana vacía perfectamente bien formada al final del búfer, y un TBytes vacío da Index = 0 en un arreglo que no tiene elemento cero en absoluto. Tomar la dirección en cualquiera de los dos casos indexa más allá del final, o desreferencia un arreglo dinámico nil. Así que la sobrecarga se ramifica: Count = 0 produce un puntero nil, cualquier otro conteo produce @Data[Index]. El nil luego fluye hacia la sobrecarga de puntero, cuya propia guardia acepta un puntero nil cuando el tamaño es cero, y la carga termina en el error ordinario "Cannot load PDF document" en vez de una violación de acceso. Un llamador que calculó una ventana de cero bytes a partir de un contenedor malformado obtiene un EPdfError limpio y capturable como cualquier otra entrada mala
Prestado o copiado: lo que Buffered decide
Buffered selecciona el contrato de propiedad, y es el único parámetro aquí con consecuencias más allá de la llamada. Con Buffered = True, PDFium Component copia la ventana seleccionada, y solo la ventana, a su búfer interno antes de cargar. El contenedor de 40 MB no se copia; el PDF de 312 KB sí. Una vez que LoadDocument retorna puedes liberar, reutilizar o sobrescribir el contenedor de inmediato, porque el componente ya no lo referencia. Este es el valor por defecto y la elección correcta para casi todo el código
Buffered = False pasa @Data[Index] directamente a FPDF_LoadMemDocument64, y PDFium mantiene ese puntero durante toda la vida del documento en vez de copiar los bytes. Eso hace la carga libre de asignaciones, y hace de todo el TBytes subyacente un recurso prestado. Debe permanecer vivo y sin modificar hasta que UnloadDocument se ejecute o Active se vuelva False. No la ventana, todo el arreglo: un arreglo dinámico tiene conteo de referencias como unidad, y dejar que la última referencia se vaya en cualquier parte de tu código libera la memoria que PDFium todavía está leyendo. Establecer Length en él es igual de fatal, porque una reasignación puede mover el bloque. Declara esto en tu propia documentación de API dondequiera que expongas tal carga, en el mismo espíritu que cualquier otro límite de prestar-versus-poseer en código Pascal; el modo de fallo es idéntico a los peligros de aliasing descritos en la fuga de FillChar y cadena de resultado en Delphi, donde un búfer parece poseído y no lo está
type
TFrameSession = class
private
FFrame: TBytes; // owns the backing storage for as long as FPdf is loaded
FPdf: TPdf;
public
procedure OpenEmbedded(Offset, Size: Integer);
destructor Destroy; override;
end;
procedure TFrameSession.OpenEmbedded(Offset, Size: Integer);
begin
// Buffered = False: FFrame must outlive the loaded document
FPdf.LoadDocument(FFrame, Offset, Size, False);
end;
destructor TFrameSession.Destroy;
begin
FPdf.UnloadDocument; // release the borrow first
FFrame := nil; // only now may the storage go
inherited;
end;
Cuándo la ventana de rango de bytes es la herramienta equivocada
Sé honesto sobre el límite. La sobrecarga de rango de bytes asume que el contenedor ya está completamente en memoria, y Count es un Integer, así que una sola ventana no puede exceder dos gigabytes. Si el contenedor es un archivo de 6 GB en disco, o llega a través de un socket que no puedes rebobinar, esta sobrecarga no puede ayudarte y leer todo en TBytes solo para direccionar una ventana dentro de él derrota el propósito. Ahí es precisamente donde pertenece la ruta FPDF_FILEACCESS, y el artículo de streaming bajo demanda muestra cómo exponer una vista con desplazamiento de un archivo como una fuente de documento personalizada. Igualmente, si los bytes incrustados necesitan transformación antes de que PDFium los vea, descompresión, descifrado, un paso de desenvoltura, entonces una copia real es inevitable y Buffered = True sobre el arreglo transformado es la respuesta honesta. La ventana de rango de bytes rinde beneficios exactamente en una forma: bytes de PDF contiguos, sin modificar, ya residentes, en un desplazamiento conocido
Si estás evaluando esto para un visor, un panel de vista previa o un pipeline de ingesta por lotes, la sobrecarga de rango de bytes y el cargador de streaming son dos de las estrategias de carga que PDFium Component ofrece junto con cargas de archivo, flujo y puntero crudo. La superficie completa de la API, licenciamiento y soporte de versión de Delphi y C++Builder están documentados en la página de producto de PDFium Component