Welcome to shell: revealed Sign in | Join | Help
in Search

Shell Blog

Your File Is In Use… Demystified

In past versions of Windows, when a user encountered a file that is in use by another process, he would be presented with an unfriendly dialog like this:

Windows XP Error Dialog - File In Use

What process is using this file?  What should the user do?  There is nothing actionable for the user on this dialog.  Some savvy users may use a utility to determine which process has an open handle on the file.  Yet, a typical user will restart their machine in the hope that he can get access to the file after logging back in.  In Windows Vista, work was done to give the user more information about who has the file opened as well as exposing capabilities to gain control of the file.  This is accomplished by IFileIsInUse:  an interface applications can implement and associate with the files they currently have open.

If you encounter a file that is currently opened in another process in Vista while performing operations on files in Windows Explorer, you will see a much different dialog:

File In Use Dialog - Vista

This is because the operations engine in Windows Explorer is applying an implementation of the IFileIsInUse interface to the file it encountered.  Now the user knows which program has the file open and can close the file from that program and click the Try Again button to repeat the operation that was being performed.

The implementer of the IFileIsInUse interface can specify further capabilities such as the ability to switch to the main window of the application that has the file open or to simply close the file in that application from the File In Use dialog.  If these capabilities are exposed, the above dialog would now look like this:

Vista File In Use Dialog with capabilities enabled

The user can now click the Switch To button to set the window of the application that has the file open to the foreground.  Also, the user could simply click Close File to notify the application to close its open handle to the file. 

So how does an application add similar functionality?  It must implement the IFileIsInUse interface.

The IFileIsInUse interface exposes the following methods:

Method

Description

HRESULT CloseFile()

Closes the file currently in use.

HRESULT GetAppName(PWSTR *ppszName)

 

Gets a string associated with an application name.

HRESULT GetCapabilities(DWORD *pdwCapFlags)

 

Gets the capabilities flag for file in use.  See below for a description of the accepted values.

HRESULT GetSwitchToHWND(HWND *phwnd)

Gets the HWND associated with the application using the file.

HRESULT GetUsage(FILE_USAGE_TYPE *pfut)

Gets the current file usage type.  This is interpreted as editing, playing, or generic usage.  See the FILE_USAGE_TYPE flags.  See below for a description of the accepted values.

FILE_USAGE_TYPE Flags

This can be one of the following values:

Name

Description

FUT_PLAYING

Indicates that the file is currently playing in the process that has it opened.

FUT_EDITING

Indicates that the file is currently being edited in the process that has it opened.

FUT_GENERIC

Indicates that the file is currently in use in the process that has it opened.

Capability Flags

This can be any combination of the following values:

Name

Description

OF_CAP_CANSWITCHTO

Indicates that the caller can retrieve the HWND associated with the process that has the file opened by calling GetSwitchToHWND

OF_CAP_CANCLOSE

Indicates that the caller can close the file by calling CloseFile

If the application that has the file in use specifies the above flags, buttons on the file in use dialog will be visible – allowing the user to bring the application to the foreground or simply close the file directly and continue with the operation that was interrupted.

The Running Object Table

So how does an application notify other applications that the file is in use and what capabilities are available?  This is done by inserting the instantiated IFileIsInUse object into the Running Object Table.  The Running Object Table (ROT) is a globally accessible lookup table on each workstation.  The ROT keeps track of those objects that can be identified by a moniker and that are currently running on the workstation.  When a client tries to bind a moniker to an object, the moniker checks the ROT to see if the object is already running.  This allows the moniker to bind to the current instance instead of loading a new one. 

For example, to place an object in the ROT:

DWORD dwCookie;

IRunningObjectTable *prot;

HRESULT hr = GetRunningObjectTable(NULL, &prot);

if (SUCCEEDED(hr))

{

    IMoniker *pmk;

    hr = CreateFileMoniker(pszFileName, &pmk);

    if (SUCCEEDED(hr))

    {

        // Add ROTFLAGS_ALLOWANYCLIENT to make this work accross security boundaries

        hr = prot->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE | ROTFLAGS_ALLOWANYCLIENT,

                           static_cast<IFileIsInUse *>(this), pmk, &dwCookie);

        if (hr == CO_E_WRONG_SERVER_IDENTITY)

        {

            // this failure is due to ROTFLAGS_ALLOWANYCLIENT and the

            // fact that we don't have the AppID registered for our CLSID.

            // Try again without ROTFLAGS_ALLOWANYCLIENT knowing that this

            // means this can only work in the scope of apps running with

            // the same MIC level.

            hr = prot->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE,            

                               static_cast<IFileIsInUse *>(this), pmk,         

                               &dwCookie);

        }

        pmk->Release();

    }

    prot->Release();

}

 

