即使使用TFile标记为Share Read,也无法再次打开文件

时间:2013-04-16 01:06:24

标签: delphi delphi-xe3

鉴于此代码:

  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;

尝试在TFile.OpenRead(FN)行打开时出错:
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完全相同的代码。

最后注意。如果我使用top(first)方式打开但是将其分配给“global”变量,则删除内部TFile.OpenRead(FN)调用,然后尝试通过另一个按钮单击打开文件说,错误仍然存​​在。这证明它与嵌套调用无关。

3 个答案:

答案 0 :(得分:10)

致电时

TFile.OpenRead(Path)

这是由

实现的
TFileStream.Create(Path, fmOpenRead, 0)

反过来导致对

的调用
FileOpen(Path, fmOpenRead or 0)

最终将CreateFile作为0传递dwShareModeCreateFile的文档说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,那么您的共享选项会被忽略。这是因为它们被传递到Rights的{​​{1}}参数,而不是与TFileStream.Create结合使用。正如documentation所述,在Windows上,fmCreate参数将被忽略。

所以正确的代码应该是:

Rights

if的另一个分支怎么样?当然这也是错的。由于忽略了传递给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的{​​{1}}分支。这看起来并不像Mode or Rights被忽略。

所有这些都解释了为什么在您FileOpen的调用中正确设置共享模式,当且仅当该文件已存在时。

因此,您不仅可以使用Rights,还可以使用TFile.Open。在你前进的时候退出并完全放弃TFile.OpenRead。我不知道在引入TFile.Open时Embarcadero的QA发生了什么,但显然有一个重大失败。将这种失败与TFile的奇怪设计缺陷相结合,你就拥有了一个名副其实的bug工厂。

我提交了质量控制报告:QC#115020。非常有趣的是TFile中的错误行为,其中TFileStream.Create在不应该使用的情况下,对XE3来说是新的。我认为这是尝试处理TFileStream.Create中的虚假代码,该代码已被报告为QC#107005,其被错误地标记为已修复。遗憾的是,修复Rights离开TFile.Open的尝试仍然被打破,反过来打破过去工作的TFile.Open

答案 1 :(得分:1)

具有Windows访问权限的

是从FileCreate和FileOpen中的模式中提取的,这些模式是从TFileStream.Create调用的。
这里使用ShareMode调用CreateFile [(模式和$ F0)shr 4]。

TFileMode.fmOpenOrCreate将调用
TFileStream.Create(Path,fmCreate,LFileStrmShare);是文件不存在。

行为可以显示

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));

答案 2 :(得分:1)

至少在Delphi 2010中存在类似的问题。基本问题是如果文件被标记为“创建”,则根本不考虑一些共享标志。 您可以在

上阅读更多相关信息

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

至少对于“旧样式”文件创建应该有一个修复,你在其中“或”编辑了这些标记。