Cuando pdfium.dll se niega a cargarse en la máquina de un cliente, PDFium Component para Delphi y Lazarus convierte el críptico error nativo en una causa específica. La rutina CheckLoadLibrary interpreta ERROR_BAD_EXE_FORMAT (193) como un conflicto de arquitectura de 32/64 bits, y CheckGetProcAddress informa de una exportación faltante cuando pdfium.dll es más antigua que la vinculación (binding). Ambos mensajes indican la solución en lugar de dejarle adivinar
Eso es importante porque una DLL nativa falla de tres maneras distintas, y el texto sin procesar del sistema operativo las confunde todas. La arquitectura puede ser incorrecta, el binario desplegado puede ser demasiado antiguo para la vinculación de Pascal que lo llama, o dos hilos pueden competir para vincularlo al mismo tiempo. Cada uno tiene una solución diferente, y el objetivo principal de los diagnósticos añadidos en el trabajo del ciclo de vida de carga de la versión 2.11.0 es indicarle a cuál de ellos se enfrenta realmente
¿Por qué pdfium.dll no se carga con un error de formato EXE no válido?
El error 193 de Windows, ERROR_BAD_EXE_FORMAT, significa que el archivo se encontró y abrió, pero su tipo de máquina PE no coincide con el proceso anfitrión. Un ejecutable de 64 bits no puede cargar una pdfium.dll de 32 bits, y un ejecutable de 32 bits no puede cargar una de 64 bits. El archivo está presente, por lo que el instinto de buscar una DLL faltante le envía exactamente en la dirección equivocada
PDFium Component intercepta este caso en CheckLoadLibrary: cuando LoadLibrary devuelve un controlador nulo y GetLastError es 193, añade una sugerencia de que la DLL se encontró pero su arquitectura no coincide con la del proceso anfitrión, y apunta a la compilación correspondiente en DLLs/Win32 o DLLs/Win64. El subdirectorio es elegido por BuildDllSubDir, que devuelve Win64 cuando IsWin64 is true y Win32 en caso contrario. Despliegue la DLL bajo la estructura que la vinculación realmente busca y el conflicto desaparecerá
uses
SysUtils, PDFium;
procedure OpenDocument(const AFileName: string);
var
Pdf: TPdf;
begin
// Deploy pdfium.dll next to the executable, matched to the build target:
// <AppDir>\DLLs\Win32\pdfium.dll for a 32-bit host process
// <AppDir>\DLLs\Win64\pdfium.dll for a 64-bit host process
Pdf := TPdf.Create(nil);
try
Pdf.FileName := AFileName;
try
Pdf.Active := True; // first use lazily binds pdfium.dll
except
on E: EPdfError do
// The message already names the real cause: a 32/64-bit mismatch
// (ERROR_BAD_EXE_FORMAT) or a pdfium.dll older than this binding
raise Exception.CreateFmt('PDF engine did not start: %s', [E.Message]);
end;
if Pdf.Active then
RenderFirstPage(Pdf);
finally
Pdf.Free;
end;
end;
¿Qué le indica una exportación faltante de PDFium?
La segunda clase de fallo es la discrepancia de versiones. PDFium publica nuevas exportaciones a lo largo del tiempo, y la vinculación de Pascal resuelve cada función que necesita a través de CheckGetProcAddress durante LoadLibrary. Si una exportación requerida no está presente, la vinculación es más reciente que la pdfium.dll en el disco, y el diagnóstico honesto es que la DLL desplegada está obsoleta en lugar de corrupta. La función CheckGetProcAddress indica precisamente eso: genera un EPdfError informando que la pdfium.dll desplegada es más antigua que esta compilación de la vinculación PDFiumPas, e indica la ruta DLLs/<subdir> donde corresponde colocar el binario coincidente
Dos detalles hacen que esa ruta sea sólida. En primer lugar, CheckGetProcAddress llama a UnloadLibrary antes de generar el error, por lo que una vinculación parcial nunca deja punteros de función medio resueltos para que el próximo intento tropiece con ellos. En segundo lugar, el nombre de la DLL que reporta proviene de BuildDllName, que devuelve pdfium.dll normalmente y pdfium.v8.dll cuando la bandera global EnableV8Engine está configurada. Si su aplicación habilita el motor de JavaScript V8 para formularios XFA o con scripts, el texto del error apunta a la compilación V8, no a la normal, para que reemplace el archivo correcto a la primera
¿Es seguro cargar la DLL PDFium desde un hilo de fondo?
Cargar la biblioteca ahora es seguro desde cualquier hilo; llamar a la API de PDFium desde varios hilos todavía no lo es. Esas son dos garantías separadas, y mantenerlas separadas representa la diferencia entre un grupo de trabajadores estable y un bloqueo intermitente. El renderizado en segundo plano normalmente ejecuta cada renderizado de página a través de un trabajador TPdfFuture<T>, y el primer futuro que interactúa con TPdf es lo que desencadena la vinculación diferida. Sin protección, dos trabajadores podrían observar una biblioteca no cargada, ejecutar la secuencia de vinculación, sobrescribir el controlador del módulo y filtrar la primera carga
Lo que el bloqueo de carga no protege
El trabajo de la versión 2.11.0 cierra esa ventana de oportunidad con PDFiumLoadLock, una TRTLCriticalSection global del proceso que envuelve la entrada tanto a LoadLibrary como a UnloadLibrary. La sección crítica hace que la secuencia de comprobación y posterior carga sea atómica, por lo que no puede ocurrir que dos hilos vean Loaded=False y realicen una doble vinculación, y ningún hilo puede liberar la DLL mientras otro está a mitad del proceso. El bloqueo se crea en la sección de initialization de la unidad y se destruye en finalization, protegido por una bandera PDFiumLoadLockReady para que el emparejamiento sea seguro incluso durante el apagado. Si necesita el patrón completo de trabajador y respuesta con cancelación, el artículo complementario sobre renderizado en segundo plano con futuros cancelables lo detalla de principio a fin
uses
PDFium, FPdfAsync;
// Worker method runs on a background thread. The first future to bind
// pdfium.dll is serialised by PDFiumLoadLock, so a second concurrent
// worker cannot double-bind or leak the module handle.
function TReportForm.RenderThumbnail(
const AToken: IPdfCancellationToken): TBitmap;
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True; // safe concurrent bind
AToken.ThrowIfCancelled;
Result := Pdf.Thumbnail; // this TPdf is owned by one thread only
finally
Pdf.Free;
end;
end;
procedure TReportForm.StartRender;
begin
TPdfFuture<TBitmap>.Run(RenderThumbnail, ThumbnailReady);
end;
procedure TReportForm.ThumbnailReady(
const AResult: TPdfFutureResult<TBitmap>);
begin
if AResult.IsSuccess then
Preview.Picture.Assign(AResult.Value);
end;
Vale la pena exponer claramente el límite aquí, porque es fácil malinterpretar la solución. PDFiumLoadLock serializa únicamente el estado de vinculación global: el controlador del módulo y las asignaciones de punteros de función FPDF_*. La API C de PDFium subyacente sigue sin ser segura para hilos, tal como declaran los encabezados originales, por lo que llamar a las funciones FPDF_* o compartir un FPDF_DOCUMENT entre hilos sigue siendo su responsabilidad serializar. La estructura segura es la anterior: cada trabajador posee su propio TPdf y nunca entrega un documento activo a otro hilo. Para un análisis más profundo de ese límite y las reglas de ABI a su alrededor, consulte protección de la vinculación PDFium VCL contra fallos de memoria y ABI
Por qué son importantes la finalización y FPDF_DestroyLibrary
Un vacío más sutil se encontraba en la ruta de apagado. La unidad PDFium no tenía una sección de finalization, lo que significaba que FPDF_DestroyLibrary nunca se ejecutaba al salir del proceso; el sistema operativo reclamaba la memoria de la DLL y omitía la propia limpieza de PDFium, concretamente la unión del hilo de trabajo, la liberación del entorno aislado (isolate) de V8 cuando se configura EnableV8Engine, y el desmontaje de fuentes y caché. En una carga de trabajo de renderizado simple, esto representa una fuga silenciosa en el desmontaje, pero con el motor V8 habilitado, filtra un entorno aislado de JavaScript en cada ejecución, lo que se nota rápidamente bajo una automatización repetida
La sección de finalization ahora llama a UnloadLibrary, que invoca a FPDF_DestroyLibrary y libera el módulo. Esto reside en el ámbito de la unidad por una razón: TPdf.Destroy solo establece Active := False to close el documento actual, y deliberadamente no descarga la biblioteca global, de modo que una aplicación de múltiples instancias mantiene una vinculación de PDFium compartida activa durante toda la vida del proceso. Colocar el desmontaje en la sección de finalization significa que la biblioteca compartida se libera exactamente una vez, cuando la unidad se descarga, y UnloadLibrary comprueba primero su bandera Loaded para que la llamada sea una instrucción inactiva inofensiva si PDFium nunca se utilizó
Una breve lista de comprobación de despliegue
Tres hábitos evitan casi cualquier fallo de carga en el entorno real. Haga coincidir la arquitectura de la DLL con el proceso anfitrión y distribúyala bajo DLLs/Win32 or DLLs/Win64; mantenga la pdfium.dll sincronizada con la versión de la vinculación para que no falte ninguna exportación requerida; y nunca comparta un TPdf o un controlador de documento entre hilos, aunque la vinculación en sí sea ahora atómica. Si compila el mismo código fuente tanto para Delphi como para Lazarus, las diferencias de empaquetado que complican el despliegue multiplataforma se detallan en las notas sobre dificultades del compilador cruzado Delphi y FPC. Los diagnósticos, el bloqueo de carga y la limpieza de finalización descritos aquí se distribuyen en PDFium Component para Delphi y C++Builder