In the above code, we call GetRunningObjectTable to retrieve an instance of IRunningObjectTable.  We then create an IMoniker for the file that is currently in use.  This moniker is then inserted into the ROT in the call to IRunningObjectTable::Register. We specify the ROTFLAGS_ALLOWANYCLIENT in our call to Register.  This is required to allow our entry in the ROT to work across security boundaries.   This requires AppID registration so COM can inspect our security settings.  Without this the Register()call will fail.  In that case, we call Register() again, but without the ROTFLAGS_ALLOWANYCLIENT – which will allow our application to work correctly but only in the same security level.   The dwCookie parameter is used to identify the entry in later calls to retrieve or remove it from the ROT. 

In the below code, the cookie is used to remove the moniker in the ROT. 

IRunningObjectTable *prot;

HRESULT hr = GetRunningObjectTable(NULL, &prot);

if (SUCCEEDED(hr))

{

    hr = prot->Revoke(dwCookie);

    prot->Release();

}

 

So from the above code we now know how to insert our object into the ROT so other applications can find it.  But how does an application check if a particular file has been added as an instance of an IFileIsInUse object to the ROT?  The application needs to enumerate the contents of the ROT to find a matching moniker.

 

IFileIsInUse *pfiu;

PCWSTR pszFile = L"c:\\some\\full\\path\\to\\file";

IRunningObjectTable *prot;

HRESULT hr = GetRunningObjectTable(0, &prot);

if (SUCCEEDED(hr))

{

    IMoniker *pmkFile;

    hr = CreateFileMoniker(pszFile, &pmkFile);

    if (SUCCEEDED(hr))

    {

        IEnumMoniker *penumMk;

        hr = prot->EnumRunning(&penumMk);

        if (SUCCEEDED(hr))

        {

            hr = E_FAIL;

            ULONG celt;

            IMoniker *pmk;

            while (FAILED(hr) && (penumMk->Next(1, &pmk, &celt) == S_OK))

            {

                DWORD dwType;

                if (SUCCEEDED(pmk->IsSystemMoniker(&dwType)) &&

                    (dwType == MKSYS_FILEMONIKER))

                {

                    // Is this a moniker prefix?

                    IMoniker *pmkPrefix;

                    if (SUCCEEDED(pmkFile->CommonPrefixWith(pmk, &pmkPrefix)))

                    {

                        if (S_OK == pmkFile->IsEqual(pmkPrefix))

                        {

                            // Get the IFileIsInUse instance

                            IUnknown *punk;

                            if (prot->GetObject(pmk, &punk) == S_OK)

                            {

                                hr = punk->QueryInterface(IID_PPV_ARGS(&pfiu));

                                punk->Release();

                            }

                        }

                        pmkPrefix->Release();

                    }

                }

                pmk->Release();

            }

            penumMk->Release();

        }

        pmkFile->Release();

    }

    prot->Release();

}

 

If the moniker was found in the ROT, the application now has an instance of an IFileIsInUse that was created in the application that is using the file.  This object can then be used to query for the capabilities that are available or any other method exposed by IFileIsInUse.  This is essentially what Windows Explorer does when it encounters a file that is in use during file operations.

The goal of creating this interface is to allow applications to play nice with one another when contending for access to common files.  As more developers implement this interface, the better it is for the entire community. 

 

Windows XP Support

Windows XP implemented a less ambitious version of this feature using the ROT and IOleObject and IOleWindow. Since many OLE applications implemented these interfaces, one could discover the application name (via IOleObject::GetUserClassID() that could then be used to look up the application name) and offer the “Switch To” function via the IOleWindow::GetWindow() method.

 

Building the IFileIsInUse Sample

The FileIsInUse sample creates an IFileIsInUse instance of a file it has opened and adds it to the ROT with all capabilities enabled.  You can then verify the functionality that the interface provides by attempting to move or delete the file through Windows Explorer.  Windows Explorer will show a File In Use dialog as shown below. 

  1. Download and install the Windows SDK
  2. Download the FileIsInUse_Sample
  3. Launch FileIsInUseSample.sln in Visual Studio
  4. Open the properties for the project
  5. Add a path to the SDK includes to the C/C++ - General page
  6. Add a path to the SDK libs to the Linker – General page
  7. Build

FileIsInUse Sample Application

File In Use Dialog with sample application

 

After launching the FileIsInUseSample.exe, you can drag and drop files to the application or click “Open File…” from the File menu (Note that drag and drop does not work across security boundaries).  After opening the file and adding an entry in the ROT with the IFileIsInUse implementation provided, the full path of the file will appear on the dialog.  You can now attempt to delete the file from Windows Explorer.  When you close the file from the File In Use dialog in Windows Explorer it will be removed from the dialog.  Also, if you click “Close File” from the File menu, it will close the file, remove it from the ROT and clear the file path from the dialog.

