2012-01-04 27 views
14

Ho un'app Delphi che scrive regolarmente su un file su disco locale. A volte non è possibile accedere al file: si verifica una violazione della condivisione quando tenta di aprirlo. Un tentativo dopo un breve ritardo è tutto ciò che è necessario, ma quando si verifica, vorrei segnalare il processo che ha impedito l'accesso.Delphi: ricerca del processo che sta accedendo a un file dal mio programma

È possibile che quando si verifica una violazione di condivisione per il mio programma per enumerare tutti gli handle di file in uso, ispezionare il nome file e, se corrisponde al nome del file di dati, recupera il nome del processo associato a tale handle?

Qualche esempio di codice sarebbe bello.

+1

È possibile farlo con WMI e 'cim_datafile' credo. Ma non so nulla di WMI. Tuttavia, sono fiducioso che uno degli altri esperti qui che ha una specializzazione in WMI sarà in grado di aiutarti! –

+3

Quale versione di Windows è necessario supportare? Se Windows Vista si trova su ['this post'] (http://blog.delphi-jedi.net/2010/11/14/is-file-in-use/), usa [' IFileIsInUse'] (http://msdn.microsoft.com/en-us/library/bb775874%28VS.85%29.aspx) interfaccia. – TLama

+0

Utilizziamo http://technet.microsoft.com/en-us/sysinternals/bb896655 nel nostro software. Il processo con il file in uso viene registrato utilizzando le informazioni di handle.exe, uno strumento gratuito di SysInternals (ora di proprietà di Microsoft). –

risposta

11

Hai fondamentalmente due modi

La via più semplice

se si utilizza Windows Vista o più recente provare l'interfaccia IFileIsInUse

The Hard Way

se avete bisogno di un metodo compatibile con Windows XP, Vista, 7 e così via. quindi usi le funzioni NtQuerySystemInformation, NtQueryInformationFile e NtQueryObject.

Questi sono i passi per procedere

  1. Chiamare il NtQuerySystemInformation passando il valore non documentato SystemHandleInformation ($10) per ottenere l'elenco delle maniglie
  2. poi elaborare l'elenco delle maniglie (solo per ObjectType = 28), che sono file.
  3. chiamata OpenProcess con PROCESS_DUP_HANDLE
  4. quindi chiamare DuplicateHandle per ottenere un handle real al file.
  5. ottenere il nome del nome file asociato all'handle utilizzando le funzioni NtQueryInformationFile e NtQueryObject.

Nota 1: la parte difficile di questo metodo è la risoluzione del nome file basato su un handle. la funzione NtQueryInformationFile si blocca in alcuni scenari (handle di sistema e altri) una soluzione alternativa per impedire che l'intera applicazione si blocchi è chiamare la funzione da un thread separato.

Nota 2: esistono altre funzioni come GetFileInformationByHandleEx e GetFinalPathNameByHandle per risolvere il nome file di un handle. ma entrambi esistono da quando Windows viste e in tal caso è meglio usare IFileIsInUse.

Controllare questa applicazione di esempio testata in Delphi 2007, XE2 e Windows XP e 7. da qui è possibile prendere alcune idee per risolvere il problema.

Nota: la funzione GetProcessIdUsingFile confronta solo il nome dei file (non il percorso).

{$APPTYPE CONSOLE} 


uses 
    Windows, 
    SysUtils; 

const 
    SystemHandleInformation = $10; 
    STATUS_SUCCESS   = $00000000; 
    FileNameInformation  = 9; 
    ObjectNameInformation = 1; 

type 
SYSTEM_HANDLE=packed record 
    uIdProcess:ULONG; 
    ObjectType:UCHAR; 
    Flags  :UCHAR; 
    Handle :Word; 
    pObject :Pointer; 
    GrantedAccess:ACCESS_MASK; 
end; 

SYSTEM_HANDLE_ARRAY = Array[0..0] of SYSTEM_HANDLE; 

SYSTEM_HANDLE_INFORMATION=packed record 
uCount:ULONG; 
Handles:SYSTEM_HANDLE_ARRAY; 
end; 
PSYSTEM_HANDLE_INFORMATION=^SYSTEM_HANDLE_INFORMATION; 

    NT_STATUS = Cardinal; 

    PFILE_NAME_INFORMATION = ^FILE_NAME_INFORMATION; 
    FILE_NAME_INFORMATION = packed record 
    FileNameLength: ULONG; 
    FileName: array [0..MAX_PATH - 1] of WideChar; 
    end; 

    PUNICODE_STRING = ^TUNICODE_STRING; 
    TUNICODE_STRING = packed record 
    Length : WORD; 
    MaximumLength : WORD; 
    Buffer : array [0..MAX_PATH - 1] of WideChar; 
    end; 

    POBJECT_NAME_INFORMATION = ^TOBJECT_NAME_INFORMATION; 
    TOBJECT_NAME_INFORMATION = packed record 
    Name : TUNICODE_STRING; 
    end; 

    PIO_STATUS_BLOCK = ^IO_STATUS_BLOCK; 
    IO_STATUS_BLOCK = packed record 
    Status: NT_STATUS; 
    Information: DWORD; 
    end; 

    PGetFileNameThreadParam = ^TGetFileNameThreadParam; 
    TGetFileNameThreadParam = packed record 
    hFile : THandle; 
    Result : NT_STATUS; 
    FileName : array [0..MAX_PATH - 1] of AnsiChar; 
    end; 

    function NtQueryInformationFile(FileHandle: THandle; 
    IoStatusBlock: PIO_STATUS_BLOCK; FileInformation: Pointer; 
    Length: DWORD; FileInformationClass: DWORD): NT_STATUS; 
    stdcall; external 'ntdll.dll'; 

    function NtQueryObject(ObjectHandle: THandle; 
    ObjectInformationClass: DWORD; ObjectInformation: Pointer; 
    ObjectInformationLength: ULONG; 
    ReturnLength: PDWORD): NT_STATUS; stdcall; external 'ntdll.dll'; 

    function NtQuerySystemInformation(SystemInformationClass: DWORD; SystemInformation: Pointer; SystemInformationLength: ULONG; ReturnLength: PULONG): NT_STATUS; stdcall; external 'ntdll.dll' name 'NtQuerySystemInformation'; 


function GetFileNameHandleThr(Data: Pointer): DWORD; stdcall; 
var 
    dwReturn: DWORD; 
    FileNameInfo: FILE_NAME_INFORMATION; 
    ObjectNameInfo: TOBJECT_NAME_INFORMATION; 
    IoStatusBlock: IO_STATUS_BLOCK; 
    pThreadParam: TGetFileNameThreadParam; 
begin 
    ZeroMemory(@FileNameInfo, SizeOf(FILE_NAME_INFORMATION)); 
    pThreadParam := PGetFileNameThreadParam(Data)^; 
    Result := NtQueryInformationFile(pThreadParam.hFile, @IoStatusBlock, @FileNameInfo, MAX_PATH * 2, FileNameInformation); 
    if Result = STATUS_SUCCESS then 
    begin 
    Result := NtQueryObject(pThreadParam.hFile, ObjectNameInformation, @ObjectNameInfo, MAX_PATH * 2, @dwReturn); 
    if Result = STATUS_SUCCESS then 
    begin 
     pThreadParam.Result := Result; 
     WideCharToMultiByte(CP_ACP, 0, @ObjectNameInfo.Name.Buffer[ObjectNameInfo.Name.MaximumLength - ObjectNameInfo.Name.Length], ObjectNameInfo.Name.Length, @pThreadParam.FileName[0], MAX_PATH, nil, nil); 
    end 
    else 
    begin 
     pThreadParam.Result := STATUS_SUCCESS; 
     Result := STATUS_SUCCESS; 
     WideCharToMultiByte(CP_ACP, 0, @FileNameInfo.FileName[0], IoStatusBlock.Information, @pThreadParam.FileName[0], MAX_PATH, nil, nil); 
    end; 
    end; 
    PGetFileNameThreadParam(Data)^ := pThreadParam; 
    ExitThread(Result); 
end; 

function GetFileNameHandle(hFile: THandle): String; 
var 
    lpExitCode: DWORD; 
    pThreadParam: TGetFileNameThreadParam; 
    hThread: THandle; 
begin 
    Result := ''; 
    ZeroMemory(@pThreadParam, SizeOf(TGetFileNameThreadParam)); 
    pThreadParam.hFile := hFile; 
    hThread := CreateThread(nil, 0, @GetFileNameHandleThr, @pThreadParam, 0, PDWORD(nil)^); 
    if hThread <> 0 then 
    try 
    case WaitForSingleObject(hThread, 100) of 
     WAIT_OBJECT_0: 
     begin 
     GetExitCodeThread(hThread, lpExitCode); 
     if lpExitCode = STATUS_SUCCESS then 
      Result := pThreadParam.FileName; 
     end; 
     WAIT_TIMEOUT: 
     TerminateThread(hThread, 0); 
    end; 
    finally 
    CloseHandle(hThread); 
    end; 
end; 

//get the pid of the process which had open the specified file 
function GetProcessIdUsingFile(const TargetFileName:string): DWORD; 
var 
hProcess : THandle; 
hFile  : THandle; 
ReturnLength: DWORD; 
SystemInformationLength : DWORD; 
Index  : Integer; 
pHandleInfo : PSYSTEM_HANDLE_INFORMATION; 
hQuery  : THandle; 
FileName : string; 
begin 
    Result:=0; 
    pHandleInfo  := nil; 
    ReturnLength  := 1024; 
    pHandleInfo  := AllocMem(ReturnLength); 
    hQuery   := NTQuerySystemInformation(DWORD(SystemHandleInformation), pHandleInfo, 1024, @ReturnLength); 
    if ReturnLength<>0 then 
    begin 
    FreeMem(pHandleInfo); 
    SystemInformationLength := ReturnLength; 
    pHandleInfo    := AllocMem(ReturnLength+1024); 
    hQuery     := NTQuerySystemInformation(DWORD(SystemHandleInformation), pHandleInfo, SystemInformationLength, @ReturnLength);//Get the list of handles 
    end 
    else 
    RaiseLastOSError; 

    try 
    if(hQuery = STATUS_SUCCESS) then 
    begin 
     for Index:=0 to pHandleInfo^.uCount-1 do 
     if pHandleInfo.Handles[Index].ObjectType=28 then 
     begin 
     hProcess := OpenProcess(PROCESS_DUP_HANDLE, FALSE, pHandleInfo.Handles[Index].uIdProcess); 
     if(hProcess <> INVALID_HANDLE_VALUE) then 
     begin 
      try 
      if not DuplicateHandle(hProcess, pHandleInfo.Handles[Index].Handle,GetCurrentProcess(), @hFile, 0 ,FALSE, DUPLICATE_SAME_ACCESS) then 
      hFile := INVALID_HANDLE_VALUE; 
      finally 
      CloseHandle(hProcess); 
      end; 

      if (hFile<>INVALID_HANDLE_VALUE) then 
      begin 
      try 
       FileName:=GetFileNameHandle(hFile); 
      finally 
       CloseHandle(hFile); 
      end; 
      end 
      else 
      FileName:=''; 

      //Writeln(FileName); 
      if CompareText(ExtractFileName(FileName), TargetFileName)=0 then 
      Result:=pHandleInfo.Handles[Index].uIdProcess; 
     end; 
     end; 
    end; 
    finally 
    if pHandleInfo<>nil then 
    FreeMem(pHandleInfo); 
    end; 
end; 

function SetDebugPrivilege: Boolean; 
var 
    TokenHandle: THandle; 
    TokenPrivileges : TTokenPrivileges; 
begin 
    Result := false; 
    if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TokenHandle) then 
    begin 
    if LookupPrivilegeValue(nil, PChar('SeDebugPrivilege'), TokenPrivileges.Privileges[0].Luid) then 
    begin 
     TokenPrivileges.PrivilegeCount := 1; 
     TokenPrivileges.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; 
     Result := AdjustTokenPrivileges(TokenHandle, False, 
     TokenPrivileges, 0, PTokenPrivileges(nil)^, PDWord(nil)^); 
    end; 
    end; 
