Процесс не может получить доступ к файлу, так как этот файл занят другим процессом... Но это не точно... Как показала практика, для программы на Delphi это действительно не точно.
При открытии файла в режиме "только для чтения" в параметре определяющем режим доступа к файлу обычно передают комбинацию "fmOpenRead or fmShareDenyNone". Но оказалось, что это избавляет только от части ошибок "Процесс не может получить доступ к файлу, так как этот файл занят другим процессом" или "The process cannot access the file because it is being used by another process".
Для начала немного теории. В Delphi режим доступа к файлу, определяется комбинацией константы режима открытия файла (fmOpen*) и константы разделения доступа к файлу (fmShare*). Эти константы описанны в модуле System.SysUtils. Приведу их значение для ОС Windows:
Теперь перейдем к практике. Попробуем в режиме fmOpenRead открыть все файлы в каталоге для хранения временных файлов (там большая вероятность встретить файл, который занят какими-нибудь процессом).
Вариант 1. Откроем файлы без указания режима разделения доступа
Вариант 2. Откроем файлы в режиме разделения доступа "fmShareDenyNone"
Метод TFileStream.Create приводит к функции FileOpen из System.SysUtils:
При открытии файла в режиме "только для чтения" в параметре определяющем режим доступа к файлу обычно передают комбинацию "fmOpenRead or fmShareDenyNone". Но оказалось, что это избавляет только от части ошибок "Процесс не может получить доступ к файлу, так как этот файл занят другим процессом" или "The process cannot access the file because it is being used by another process".
Для начала немного теории. В Delphi режим доступа к файлу, определяется комбинацией константы режима открытия файла (fmOpen*) и константы разделения доступа к файлу (fmShare*). Эти константы описанны в модуле System.SysUtils. Приведу их значение для ОС Windows:
Наименование | Значение | Описание |
Режимы открытия файла | ||
fmOpenRead | $0000 | Открытие файла только для чтения |
fmOpenWrite | $0001 | Открытие файла только для записи |
fmOpenReadWrite | $0002 | Открытие файла для чтения и записи |
Режимы разделения доступа | ||
fmShareExclusive | $0010 | Другие приложения не имеют доступа к файлу |
fmShareDenyWrite | $0020 | Другие приложения могут только читать файл |
fmShareDenyRead | $0030 | Другие приложения могут только писать в файл |
fmShareDenyNone | $0040 | Другие приложения могут читать файл и писать в него |
Теперь перейдем к практике. Попробуем в режиме fmOpenRead открыть все файлы в каталоге для хранения временных файлов (там большая вероятность встретить файл, который занят какими-нибудь процессом).
Вариант 1. Откроем файлы без указания режима разделения доступа
У меня в каталоге для хранения временных файлов нашлось 23 файла, которые заняты какими-то процессами.try with TFileStream.Create(sFileName, fmOpenRead) do try // читаем секретные материалы finally Free end; except on E: Exception do Writeln(E.Message); end;
Вариант 2. Откроем файлы в режиме разделения доступа "fmShareDenyNone"
Теперь получаем 16 файлов занятых другими процессами и радуемся улучшению результата. Радуемся, но не долго. Некоторые из этих "занятых" файлов открываются на просмотр в "Lister" из Total Commander! Как так?!?try with TFileStream.Create(sFileName, fmOpenRead or fmShareDenyNone) do try // истина где-то рядом finally Free end; except on E: Exception do Writeln(E.Message); end;
Метод TFileStream.Create приводит к функции FileOpen из System.SysUtils:
Получается, что в режиме разделения доступа fmShareDenyNone функция CreateFile вызывается с параметром dwShareMode = FILE_SHARE_READ or FILE_SHARE_WRITE. Согласно документации от Microsoft, в WinAPI, кроме FILE_SHARE_READ и FILE_SHARE_WRITE, есть еще режим разделения доступа FILE_SHARE_DELETE. В Delphi константа FILE_SHARE_DELETE описана в модуле Winapi.Windows, но не используется. Откроем файл для нашего потока сами с учетом FILE_SHARE_DELETE:function FileOpen(const FileName: string; Mode: LongWord): THandle; const AccessMode: array[0..2] of LongWord = ( GENERIC_READ, GENERIC_WRITE, GENERIC_READ or GENERIC_WRITE); ShareMode: array[0..4] of LongWord = ( 0, 0, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE); begin Result := INVALID_HANDLE_VALUE; if ((Mode and 3) <= fmOpenReadWrite) and ((Mode and $F0) <= fmShareDenyNone) then Result := CreateFile(PChar(FileName), AccessMode[Mode and 3], ShareMode[(Mode and $F0) shr 4], nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); end;
Оказывается, что у меня в каталоге для хранения временных файлов реально только 4 файла занятых другими процессами!var h: THandle; ... h := CreateFile(PChar(sFileName), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if h = INVALID_HANDLE_VALUE then Writeln(SysErrorMessage(GetLastError)) else with TFileStream.Create(h) do try // X-файлы наши! finally Free end;
В результате я добавил себе в проект функцию
и использовал ее для открытия файлов в режиме "только для чтения":function FileOpenAsReadOnly(const sFileName: String): THandle; begin Result := CreateFile(PChar(sFileName), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if Result = INVALID_HANDLE_VALUE then raise EFOpenError.CreateResFmt(@SFOpenErrorEx, [ExpandFileName(sFileName), SysErrorMessage(GetLastError)]) end;
Конечно, полностью победить эту ошибку мне не удалось, но 4 файла против 23 или 16 - это тоже хорошее достижение. Интересно, то, что разработчики из Borland потеряли FILE_SHARE_DELETE, а разработчики из Embarcadero не нашли - это ошибка в Delphi? Или так надо, и я чего-то не понимаю?TFileStream.Create(FileOpenAsReadOnly(sFileName))
Исходный текст тестовой программы: FileOpenTest.dpr
Проблема известна давно и в некоторых случаях решается указанным вами способом, но, к сожалению, не универсальна. Microsoft большая и там случаются нарушители конвенций. У меня есть надежно повторяемый пример с PowerShell, когда CreateFile с любыми параметрами показывает, что файл свободен, но реально он занят. Приходится тупо использовать эмпирический Sleep.
ОтветитьУдалитьal