While this sample does very little, it should helpful in importing this functionality with the provided IFileIsInUse implementation. 

Published Thursday, March 29, 2007 9:39 PM by chrdavis

Comments

 

links for 2007-03-31 | ITsVISTA said:

March 31, 2007 2:27 AM
 

someone said:

Very nice but why cant Vista or Explorer get more sophisticated and actually close the program automatically, do the operation and reopen the file using Restart Manager? At least while moving, copying, renaming you can do that. If deleting, that can be handled the existing way. Or why didn't you offer a button in the UI asking the user to DO IT ALL automatically and then reopen the file?

April 3, 2007 5:47 AM
 

chrdavis said:

The Restart Manager's primary purpose is to shut down applications and services to allow updates to be applied which is beyond the scope of what IFileIsInUse was designed for.  If an application has a file opened which blocks the copy engine in Windows Explorer, the shell would like to be a good citizen and use the IFileIsInUse interface (if it is available).  I'm sure application developers would not appreciate the shell restarting their application to gain access to a file handle.

For the suggestion of a button to "DO IT ALL" - we try to give the user the option to "reply to all" confirmation dialogs of a certain type in the same way.  This should be shown to the user if the copy engine encounters multiple files open.

April 3, 2007 2:29 PM
 

droid said:

For me it's actually quite rare to have file being open/locked by another user application.

A more common thing to occur is that I'm trying to safely disconnect an external serial ata drive from my laptop and even though I have no applications open I (sometimes) get message that something is open on the drive until I logoff and log back in.

BTW. Not that you wanted to hear it but what is the #1 annoying thing about Vista?

It's how the explorer when pressing Windows+E to open it takes approximately 3-4 times longer to open than in Windows XP/2003.

I haven't really timed it but in earlier Windows everything appears in less than 100 ms. In Windows Vista, even after multiple Windows+E presses it takes 100-150 ms for the Explorer window frame to appear and another 200 ms for the drives and folders to appear.

BTW2. Is there a way to make Windows+E always open Explorer with Administrative priveleges? I'd put UAC back on in a heartbeat if I'd always the Win+E explorer with full rights and without answering any popups in the process. Same for a key to open command prompt always like that.

April 4, 2007 8:11 PM
 

droid said:

Oh hey and could you please ask the regedit maintainer to add a feature to go to the key/field in the clipboard?

eg If I have

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Shell Extensions

In my clipboard I could the open regedit and press one button in the menu to go to that? To prove that there is demand for this feature, just google around, there are utilities written that will even manually send keyboard input to regedit so it would navigate to the key you have.

April 4, 2007 8:21 PM
 

chrdavis said:

droid - please direct these requests to the forum section of the site where they will get more traction.

April 5, 2007 2:32 AM
 

Davester said:

Care to expound on why this file locking thing is still part of Window's design?  I don't understand the usecases that make such a drastic thing a requirement.  Every time I've had a locked .dll or a .jar send me to the Task Manager to kill a process, I send a neatly wrapped curse word floating up towards Redmond.

Perhaps .exe's could opt out of the file locking scheme?  Is this even possible?  Can you give pointers to friendly programmers who want to keep open file handles without locking them to other processes?

On the wider question of application cross platform compatibility, this file locking thing is a persistent bug generator.  On unix/linux this automagic file locking thing doesn't exist, and porting apps to windows becomes less savory when facing a bazillion workarounds and extra testing to get things working there.  Many many failed porting attempts due to this design point in windows, many lost opportunities for Microsoft on the server side...

In general, the Microsoft approach of throwing a modal dialog at an annoying design rather than fixing the annoying design is not one of my favorite things, as I have noted in the past on this blog.  Making the dialog more pretty is not a solution, sorry I'm not wow'ed by your new dialogs.

Thanks,

Davester (modal == evil)

September 18, 2007 4:29 PM
 

Christian Knott said:

DaveStar: http://msdn2.microsoft.com/en-us/library/aa363858.aspx is the documentation for CreateFile (aka OpenFile). Just Google "CreateFile".

The dwShareMode flag of CreateFile gives the choice of 0 - don't share nicely, FILE_SHARE_DELETE - allow deletes, FILE_SHARE_READ - allow others to read the file and FILE_SHARE_WRITE - allow other to write to the file.

http://msdn2.microsoft.com/en-us/library/aa363874.aspx gives all the entire matrix of the gory details of the combinations of the dwShareMode flag and the open rights flag.

So a carefully crafted server or client program can help here. The affordance provide by the *shell* only dialog here is rather nice, IMHO.

But I fail to see (could you provide an example?) of how this UI is hinderance to server developers? There are a 1 or 2 successful server products on Windows ;) - I written one or two myself.

September 20, 2007 10:23 PM
 

Davester said:

