2013-04-16 1 views
3

Учитывая этот код:Невозможно открыть файл во второй раз, даже если помечено как доля считаны с помощью TFile

FN := 'c:\temp\test_file.log'; 
    AFile := TFile.Open(FN, TFileMode.fmOpenOrCreate, TFileAccess.faReadWrite, TFileShare.fsRead); 
    try 
    with TFile.OpenRead(FN) do 
    try 

    finally 
     Free; 
    end; 

    finally 
    AFile.Free; 
    end; 

Я получаю сообщение об ошибке при попытке открыть на линии (FN) TFile.OpenRead:
enter image description here

с помощью:

with TFile.Open('c:\temp\test_file.log', TFileMode.fmOpen, TFileAccess.faRead, TFileShare.fsRead) do 
try 

finally 
    Free; 
end; 

также приводит к одной и той же ошибки. Как делает:

FS := TFileStream.Create('c:\temp\test_file.log', fmOpenRead or fmShareDenyWrite); 
    try 

    finally 
    FS.Free; 
    end; 

Однако, я могу с радостью открыть файл в блокноте сказать (как ReadOnly), или если я изменить первоначальный TFileShare.fsRead к TFileShare.fsNone, я не могу открыть его, как и ожидалось (в Блокнот).

Однако, если я запускаю два экземпляра этого фиктивного приложения, сначала открываем его с помощью TFileShare.fsRead. Я могу открыть его. Могу ли я повторно открыть файл дважды в одном приложении? Кажется неправдой.

Если открыть файл сначала с:

FS := TFileStream.Create('c:\temp\test_file.log', fmOpenReadWrite or fmShareDenyWrite); 
    try 

    finally 
    FS.Free; 
    end; 

Я могу открыть его во второй раз с описанными выше способами (где с помощью fsRead). То, что вводит в заблуждение, - это пройти через код TFile.Open, он в конечном счете выполняет тот же самый код, что и выше, TFileStream.Create.

Окончательный комментарий. Если я открою, используя верхний (первый) путь, но назначу его «глобальной» переменной, удалите внутренний вызов TFile.OpenRead (FN), а затем попытайтесь открыть файл с помощью другой кнопки щелчок, скажем, ошибка сохраняется. Это доказывает, что это не связано с вложенным вызовом.

+0

Ваша проблема заключается в флагов fmOpenOrCreate. Сделайте это в 3 шага: проверьте, существует ли файл, если не сделать fmCreate. закрыть файл, открыть сейчас для readwrite и второй раз только для чтения ... – whosrdaddy

+0

@whosrdaddy Нет, это не оно. –

+0

@whosrdaddy, как сказал Дэвид, это не поможет. TFile.Open проверяет существование при передаче в параметре fmOpenOrCreate, поскольку я был – Jason

ответ

10

Когда вы звоните

TFile.OpenRead(Path) 

это реализуется

TFileStream.Create(Path, fmOpenRead, 0) 

, который, в свою очередь, приводит к вызову

FileOpen(Path, fmOpenRead or 0) 

, который, наконец, вызывает CreateFile прохождение 0 в dwShareMode. И документация CreateFile говорит, что dwShareMode из 0 означает:

Предотвращает другие процессы от открытия файла или устройства, если они просят удаления, чтения или записи.

Иными словами, TFile.OpenRead(Path) пытается открыть файл с эксклюзивным режимом совместного использования. И это явно провалится, так как файл уже открыт.

Я думаю, что TFile.OpenRead(Path) использует неправильный режим совместного использования. Это должно позволить доступ для чтения. Однако, даже если бы это было так, это не помогло бы вам, так как ваш другой дескриптор имеет доступ на запись.

Решите проблему, избегая TFile.OpenRead. Вместо того, чтобы открыть его, как это:

TFileStream.Create(Path, fmOpenRead or fmShareDenyNone) 

Вы должны пройти fmShareDenyNone. Вы не можете отрицать какую-либо форму обмена, поскольку вы уже открыли ее для чтения и письма.


Есть еще одна проблема, которую я не смог понять, когда я изначально написал этот ответ. Это правда, что TFile.OpenRead() всегда пытается получить эксклюзивный доступ. Но также верно, что использование вами TFile.Open(), самого первого вызова, которое вы совершаете, также может привести к эксклюзивному доступу. Даже если вы указали TFileShare.fsRead.

Код в TFile.Open(), который создает файлы поток читает так:

if Exists(Path) then 
    Result := TFileStream.Create(Path, LFileStrmAccess, LFileStrmShare) 
else 
    Result := TFileStream.Create(Path, fmCreate, LFileStrmShare); 

Сразу это катастрофа. Это просто и просто неправильно для того, чтобы поведение файла было включено в файл. Создание файла должно быть атомной операцией. Что делать, если файл создается после того, как Exists возвращает, но перед вызовом CreateFile, который сделан внутри TFileStream.Create? Но я думаю, причина, по которой код был написан таким образом, заключается в том, что нет способа использовать TFileStream.Create и отправлено OPEN_ALWAYS в CreateFile. И, следовательно, эта ужасная битка.

И получается, что если выбрана опция fmCreate, потому что Exists() возвращает False, тогда ваши параметры совместного доступа игнорируются. Это связано с тем, что они передаются в параметр RightsTFileStream.Create вместо объединения с fmCreate. Как указывает documentation, в Windows параметр Rights игнорируется.

Так правильный код должен быть:

Result := TFileStream.Create(Path, fmCreate or LFileStrmShare); 

