当你调用
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
返回之后但在TFileStream.Create
内部调用CreateFile
之前文件被创建会怎么样?但我猜代码被写成这样的原因是没有办法同时使用TFileStream.Create
和将OPEN_ALWAYS
传递给CreateFile
。因此这是一个可怕的拼凑。
而且,如果选择了fmCreate
选项,因为Exists()
返回False
,那么你的共享选项会被忽略。这是因为它们被传递到TFileStream.Create
的Rights
参数中,而不是与fmCreate
合并。根据文档所说,在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;
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
的使用。我不知道 Embarcadero 在引入 TFile
时发生了什么,但显然出现了重大失误。结合 TFileStream.Create
的奇怪设计缺陷,就形成了一个真正的错误工厂。
我提交了一个 QC 报告:QC#115020。非常有趣的是,TFileStream.Create
中错误的行为(在应该不使用 Rights
的情况下使用了它)是新的 XE3 版本才出现的。我认为这是为了解决 TFile.Open
中错误的代码而做出的尝试,而该问题已经被报告为 QC#107005,但错误地标记为“已修复”。可悲的是,试图修正 TFile.Open
仍然使其失效,并且进而破坏了以前能够正常工作的 TFileStream.Create
!