Christian:  I see your Google and raise you a Wikipedia!  ;)  http://en.wikipedia.org/wiki/File_locking

CreateFile is not the beginning nor end of this file locking issue.  The relevant quote from the wikipedia article follows:

"Any file that is executing on the computer system as a program (e.g., an EXE, COM, DLL, CPL or other binary program file format) is normally prevented by the file system from being opened for write or delete access, reporting a sharing violation, despite the fact that the program is not opened by any application. However, some access is still allowed. For example, a running application file can be renamed or copied (read) even when executing."

But back to your msdn reference:  it *looks* like CreateFile is all well and good for normal file handles and being unlock friendly.  I have yet to test that theory for myself to find out what the gotchas are there.  For example, say I open a file handle in my program for maximum friendliness (read, write, delete by others is ok).  What happens when I move that file to another folder using Windows explorer?  Fail with a lock?  Does the file handle travel such that we're still operating on the moved file contents?  Allowed, treated by the file handle like a delete?  I observe, based on the behavior of most Windows programs, that being file lock friendly is NOT the default behavior of CreateFile.  And, I suppose it must be elusive or onerous enough that most developers choose not to opt to be friendly.  

Regardless of how CreateFile fares, the fact still remains (and Wikipedia confirms it):  for open executables, the file lock is set by the OS itself - out of our control!  (Or is there some Win32 API good juju that can change that behavior?  Microsoft?)

Christian, you're a Windows developer, it sounds like.  Are you telling me you've never run into this problem with your .exe's or .dll's getting locked up such that you can't delete them?   You seem to be so fine with this goofball fiasco.  Yes, that's right I said it: this is a FIASCO!  Embarassing!  Alrighty, now I want to hear usecases from the grey beards at Microsoft!  Why why why?  Or will I only get excuses?  Is this just some CP/M, er, I mean, QDOS, uhm ah, erm, I mean *MSDOS* holdover?  Did someone get their SMB's in a twist back in the day?  If there's anything Microsoft needs to break backwards compatibility with is the exe file locking thing, at the very least.

Let me cut to the quick, I'd like the design to change:  at the very least, I'd like to opt for lock free file handles by setting some global thread/process setting, and have that option "just work"(tm).

Christian, let me give you an example of a failed porting effort that happened because of this file-locking-by-default design flaw.  We had a Java application running on Linux that created temp jar files.  These jar files contained generated java classes.  After jar production was done, we would load these jar files into memory using a custom ClassLoader and then reflect and call on the classes wrt to how they play in the wider execution environment.  

On Linux, after we were finished, we would go to the temp folder and delete.  The ClassLoader that had open file handles to these deleted jars was still in memory and wouldn't be garbage collected until Java saw fit to - I could care less if that ClassLoader freaks out that the jars were gone or not, I was done with that ClassLoader and Java generally knows how to take care of itself in situations like that anyhow.

On Windows, it was another story.  The ClassLoaders held open file handles to these jars that are locked.  After we were done with these jars, our file deletions would fail and those jars would be left on disk - essentially a leak of file space.  We had a couple options to select from.  1)  Stop Java every once in awhile (so we could be sure file locks were released) and clean out the temp dir.  2) We could write a complicated background daemon that waits for the custom ClassLoader to be garbage collected (at which time the file handle should be released) and then try to delete the temp jar files then.  3) do nothing, let the files leak.  Option 2 was the recommended one because it would approximate the behavior of the application on Linux without downtime.

Turns out none of those options were acceptable to the manager since it required server downtime and/or more code/testing and/or customer inconvenience.  Linux is pervasive enough, and we were told we would just require users run Linux rather than deal with this b.s.

I have three observations from this experience:

1) A lot of the time, the programmer is not directly responsible for opening a file handle.  An API is opening the file, or Java is, or some DLL (that I don't own) on the system is.  Since I don't always directly call the CreateFile API myself, I need to be able to specify to *my thread/process* scope that file locking is *off* by default.

2) When you have something running on a platform like Java, there can be very little tolerance from management for O.S. compatibility swizzling.  So, it doesn't take too many logs on the tracks to derail a porting project, especially when Windows compatibility is not mission critical.

3) To make file handle friendly code on windows costs both in terms of programmer time as well as extra execution resources (that are not required on other OS'es.)

Am I the only one who tears his hair out on this file locking thing?  I think this is just such an esoteric topic that when people run up against it, they kindof just roll over and wrap themselves in the suckiness like a blanket.  Well, I'm not snowed here.  In my opinion, this new dialog box is just as fugly as the old one.  Fix the design, please.

Davester

September 21, 2007 5:29 PM
Anonymous comments are disabled
Powered by Community Server, by Telligent Systems © 2006 Microsoft Corporation. All rights reserved. Terms of Use | Trademarks | Privacy Statement.