end; 

begin 
    try 
    SetDebugPrivilege; 
    Writeln('Processing'); 
    Writeln(GetProcessIdUsingFile('MyFile.txt')); 
    Writeln('Done'); 
    except 
    on E:Exception do 
     Writeln(E.Classname, ': ', E.Message); 
    end; 
    Readln; 
end. 
+0

Le dichiarazioni delle API NtXXX sono già in Jedi ApiLib (JwaNative.pas) ... – Remko

+0

Grazie @RRUZ per una risposta molto completa. La piattaforma è XP e W7. Ho compilato il codice fornito e mentre funziona, ho il sospetto che non posso fare quello che voglio perché il probabile ritardo nella scansione delle maniglie significa che il processo incriminato avrà probabilmente finito con il file e da tempo sparito. Riprovo dopo un ritardo di 50 mS quando ottengo la violazione della condivisione e nella maggior parte dei casi il secondo tentativo ha esito positivo. Il mio iniziale gioco con il codice fornito suggerisce ritardi nell'ordine dei secondi per la scansione dell'elenco di maniglie. – rossmcm

2

Usando NtQuerySystemInformation è possibile elencare tutti gli handle aperti da tutti i processi è possibile utilizzare questa funzione per ottenere il nome del file

function NtQueryInformationFile(FileHandle: THandle;IoStatusBlock: PIO_STATUS_BLOCK; FileInformation: Pointer;Length: DWORD; FileInformationClass: DWORD): NTSTATUS;stdcall; external 'ntdll.dll'; 

