Jump to content
Mark Williams

isFileInUSe

Recommended Posts

I have used the following function for checking if a file is in use for many years (it is recommended on numerous sites):

function IsFileInUse(FileName: String): Boolean;
var
  HFileRes: HFILE;
  hdl:THandle;
begin
  Result := False;
  if not FileExists(FileName) then
    Exit;
  HFileRes := CreateFile(PChar(FileName),
     GENERIC_READ or GENERIC_WRITE,
     0,
     nil,
     OPEN_EXISTING,
     FILE_ATTRIBUTE_NORMAL,
     0);
  Result := (HFileRes = INVALID_HANDLE_VALUE);
  if not Result then
    CloseHandle(HFileRes);
end;

It still works fine for 32Bit, but not 64 bit. On 64 bit It always returns a valid handle even when the file is in use and thus reports that the file is not in use even when it is.

 

Also, when run from the IDE in 64 bit it crashes with an EExternalException: "External exception C0000008" when trying to close the handle. I have found some posts on this via Google and it seems to be a known issue, the answer being to switch off some debug options.

 

But I am not particularly troubled by the IDE bug. I am majorly concerned by the failure to detect that a file is in use!

 

I've tried with CreateFileA and CreateFileW, but with the exactly the same result.

Share this post


Link to post

HFileRes should be a THandle instead of HFILE (which is a LongWord).  In 64bit CreateFile returns a 64bit value and you are assigning it to a 32bit one.  On a side note I just checked Delphi RIO and still no compiler hint for this type of thing...  Seems like that would be useful.

 

-Mark

Edited by MarkShark

Share this post


Link to post
5 minutes ago, MarkShark said:

HFileRes should be a THandle instead of HFILE (which is a LongWord).  In 64bit CreateFile returns a 64bit value and you are assigning it to a 32bit one.

Noted and thanks, but still returns false when the file is actually in use in 64 bit. Any idea why?

Share this post


Link to post

Why do you call FileExists? A single call to CreateFile can tell you what you need. The code as it stands won't tell you whether or not the file is in use. What is hdl? Use THandle as stated above. 

 

As for the problem, debug it! Use GetLastError if CreateFile fails. If CreateFile succeeds then there's presumably something wrong with your logic. 

Edited by David Heffernan

Share this post


Link to post
1 hour ago, David Heffernan said:

Why do you call FileExists? A single call to CreateFile can tell you what you need. The code as it stands won't tell you whether or not the file is in use. What is hdl? Use THandle as stated above. 

I copied the code from I believe Mike Lischke's site and found it worked exactly as I wanted in 32 bit.

Never thought about the reason for the fileExists, but I suppose if the file doesn't exist then the answer to isFileInUse is "No". If it doesn't exist why bother creating it? So to my mind, it makes sense. However, it certainly isn't the cause of the problem.

 

As for using THandle, please read my post to which you have responded where I say "Noted and thanks, but still returns false...". I was unaware of the problem with FileHdl in64 bit, but I have now changed it and it makes no difference whatsoever. In 64 bit when I run the function, it creates the file even though it is in use in another process and returns no error at least not on the CreateFile process. The only error it returns is on the call to CloseHandle, but that only happens in the IDE so, as I stated in my original post, I don't particularly care. Anyway, It's not hard to imagine that the reason CloseHandle bugs out is because the file is open in another process already and the handle should never have been created.

 

But ignoring the CloseHandle problem, which would not exist if the function worked as expected (and as it does work in 32 bit), why doesn't it work in 64 bit?

 

It would be helpful if someone else tried it in a 64 bit app. All you need is a button and an openDialog, the above function (obviously with HFileRes, changed to THandle) and the following code in your button's onClick:

if OpenDialog1.Execute then
  if isFileInUse(openDialog1.FileName) then
    Showmessage('in use')
  else
   Showmessage('not in use');

 

Edited by Mark Williams

Share this post


Link to post

The reason you don't call FileExists is that CreateFile will fail and GetLastError will tell you that the file does not exist.

 

You have tell us why you believe that CreateFile should fail in the scenario that concerns you. Another program already opened the file? With what flags? 

 

