Когда библиотека pdfium.dll отказывается загружаться на компьютере пользователя, компонент PDFium Component для Delphi и Lazarus преобразует неясную системную ошибку в конкретную понятную причину. Процедура CheckLoadLibrary интерпретирует код ошибки ERROR_BAD_EXE_FORMAT (193) как несовпадение архитектуры 32/64-бит, а CheckGetProcAddress указывает на отсутствие экспортируемой функции, если версия dll старше версии биндинга. Оба сообщения сразу предлагают способ решения проблемы вместо необходимости гадать
Это важно, поскольку нативная DLL может давать сбои в трех совершенно разных случаях, тогда как обычный текст системной ошибки объединяет их все. Ошибка может заключаться в несовпадении архитектуры, в том, что развернутый бинарный файл слишком стар для вызывающего его биндинга на Pascal, или в состоянии гонки двух потоков, пытающихся привязать ее в один момент. Каждый сценарий требует своего решения, и цель диагностических проверок, добавленных в версии v2.11.0, — сообщить вам, с чем именно вы столкнулись
Почему pdfium.dll не загружается с ошибкой некорректного формата EXE?
Системная ошибка Windows 193, ERROR_BAD_EXE_FORMAT, означает, что файл был найден и открыт, но тип архитектуры в заголовке PE не совпадает с типом процесса хоста. 64-битный исполняемый файл не может загрузить 32-битную версию pdfium.dll, а 32-битный процесс не может загрузить 64-битную библиотеку. Сам файл присутствует на диске, поэтому попытки найти отсутствующую DLL уведут вас в неверном направлении
Компонент PDFium Component перехватывает этот случай в методе CheckLoadLibrary: если функция LoadLibrary возвращает нулевой дескриптор, а GetLastError равен 193, компонент сообщает, что DLL была найдена, но ее архитектура не совпадает с процессом хоста, и указывает на соответствующую сборку в папке DLLs/Win32 или DLLs/Win64. Имя подкаталога определяется функцией BuildDllSubDir, которая возвращает Win64 при значении IsWin64 равном True, и Win32 в противном случае. Разверните DLL в каталоге, который фактически опрашивается биндингом, и проблема исчезнет
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;
О чем говорит отсутствие экспортируемой функции PDFium?
Второй тип сбоев — несоответствие версий. PDFium со временем добавляет новые экспортируемые функции, и биндинг на Pascal сопоставляет каждую необходимую функцию через метод CheckGetProcAddress при вызове LoadLibrary. Если требуемый экспорт отсутствует, значит, биндинг новее, чем библиотека pdfium.dll на диске. Диагностика в методе CheckGetProcAddress сообщает именно об этом: вызывается исключение EPdfError с сообщением, что развернутый файл pdfium.dll старше текущей сборки биндинга PDFiumPas, и указывается путь DLLs/<subdir>, где должен находиться соответствующий бинарный файл
Две детали делают этот путь надежным. Во-первых, CheckGetProcAddress вызывает UnloadLibrary перед генерацией исключения, благодаря чему частичная привязка никогда не оставляет не полностью инициализированные указатели функций, способные вызвать сбои при последующих вызовах. Во-вторых, имя DLL берется из BuildDllName, которая обычно возвращает pdfium.dll, но выдает pdfium.v8.dll при включенном глобальном флаге EnableV8Engine. Если ваше приложение активирует движок V8 JavaScript для форм XFA или интерактивных сценариев, текст ошибки укажет именно на сборку V8, что позволит вам сразу заменить нужный файл
Безопасно ли загружать PDFium DLL из фонового потока?
Загрузка библиотеки теперь полностью безопасна из любого потока, однако одновременный вызов API PDFium из нескольких потоков по-прежнему не поддерживается. Это два различных правила, и их разделение критично для стабильности пула потоков. Фоновый рендеринг обычно распределяет обработку страниц по задачам TPdfFuture<T>, и первая задача, обращающаяся к TPdf, инициирует ленивую привязку. Без защиты два рабочих потока могут обнаружить незагруженную библиотеку, одновременно запустить процедуру привязки, перезаписать дескриптор модуля и вызвать утечку памяти
В версии v2.11.0 эта проблема решена добавлением PDFiumLoadLock — глобальной критической секции процесса TRTLCriticalSection, которая защищает вызовы LoadLibrary и UnloadLibrary. Она делает последовательность проверки и загрузки атомарной, предотвращая двойную загрузку из разных потоков и блокируя выгрузку DLL одним потоком на время привязки в другом. Блокировка создается в секции initialization модуля и удаляется в finalization, защищенная флагом PDFiumLoadLockReady для безопасного завершения работы. Если вам нужен полный шаблон работы с асинхронными задачами и отменой вызовов, статья о фоновом рендеринге с возможностью отмены задач подробно разбирает этот сценарий
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;
Что не защищает блокировка загрузки
Важно четко очертить область действия блокировки, чтобы не допустить ошибок. Блокировка PDFiumLoadLock сериализует только глобальное состояние привязки: дескриптор модуля и сопоставление указателей функций FPDF_*. Лежащий в основе C API PDFium по-прежнему не является потокобезопасным, о чем прямо сообщают заголовочные файлы, поэтому синхронизация вызовов функций FPDF_* или совместное использование одного FPDF_DOCUMENT в разных потоках остается полностью на вашей стороне. Безопасная структура показана выше: каждый рабочий поток владеет своим экземпляром TPdf и никогда не передает дескрипторы документов другим потокам. Подробнее об этом ограничении и правилах ABI см. в статье об обеспечении безопасности памяти и ABI биндинга PDFium VCL в Delphi
Почему важны секция finalization и метод FPDF_DestroyLibrary
Небольшая недоработка существовала на этапе завершения процесса. Модуль PDFium не имел секции finalization, из-за чего метод FPDF_DestroyLibrary никогда не вызывался при закрытии приложения. Операционная система просто освобождала память DLL, пропуская внутренние процедуры очистки PDFium (завершение рабочих потоков, уничтожение V8 isolate при включенном EnableV8Engine, очистку кэшей и шрифтов). При обычном рендеринге это приводит к незначительным утечкам при выходе из программы, но при включенном движке V8 это вызывает утечки V8 isolate на каждом проходе, что быстро сказывается при регулярном использовании автоматизации
Секция finalization теперь вызывает UnloadLibrary, которая выполняет метод FPDF_DestroyLibrary и освобождает модуль. Это реализовано на уровне модуля: метод TPdf.Destroy лишь устанавливает Active := False для закрытия текущего документа, но намеренно не выгружает глобальную библиотеку, благодаря чему приложение с несколькими экземплярами TPdf сохраняет одну общую привязку PDFium на протяжении всей своей работы. Размещение очистки в секции finalization гарантирует, что общая библиотека будет освобождена ровно один раз при выгрузке модуля, а метод UnloadLibrary предварительно проверяет флаг Loaded, делая вызов безопасным, если PDFium вообще не использовался
Контрольный список развертывания
Три правила предотвращают практически любые проблемы загрузки на стороне пользователя. Сопоставляйте разрядность DLL с процессом хоста и поставляйте ее в каталогах DLLs/Win32 или DLLs/Win64; обновляйте файл pdfium.dll синхронно с версией биндинга, чтобы не допустить отсутствия функций; и никогда не используйте экземпляр TPdf или дескриптор документа в разных потоках одновременно, несмотря на атомарность самой процедуры привязки. Если вы собираете один проект для Delphi и Lazarus, особенности упаковки и кросс-платформенного развертывания описаны в статье о особенностях кросс-компиляции Delphi и FPC. Диагностика, блокировка загрузки и финальная очистка поставляются в составе компонента PDFium Component для Delphi и C++Builder