function GetFileNameFromHandle(const hFile: THandle): string; 
var 
    IO_STATUSBLOCK:IO_STATUS_BLOCK; 
    FileNameInfo:FILE_NAME_INFORMATION; 
    szFile:String; 
begin 
    FillChar(FileNameInfo.FileName,SizeOf(FileNameInfo.FileName),0); 
    NtQueryInformationFile(hFile,@IO_STATUSBLOCK,@FileNameInfo,500,9); 
    szFile:=WideCharToString(FileNameInfo.fileName); 
    CloseHandle(hFile); 
    Result:=szFile; 
end; 

Se questo è il file che susciterà un messaggio ...

+0

Quale classe di informazioni di sistema renderà NtQuerySystemInformation a dirci le maniglie di tutti gli altri processi? Vedo solo quello che ci dirà * come molti * gestisce ogni processo ha. –

+2

@RobKennedy http://forum.sysinternals.com/howto-enumerate-handles_topic18892.html – opc0de

+0

@ opc0de +1 per il link al forum SysInternals – rossmcm

0

Si può trovare una sorgente di esempio per l'interfaccia IFileIsInUse dal progetto JEDI qui: https://svn.code.sf.net/p/jedi-apilib/code/jwapi/trunk/Examples/FileIsInUse/Client/FileIsInUseClientExample.dpr