Rather than have us spend time trying to guess how to recreate the problem, can you provide a complete console app that reproduces the issue. 

Share this post


Link to post
6 minutes ago, David Heffernan said:

The reason you don't call FileExists is that CreateFile will fail and GetLastError will tell you that the file does not exist.

Ok. Noted, but not really my issue.

 

However, for some reason the function is now working as expected in 64 bit and I have no idea why. 

 

Thanks anyway.

Share this post


Link to post

I would suggest rewriting the function to something more like this:

function IsFileInUse(const FileName: String): Boolean;
var
  hf: THandle;
begin
  hf := CreateFile(PChar(FileName),
    GENERIC_READ or GENERIC_WRITE,
    0,
    nil,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    0);
  if hf <> INVALID_HANDLE_VALUE then begin
    CloseHandle(hf);
    Result := False;
  end else begin
    Result := (GetLastError() = ERROR_SHARING_VIOLATION);
  end;
end;

 

Edited by Remy Lebeau
  • Like 2

Share this post


Link to post
3 hours ago, Remy Lebeau said:

I would suggest rewriting the function to something more like this:

Thanks. Will do.

Share this post


Link to post

Sorry to bring up this old topic but I do have a problem.

On normal folders all works sweet.

On Windows protected folders it fail.

(Always return false.)

Is there a way to make it compatible to work for protected folder content?

(C:\Program Files\ is a protected folder for example.)

 

My addition to above code was to add a SetFileAttributes call to try if FILE_ATTRIBUTE_NORMAL can be set.

If it cannot, folder is protected. 

 

Now I run out of Ideas.

Share this post


Link to post
1 hour ago, KodeZwerg said:

Now I run out of Ideas.

For protected folders you are fighting the builtin security system.. it's like a builtin Firewall, you only know your call didn't work and most likely froze you never get an explanation why..

 

Share this post


Link to post

I did thought such, thanks.

 

If someone find it usefull, here is my modificated variant.

 

function IsFileInUse(const FileName: string): Boolean;
// found on Delphi-Praxis forum
// code provided by Remy Lebeau
// slightly modified by KodeZwerg
// (now you will get "True" for files that need administrative rights or on write protected media)
var
  hFile: THandle;
  Attr: DWORD;
begin
  Result := False;
  SetLastError(ERROR_SUCCESS);
  Attr := GetFileAttributes(PChar(FileName));
  if (Attr <> $FFFFFFFF) and (Attr and FILE_ATTRIBUTE_DIRECTORY <> 0) then
    Exit(False);
  // only continue if we can normalize attributes
  if SetFileAttributes(PChar(FileName), FILE_ATTRIBUTE_NORMAL) then
    begin
      hFile := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
      if (hfile <> INVALID_HANDLE_VALUE) then
        CloseHandle(hFile)
        else
        Result := (GetLastError() = ERROR_SHARING_VIOLATION);
      // restore original attributes
      SetFileAttributes(PChar(FileName), Attr);
    end
    else
      Result := True;
end;

 

Edited by KodeZwerg

Share this post


Link to post
11 hours ago, KodeZwerg said:

I did thought such

 

Remembered I added this for a self updater. But I can't say its fully tested yet the remark sounds correct:

 

class function TDirectoryHelper.HaveControlledFolderAccess(const Path: string): Boolean;
{$REGION 'History'}
//  04-Jan-2019 - 'Controlled Folder Access' won't return an error nor an Invalid Handle
//                those checks are here in case the path is bad or doesn't exist.
//                The only indication is that the file is not created
{$ENDREGION}
var
  LFile: string;
  h: THandle;
begin
  LFile := TPath.Combine(Path, TGuid.NewGuid.ToString + PERIOD + TGuid.NewGuid.ToString);
  h := CreateFile(LFile.ToPchar, GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_NEW,
      FILE_ATTRIBUTE_HIDDEN or FILE_FLAG_DELETE_ON_CLOSE, 0);
  if (h = INVALID_HANDLE_VALUE) then Exit(False);
  Result := FileExists(LFile, False); 
  CloseHandle(h);
end;

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×