当前位置 : 主页 > 编程语言 > delphi >

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

来源:互联网 收集:自由互联 发布时间:2021-06-23
鉴于此代码: 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.O
鉴于此代码:

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)行打开时出错:

使用:

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)调用,然后尝试通过另一个按钮单击打开文件说,错误仍然存​​在.这证明它与嵌套调用无关.

你打电话的时候

TFile.OpenRead(Path)

这是通过实现的

TFileStream.Create(Path, fmOpenRead, 0)

这反过来导致呼吁

FileOpen(Path, fmOpenRead or 0)

最后调用CreateFile传递0作为dwShareMode. CreateFile的文档说dwShareMode为0意味着:

Prevents other processes from opening a file or device if they request delete, read, or write access.

换句话说,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返回之后但在调用在TFileStream.Create中创建的CreateFile之前创建的,该怎么办?但我想代码编写的原因是没有办法使用TFileStream.Create并将OPEN_ALWAYS传递给CreateFile.因此,这可怕的拙劣.

事实证明,如果选择了fmCreate选项,因为Exists()返回False,那么您的共享选项将被忽略.那是因为它们被传递给TFileStream.Create的Rights参数,而不是与fmCreate结合.正如documentation所说,在Windows上,Rights参数被忽略.

所以正确的代码应该是:

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

那if的另一个分支怎么样?当然这也是错的.由于忽略了传递给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;

查看将模式或权限传递给FileOpen的else分支.这看起来并不像人们被忽视了.

所有这些都解释了为什么在调用TFile.Open时正确设置共享模式,当且仅当文件已经存在时.

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

我提交了一份质量控制报告:QC#115020.非常有趣的是,TFileStream.Create中的错误行为,在不应该使用权利的情况下,对XE3来说是新的.我认为这是尝试处理TFile.Open中的伪代码,该伪代码已被报告为QC#107005,错误地标记为已修复.可悲的是,修复TFile.Open的尝试让TFile.Open仍然破碎,反过来打破了以前工作的TFileStream.Create!

网友评论