{******************************************************************************} 
{ JEDI FileIsInUse Example Project            } 
{ http://jedi-apilib.sourceforge.net           } 
{                    } 
{ Obtained through: Joint Endeavour of Delphi Innovators (Project JEDI)  } 
{                    } 
{ Author(s): Christian Wimmer             } 
{                    } 
{ Description: Shows how to use the IFileIsInUse API       } 
{                    } 
{ Preparations: JWA must be ready to use.          } 
{    Requires at least Windows Vista        } 
{                    } 
{ Version history: 14th November 2010 initial release       } 
{                    } 
{ No license. Use this example with no warranty at all and on your own risk. } 
{ This example is just for learning purposes and should not be used in   } 
{ productive environments.              } 
{ The code has surely some errors that need to be fixed. In such a case  } 
{ you can contact the author(s) through the JEDI API hompage, the mailinglist } 
{ or via the article link.              } 
{                    } 
{******************************************************************************} 
program FileIsInUseClientExample; 


{Define this switch to use the definition of the IFileIsInUse interface from 
the JEDI API units. 
Undefine it, to use it from the file here. 
} 
{.$DEFINE JWA_BUILTIN_IFILEISINUSE} 

uses 
    ComObj, 
    ActiveX, 
    SysUtils, 
    JwaWinType, 
    JwaWinUser 
{$IFDEF JWA_BUILTIN_IFILEISINUSE} 
    ,JwaShlObj 
{$ENDIF JWA_BUILTIN_IFILEISINUSE} 
    ; 

{$IFNDEF JWA_BUILTIN_IFILEISINUSE} 
{$ALIGN 4} 
const 
    IID_IFileIsInUse: TGUID = (
    D1:$64a1cbf0; D2:$3a1a; D3:$4461; D4:($91,$58,$37,$69,$69,$69,$39,$50)); 

type 
    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 JWA_BUILTIN_IFILEISINUSE} 

