Процесс не может получить доступ к файлу, так как этот файл занят другим процессом... Но это не точно... Как показала практика, для программы на 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. Откроем файлы без указания режима разделения доступа
try
with TFileStream.Create(sFileName, fmOpenRead) do
try
// читаем секретные материалы
finally
Free
end;
except
on E: Exception do
Writeln(E.Message);
end;У меня в каталоге для хранения временных файлов нашлось 23 файла, которые заняты какими-то процессами.Вариант 2. Откроем файлы в режиме разделения доступа "fmShareDenyNone"
try
with TFileStream.Create(sFileName, fmOpenRead or fmShareDenyNone) do
try
// истина где-то рядом
finally
Free
end;
except
on E: Exception do
Writeln(E.Message);
end;Теперь получаем 16 файлов занятых другими процессами и радуемся улучшению результата. Радуемся, но не долго. Некоторые из этих "занятых" файлов открываются на просмотр в "Lister" из Total Commander! Как так?!?Метод TFileStream.Create приводит к функции FileOpen из System.SysUtils:
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;
Получается, что в режиме разделения доступа 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:
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;Оказывается, что у меня в каталоге для хранения временных файлов реально только 4 файла занятых другими процессами!
В результате я добавил себе в проект функцию
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