鉴于此代码:

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

===============>>#1 票数:10 已采纳

你打电话时

TFile.OpenRead(Path)

这是通过实现的

TFileStream.Create(Path, fmOpenRead, 0)

这反过来导致呼吁

FileOpen(Path, fmOpenRead or 0)

最后调用CreateFile传递0作为dwShareMode CreateFile的文档说dwShareMode0表示:

阻止其他进程在请求删除,读取或写入访问权限时打开文件或设备。

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

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

所以正确的代码应该是:

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

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

查看将Mode or Rights传递给FileOpenelse分支。 这看起来很像Rights被忽略。

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

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

我提交了QC报告: QC#115020 非常有趣的是, TFileStream.Create的错误行为(当不应该使用Rights时)对XE3来说是新的。 我认为这是尝试处理TFile.Open的伪代码,该代码已被报告为QC#107005 ,其被错误地标记为已修复。 可悲的是,修复TFile.Open的尝试让TFile.Open仍然破碎,反过来打破了以前工作的TFileStream.Create

===============>>#2 票数:1

具有Windows访问权限的文件是从FileCreate和FileOpen中的模式中提取的,这些模式是从TFileStream.Create调用的。
这里使用ShareMode [(Mode和$ F0)shr 4]调用CreateFile

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

===============>>#3 票数:1

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

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

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

  ask by Jason translate from so

未解决问题?本站智能推荐: