鉴于此代码:
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)调用,然后尝试通过另一个按钮单击打开文件说,错误仍然存在。这证明它与嵌套调用无关。
答案 0 :(得分:10)
致电时
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
返回后创建的,而是在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)
是从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
至少对于“旧样式”文件创建应该有一个修复,你在其中“或”编辑了这些标记。