In a thread on DelphiPraxis there was a question that comes up several times a year. However, this time I could remember that there is actually an COM API that can solve the problem. The question was about how to find out which process has a lock on a file. The main problem was, in deed, that I could not remember the API’s name so Assarbad put some effort in retrieving it. Thanks.
The name of the API is IFileIsInUse, an interface. It resides in Shobjidl.h, Shobjidl.idl and newly in JwaShlObj.pas. This is the translation:
{$IFDEF WINVISTA_UP}
const
IID_IFileIsInUse: TGUID = (
D1:$64a1cbf0; D2:$3a1a; D3:$4461; D4:($91,$58,$37,$69,$69,$69,$39,$50));
type
{$ALIGN 4}
tagFILE_USAGE_TYPE = (
FUT_PLAYING = 0,
FUT_EDITING = 1,
FUT_GENERIC = 2
);
FILE_USAGE_TYPE = tagFILE_USAGE_TYPE;
TFileUsageType = FILE_USAGE_TYPE;
const
OF_CAP_CANSWITCHTO = $0001;
OF_CAP_CANCLOSE = $0002;
type
IFileIsInUse = interface(IUnknown)
['{64a1cbf0-3a1a-4461-9158-376969693950}']
function GetAppName(out ppszName: LPWSTR) : HRESULT; stdcall;
function GetUsage(out pfut : FILE_USAGE_TYPE) : HRESULT; stdcall;
function GetCapabilities(out pdwCapFlags : DWORD) : HRESULT; stdcall;
function GetSwitchToHWND(out phwnd : HWND) : HRESULT; stdcall;
function CloseFile() : HRESULT; stdcall;
end;
{$ENDIF WINVISTA_UP}
The interface can be used as a client and also can be implemented by a server. A client usually checks if a file has a lock and retrieves a pointer to this interface to access the methods. A server usually holds a lock on a given file and implements the interface to provide the methods to the client. This article will discuss only the client side.
You see that this API only works if both sides do their jobs. A process that locks a file must also implement this interface and register it, so a client can receive a status information. There is no direct link between a file lock and the process name. If a process holds a lock on a file but does not implement the interface you are on your own again.
Eventually, this is only a shell helper for the nice Windows Explorer deletion dialog.
IFileIsInUse = interface(IUnknown)
function GetAppName(out ppszName: LPWSTR) : HRESULT; stdcall;
function GetUsage(out pfut : FILE_USAGE_TYPE) : HRESULT; stdcall;
function GetCapabilities(out pdwCapFlags : DWORD) : HRESULT; stdcall;
function GetSwitchToHWND(out phwnd : HWND) : HRESULT; stdcall;
function CloseFile() : HRESULT; stdcall;
end;
The methods of the interface are really easy to understand.
To retrieve an interface on the file you have to lock it first. Either you download and compile the MSDN example IsFileInUse or you open up an application that implements the interface (e.g. a PDF Reader, MS Office).
The next step is to know where the locked files are placed. The location is the running object table, short ROT (ActiveX.GetRunningObjectTable() retrieves it). It is a global* (on machine) table that holds running COM objects. You can implement your own interfaces and put it in this table. Because interfaces can be arbitrary in its structure and reason to be, they are attached to monikers (IMoniker) which describe them uniquely. There are several types of monikers like class, item and file moniker (more information provides IMoniker in MSDN). We are interested in the latter only.
So the whole work is an enumeration over all monikers in the ROT. Usually there are not that much in there (I got 5 here). Each moniker is checked for its type (IsSystemMoniker) and if it is a file moniker (MKSYS_FILEMONIKER) the path is compared to the input parameter FileName. Honestly, I cannot tell why there is a comparison of the prefix at first followed by a comparison of the moniker itself, sorry.
The object itself is retrieved from the moniker by GetObject. It may fail with E_ACCESS_DENIED so a check is done using Succeeded. In the end, we can try to retrieve the IFileIsInUse interface. There may be such a file registered but without the implementation of the interface, thus an additional check.
I didn’t create this whole source by myself. In fact, there is a FileIsInUse example in MSDN that is the base of this article. The original source is located in the docx file in the download. The example FileIsInUse will create a server.
function GetFileInUseInfo(const FileName : WideString) : IFileIsInUse;
var
ROT : IRunningObjectTable;
mFile, enumIndex, Prefix : IMoniker;
enumMoniker : IEnumMoniker;
MonikerType : LongInt;
unkInt : IInterface;
begin
result := nil;
OleCheck(GetRunningObjectTable(0, ROT));
OleCheck(CreateFileMoniker(PWideChar(FileName), mFile));
OleCheck(ROT.EnumRunning(enumMoniker));
while (enumMoniker.Next(1, enumIndex, nil) = S_OK) do
begin
OleCheck(enumIndex.IsSystemMoniker(MonikerType));
if MonikerType = MKSYS_FILEMONIKER then
begin
if Succeeded(mFile.CommonPrefixWith(enumIndex, Prefix)) and
(mFile.IsEqual(Prefix) = S_OK) then
begin
if Succeeded(ROT.GetObject(enumIndex, unkInt)) then
begin
if Succeeded(unkInt.QueryInterface(IID_IFileIsInUse, result)) then
begin
result := unkInt as IFileIsInUse;
exit;
end;
end;
end;
end;
end;
end;
This whole API has some caveats
You can download the example file from the Subversion directly.
| https://jedi-apilib.svn.sourceforge.net/svnroot/jedi-apilib/jwapi/trunk/Examples/FileIsInUse/Client/FileIsInUseClientExample.dpr |
The next article discusses the the implementation if the interface IFileIsInUse.
2 Responses
Mason Wheeler
15|Nov|2010 1Very nice.
BTW you can declare your const GUID a lot more readably like this:
IID_IFileIsInUse: TGUID = ‘{64a1cbf0-3a1a-4461-9158-376969693950}’;
The compiler will accept it.
Christian Wimmer
15|Nov|2010 2Yes, this will do fine. However, the basic code was auto generated so I didn’t think about this detail too much.
Leave a reply
You must be logged in to post a comment.