Вопрос, поднятый в данном запросе, касается синхронизации потоков в Delphi для обеспечения безопасной загрузки динамически подключаемой библиотеки (DLL). Основная проблема заключается в том, что необходимо, чтобы только один поток выполнил загрузку DLL, и если попытка загрузки не удалась, другие потоки не должны пытаться это делать повторно. Для синхронизации используется критический раздел, но, по словам пользователя, он работает не так, как ожидается.
Описание проблемы
Разработчик работает с Delphi-проектом, в котором используется DLL, которая должна быть вызвана из основного пользовательского интерфейса или из фоновых потоков. Загрузка и освобождение DLL должны происходить только один раз: при первом вызове из любого потока или интерфейса и при завершении работы приложения соответственно. Для синхронизации используется критический раздел, но он не работает должным образом: загрузка DLL происходит не один раз, как задумано.
Пример кода
program Project1;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils,
Classes;
const
MyDLL = 'MyDLL.dll';
type
TDLLProcessProc = function(A: Integer): Integer; stdcall;
var
DLLProc: TDLLProcessProc = nil;
DLLModule: HMODULE = 0;
DLLInitialized: Boolean = False;
CS: TRTLCriticalSection;
procedure InitDLLByFirstCall;
begin
// Код загрузки DLL с использованием критического раздела
end;
function DLLProcess(A: Integer): Integer;
begin
// Инициализация DLL перед вызовом процедуры
end;
type
TDLLThread = class(TThread)
private
FNum: Integer;
public
constructor Create(CreateSuspended: Boolean; ANum: Integer);
procedure Execute; override;
end;
constructor TDLLThread.Create(CreateSuspended: Boolean; ANum: Integer);
begin
FreeOnTerminate := True;
FNum := ANum;
inherited Create(CreateSuspended);
end;
procedure TDLLThread.Execute;
begin
// Вызов процедуры DLL из потока
end;
begin
InitializeCriticalSection(CS);
try
// Создание потоков
finally
DeleteCriticalSection(CS);
end;
end.
Подтвержденный ответ
Проблема заключается в неправильной реализации двойной проверки (double-checked locking). Необходимо изменить код так, чтобы переменная, проверяемая в начале InitDLLByFirstCall, устанавливалась только после завершения всех инициализаций. Использование переменной DLLModule для проверки не является хорошей идеей, так как она может быть не NULL, в то время как DLLProc все еще может быть NULL.
Также следует использовать ту же самую переменную состояния и внутри, и снаружи критического раздела. Если использовать DLLInitialized, то нет необходимости в переменных DLLInitialized_OK и DLLModule.
Для упрощения кода и облегчения его восприятия рекомендуется использовать минимальное количество переменных. Пример кода, который должен работать:
var
DLLProc: TDLLProcessProc = nil;
DLLInitialized: Boolean = False;
CS: TRTLCriticalSection;
procedure InitDLLByFirstCall;
var
DLLModule: HMODULE;
begin
if DLLInitialized then
Exit;
EnterCriticalSection(CS);
try
if not DLLInitialized then
try
DLLModule := LoadLibrary(MyDLL);
Win32Check(DLLModule <> 0);
DLLProc := GetProcAddress(DLLModule, 'Process');
Win32Check(Assigned(DLLProc));
finally
DLLInitialized := True;
end;
finally
LeaveCriticalSection(CS);
end;
end;
function DLLProcess(A: Integer): Integer;
begin
InitDLLByFirstCall;
if @DLLProc = nil then
raise Exception.Create('DLL was not initialized OK');
Result := DLLProc(A);
end;
Если не желаете проверять адрес функции внутри DLLProcess, можно использовать целочисленную переменную или перечисление для DLLInitialized, с различными значениями для состояний "не инициализировано", "ошибка" и "успешно".
Заключение
Для корректной работы с DLL в многопоточном приложении на Delphi необходимо правильно организовать синхронизацию потоков, используя критический раздел и двойную проверку. В данном случае важно правильно определить переменную состояния и убедиться, что она устанавливается после полного выполнения операций инициализации.
Разработчик сталкивается с проблемой синхронизации потоков при загрузке DLL в Delphi, где необходимо, чтобы загрузка произошла только один раз, несмотря на многопоточность.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS