Given this code:
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;
I get an error when trying to open at the TFile.OpenRead(FN) line:
using:
with TFile.Open('c:\temp\test_file.log', TFileMode.fmOpen, TFileAccess.faRead, TFileShare.fsRead) do
try
finally
Free;
end;
also results in the same error. As does:
FS := TFileStream.Create('c:\temp\test_file.log', fmOpenRead or fmShareDenyWrite);
try
finally
FS.Free;
end;
However, I can happily open the file in Notepad say (as Readonly), or if I change the initial TFileShare.fsRead to TFileShare.fsNone, I can't open it as expected (in Notepad).
However, if I run two instances of this dummy app, first one opening with TFileShare.fsRead I can open it. So am I unable to re-open a file twice in the same application? Doesn't seem right.
If I open the file initially with:
FS := TFileStream.Create('c:\temp\test_file.log', fmOpenReadWrite or fmShareDenyWrite);
try
finally
FS.Free;
end;
I can open it a second time with the above methods (where using fsRead). What is confusing is stepping through the TFile.Open code, it ultimately executes the exact same code as the above TFileStream.Create.
Finaly note. If I open using the top (first) way but assign it to a "global" variable, remove the internal TFile.OpenRead(FN) call, and then try to open the file through another button click say, the error persists. This proves it's not related to the nested call.
When you call
TFile.OpenRead(Path)
this is implemented by
TFileStream.Create(Path, fmOpenRead, 0)
which in turn leads to a call to
FileOpen(Path, fmOpenRead or 0)
which finally calls CreateFile
passing 0
as dwShareMode
. And the documentation for CreateFile
says that dwShareMode
of 0
means:
Prevents other processes from opening a file or device if they request delete, read, or write access.
In other words, TFile.OpenRead(Path)
is trying to open the file with exclusive share mode. And that will clearly fail since the file is already open.
I think that TFile.OpenRead(Path)
is using the wrong share mode. It should allow read access. However, even if that was the case it would not help you since your other handle has write access.
Solve the problem by avoiding TFile.OpenRead
. Instead open it like this:
TFileStream.Create(Path, fmOpenRead or fmShareDenyNone)
You have to pass fmShareDenyNone
. You are in no position to deny any form of sharing since you already opened it for both reading and writing.
There is a further problem which I had failed to grasp when I originally wrote this answer. It's true that TFile.OpenRead()
always tries to gain exclusive access. But it's also true that your use of TFile.Open()
, the very first call you make, can also result in exclusive access. Even though you specified TFileShare.fsRead
.
The code in TFile.Open()
that creates files stream reads like this:
if Exists(Path) then
Result := TFileStream.Create(Path, LFileStrmAccess, LFileStrmShare)
else
Result := TFileStream.Create(Path, fmCreate, LFileStrmShare);
Right off the bat this is a disaster. It's plain and simply wrong for file creation behaviour to be switched on file exists check. File creation needs to be an atomic operation. What if the file is created after Exists
returns, but before the call to CreateFile
that is made inside TFileStream.Create
? But I guess the reason the code has been written like this is that there is no way to use TFileStream.Create
and have OPEN_ALWAYS
passed to CreateFile
. And hence this horrid botch.
And it turns out that if the fmCreate
option is chosen, because Exists()
returns False
, then your sharing options are ignored. That's because they are passed to the Rights
parameter of TFileStream.Create
instead of being combined with fmCreate
. As the documentation says, on Windows the Rights
parameter is ignored.
So the correct code should be:
Result := TFileStream.Create(Path, fmCreate or LFileStrmShare);
And what about the other branch of the if? Surely that's wrong too. Since the valued passed to Rights
is ignored, then surely the value of LFileStrmShare
is ignored. Well, it turns out that the documentation lied. The code in TFileStream.Create
reads:
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;
Look at the else
branch where Mode or Rights
is passed to FileOpen
. That doesn't look very much like Rights
is being ignored.
So all that explains why the sharing mode is set correctly in your call to TFile.Open
if and only if the file already exists.
So, not only can you not use TFile.OpenRead
, but TFile.Open
is also out. Quit while you are ahead and give up on TFile
altogether. I have no idea what happened with the QA at Embarcadero when TFile
was introduced, but there was evidently a major failure. Combine that failure with the bizarre design flaws of TFileStream.Create
and you have a veritable bug factory.
I submitted a QC report: QC#115020. Quite interestingly the erroneous behaviour in TFileStream.Create
, where Rights
is used when it should not, is new to XE3. I believe it was an attempt to deal with the bogus code in TFile.Open
which has already been reported as QC#107005, which is incorrectly marked as Fixed. Sadly the attempt to fix TFile.Open
leaves TFile.Open
still broken, and in turn breaks TFileStream.Create
which used to work!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With