А как насчет другой ветви, если? Наверное, это тоже неправильно. Поскольку значение, переданное в Rights, игнорируется, то, конечно, значение LFileStrmShare игнорируется. Ну, оказывается, что документация лгала. Код в TFileStream.Create гласит:

constructor TFileStream.Create(const AFileName: string; Mode: Word; Rights: Cardinal); 
var 
    LShareMode: Word; 
begin 
    if (Mode and fmCreate = fmCreate) then 
    begin 
    LShareMode := Mode and $FF; 
    if LShareMode = $FF then 
     LShareMode := fmShareExclusive; // For compat in case $FFFF passed as Mode 
    inherited Create(FileCreate(AFileName, LShareMode, Rights)); 
    if FHandle = INVALID_HANDLE_VALUE then 
     raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(AFileName), SysErrorMessage(GetLastError)]); 
    end 
    else 
    begin 
    inherited Create(FileOpen(AFileName, Mode or Rights)); 
    if FHandle = INVALID_HANDLE_VALUE then 
     raise EFOpenError.CreateResFmt(@SFOpenErrorEx, [ExpandFileName(AFileName), SysErrorMessage(GetLastError)]); 
    end; 
    FFileName := AFileName; 
end; 

Посмотрите на else отделение, где Mode or Rights передается FileOpen. Это не очень похоже на то, что Rights игнорируется.

Итак, все это объясняет, почему режим совместного доступа правильно установлен в вашем вызове до TFile.Open тогда и только тогда, когда файл уже существует.

Итак, вы не можете не только использовать TFile.OpenRead, но и TFile.Open также нет. Бросьте, пока вы впереди, и вообще откажитесь от TFile. Я не знаю, что случилось с QA в Embarcadero, когда был введен TFile, но, очевидно, был серьезный провал. Объедините эту неудачу с необычными недостатками дизайна от TFileStream.Create, и у вас есть настоящая фабрика ошибок.

Я представил отчет о контроле качества: QC#115020. Весьма интересно, что ошибочное поведение в TFileStream.Create, где Rights используется, когда оно этого не должно, является новым для XE3. Я считаю, что это попытка разобраться с фиктивным кодом в TFile.Open, который уже был указан как QC#107005, который неправильно помечен как Исправлено. К сожалению, попытка исправить TFile.Open оставляет TFile.Open еще сломанной и, в свою очередь, ломает TFileStream.Create, который раньше работал!

+2

+1 для ответа, который объясняет его проблему ... – whosrdaddy

+0

Спасибо. Не думал вникать в Windows API (в конечном счете). Спасибо – Jason

+0

@DavidHeffernan Я не согласен в настройке TFile.OpenRead как только виновен, так как TFile.Open allready заблокирует файл (кроме allready existing на XE3) – bummi

1

с правами доступа к окну извлекаются из режима в FileCreate и FileOpen, которые вызывается из TFileStream.Create.
CreateFile здесь называется ShareMode [(Mode и $ F0) shr 4].

TFileMode.fmOpenOrCreate будет звонить
TFileStream.Create (Path, fmCreate, LFileStrmShare); файл не существует.

behavoir можно показать

function OpenReadShareALL(const Path: string): TFileStream; 
begin 
    If FileExists(Path) then 
    begin 
     try 
     Result := TFileStream.Create(Path, fmOpenRead or fmShareDenyNone); 
     // will work too at lest up to XE since rights are ignored in oder versions 
     //Result := TFileStream.Create(Path, fmOpenRead or fmShareDenyNone,fmShareExclusive); 
     except 
     on E: EFileStreamError do 
      raise EInOutError.Create(E.Message); 
     end; 
    end; 
end; 



var 
    FN:String; 
    AFile:TFileStream; 
begin 
    FN := 'c:\temp\test_file.log'; 
// this will lock file at least until Delphi XE if file has to be created 
//AFile := TFile.Open(FN, TFileMode.fmOpenOrCreate, TFileAccess.faReadWrite, TFileShare.fsReadWrite); 


// the following will work 
if Fileexists(FN) then 
    AFile := TFileStream.Create(fn,fmOpenRead or fmOpenWrite or fmShareDenyNone) 
else 
    AFile := TFileStream.Create(fn,fmCreate or fmShareDenyNone);  

// won't work if file does not exists , and will not work with existing file up to at least Delphi XE (fixed in XE 3 , maybe XE 2 too) 
// AFile := TFileStream.Create(fn,fmCreate or fmOpenReadWrite ,fmShareDenyNone); 
    try 
    with OpenReadShareAll(FN) do 
    try 
     Showmessage('Worked'); 
    finally 
     Free; 
    end; 

    finally 
    AFile.Free; 
    end; 
end; 

В XE3 игнорирование прав, как представляется, с поправкой на другие, то fmCreate

inherited Create(FileOpen(AFileName, Mode or Rights)); 
+0

Такое же поведение, независимо от того, существует ли оно. –

+0

@DavidHeffernan Да, поэтому я упомянул FileCreate AND FileOpen – bummi

+0

Боюсь, я не могу понять ваши объяснения. Сожалею. –

1

режимы доступа по крайней мере до Delphi 2010 есть аналогичный вопрос , Основная проблема заключается в том, что если файл помечен как «созданный», некоторые флаги обмена просто не учитываются. Вы можете прочитать больше о том, что на

http://cc.embarcadero.com/Item/21636

который должен исправить по крайней мере, для создания файла «старый стиль», где вы «или» 'Ed toggether флаги.

Смежные вопросы