function GetFileInUseInfo(const FileName : WideString) : IFileIsInUse; 
var 
    ROT : IRunningObjectTable; 
    mFile, enumIndex, Prefix : IMoniker; 
    enumMoniker : IEnumMoniker; 
    MonikerType : LongInt; 
    unkInt : IInterface; 
    ctx : IBindCtx; 
    sEnumIndex, sFile : PWideChar; 
begin 
    result := nil; 
    OleCheck(CreateBindCtx(0, ctx)); 

    // 
    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 
     OleCheck((EnumIndex as IMoniker).GetDisplayName(ctx, nil, sEnumIndex)); 

     sFile := CoTaskMemAlloc(MAX_PATH); 
     OleCheck(mFile.GetDisplayName(ctx, nil, sFile)); 

     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; 

const 
    TFileUsageTypeStr : array[TFileUsageType] of String = (
    'FUT_PLAYING (0)', 
    'FUT_EDITING (1)', 
    'FUT_GENERIC (2)'); 

    CapStr : array[1..3] of String = (
    'OF_CAP_CANSWITCHTO ($0001)', 
    'OF_CAP_CANCLOSE ($0002)', 
    'OF_CAP_CANSWITCHTO ($0001) or OF_CAP_CANCLOSE ($0002)' 
); 


var 
    FileInUse : IFileIsInUse; 
    pAppName : PWidechar; 
    Usage : TFileUsageType; 
    Caps : Cardinal; 
    WindowHandle : HWND; 
    Msg, S : String; 
    Buttons : Integer; 
begin 
    CoInitialize(nil); 

    if not FileExists(ParamStr(1)) then 
    begin 
    MessageBox(0, 'Missing filename as command line parameter', '', MB_ICONERROR or MB_OK); 
    exit; 
    end; 

    FileInUse := GetFileInUseInfo(ParamStr(1)); 

    if Assigned(FileInUse) then 
    begin 
    OleCheck(FileInUse.GetAppName(pAppName)); 
    OleCheck(FileInUse.GetUsage(Usage)); 
    OleCheck(FileInUse.GetCapabilities(Caps)); 
    OleCheck(FileInUse.GetSwitchToHWND(WindowHandle)); 

    Buttons := MB_OK; 

    if (Caps and OF_CAP_CANSWITCHTO = OF_CAP_CANSWITCHTO) then 
    begin 
     Msg := 'YES = Switch to Window? NO = Send close file; Cancel= Do nothing'; 
     Buttons := MB_YESNOCANCEL; 
    end; 


    S := Format('AppName: %s'#13#10'Usage: %s'#13#10'Caps: %s'#13#10'Hwnd: %d'#13#10+Msg, 
     [WideString(pAppName), TFileUsageTypeStr[Usage], CapStr[Caps], WindowHandle]); 

    case MessageBox(0, PChar(S), '', MB_ICONINFORMATION or Buttons) of 
     IDYES: 
     begin 
     SetForegroundWindow(WindowHandle); 
     Sleep(2000); //allows the window to be displayed in front; otherwise IDE will be shown 
     end; 
     IDNO: 
     begin 
     OleCheck(FileInUse.CloseFile); 
     end; 
    end; 

    CoTaskMemFree(pAppName); 
    end; 
end.