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!