2012-01-04 38 views
14

मेरे पास एक डेल्फी ऐप है जो नियमित रूप से स्थानीय डिस्क फ़ाइल को लिखता है। कभी-कभी यह फ़ाइल तक पहुंचने में असमर्थ है - एक साझाकरण उल्लंघन परिणाम जब इसे खोलने का प्रयास करता है। थोड़ी देर के बाद पुनः प्रयास करना आवश्यक है, लेकिन जब ऐसा होता है, तो मैं उस प्रक्रिया की रिपोर्ट करना चाहता हूं जो पहुंच को रोकता है।डेल्फी - मेरे प्रोग्राम से फ़ाइल तक पहुंचने वाली प्रक्रिया को ढूंढना

क्या यह संभव है जब मेरे प्रोग्राम के लिए सभी फ़ाइल हैंडल का उपयोग करने के लिए एक साझाकरण उल्लंघन होता है, फ़ाइल नाम का निरीक्षण करें, और यदि यह मेरी डेटा फ़ाइल के नाम से मेल खाता है, तो उस हैंडल से जुड़े प्रक्रिया का नाम पुनर्प्राप्त करता है?

कुछ उदाहरण कोड अच्छा होगा।

+1

आप WMI और 'cim_datafile' मेरा मानना ​​है कि के साथ ऐसा कर सकते हैं। लेकिन मुझे डब्लूएमआई के बारे में कुछ नहीं पता है। हालांकि, मुझे उम्मीद है कि यहां अन्य विशेषज्ञों में से एक जो डब्लूएमआई में विशेषता रखते हैं, आपकी मदद कर सकेंगे! –

+3

विंडोज के किस संस्करण को आपको समर्थन देने की आवश्यकता है? यदि Windows Vista ऊपर है तो ['this post'] देखें (http://blog.delphi-jedi.net/2010/11/14/is-file-in-use/), यह [' IFileIsInUse'] का उपयोग करता है (http://msdn.microsoft.com/en-us/library/bb775874%28VS.85%29.aspx) इंटरफ़ेस। – TLama

+0

हम अपने सॉफ्टवेयर में http://technet.microsoft.com/en-us/sysinternals/bb896655 का उपयोग करते हैं। उपयोग में फ़ाइल रखने वाली प्रक्रिया को हैंडल.एक्सई की जानकारी का उपयोग करके लॉग किया गया है जो SysInternals (अब माइक्रोसॉफ्ट के स्वामित्व में) द्वारा एक निःशुल्क टूल है। –

उत्तर

11

आप मूल रूप से दो तरह से

आसान तरीका

है

आप Windows Vista का उपयोग कर रहे या नए IFileIsInUse इंटरफ़ेस

मुश्किल रास्ता

कोशिश आप एक विधि विंडोज एक्सपी, विस्टा, 7 और इतने पर के साथ संगत की जरूरत है, तो। तो आप NtQuerySystemInformation, NtQueryInformationFile और NtQueryObject फ़ंक्शंस का उपयोग करें।

ये आगे बढ़ने के लिए चरणों

  1. कॉल NTQuerySystemInformation अप्रलेखित SystemHandleInformation ($10) मूल्य गुजर हैंडल
  2. की सूची प्राप्त करने जो फ़ाइलें हैं (केवल ObjectType = 28 के लिए) तो हैंडल की सूची पर कार्रवाई कर रहे हैं।
  3. PROCESS_DUP_HANDLE
  4. साथ कॉल OpenProcess तो DuplicateHandle फोन के लिए फाइल करने के लिए एक real संभाल मिलता है।
  5. NtQueryInformationFile और NtQueryObject फ़ंक्शंस का उपयोग करके हैंडल के लिए संबद्ध फ़ाइल नाम का नाम प्राप्त करें।

नोट 1: इस विधि का मुश्किल हिस्सा एक हैंडल में आधारित फ़ाइल नाम को हल है। फ़ंक्शन NtQueryInformationFile कुछ परिदृश्यों (सिस्टम हैंडल और अन्य) में लटकता है, पूरे एप्लिकेशन को लटकने से रोकने के लिए एक वर्कअराउंड फ़ंक्शन को एक अलग थ्रेड से कॉल करता है।

नोट 2: हैंडल के फ़ाइल नाम को हल करने के लिए GetFileInformationByHandleEx और GetFinalPathNameByHandle जैसे अन्य फ़ंक्शंस मौजूद हैं। लेकिन दोनों मौजूद हैं क्योंकि Windows viste इस तरह के मामले में डी 0 बेहतर उपयोग IFileIsInUse है।

डेल्फी 2007, एक्सई 2 और विंडोज एक्सपी और 7 में परीक्षण किए गए इस नमूना अनुप्रयोग की जांच करें। यहां से आप अपनी समस्या को हल करने के लिए कुछ विचार ले सकते हैं।

नोट: फ़ंक्शन GetProcessIdUsingFile केवल फ़ाइलों का नाम (पथ नहीं) की तुलना करता है।

{$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

एनटीXXएक्स एपीआई की घोषणाएं पहले ही जेडी अपिलीब (JwaNative.pas) में हैं ... – Remko

+0

धन्यवाद एक बहुत ही पूर्ण उत्तर के लिए @ आरआरयूजेड। मंच एक्सपी और डब्ल्यू 7 है। मैंने दिए गए कोड को संकलित किया और यह काम करता है, मुझे संदेह है कि मैं जो भी चाहता हूं वह नहीं कर सकता क्योंकि हैंडल स्कैन करने में संभावित देरी का मतलब है कि अपमानजनक प्रक्रिया शायद फाइल के साथ समाप्त हो जाएगी और लंबे समय तक चली जाएगी। मैं 50 एमएस देरी के बाद पुनः प्रयास करता हूं जब मुझे साझाकरण उल्लंघन मिलता है और ज्यादातर मामलों में दूसरा प्रयास सफल होता है। आपूर्ति कोड के साथ मेरा प्रारंभिक खेल हैंडल की सूची स्कैन करने के लिए सेकंड के क्रम में देरी का सुझाव देता है। – rossmcm

2

का उपयोग NtQuerySystemInformation आप सभी प्रक्रियाओं द्वारा सभी खोला हैंडल सूचीबद्ध कर सकते हैं तो आप इस सुविधा का उपयोग इस संदेश ऊपर उठाने से आपकी फ़ाइल है, तो फ़ाइल नाम

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; 

प्राप्त करने के लिए कर सकते हैं ...

+0

कौन सी सिस्टम सूचना क्लास NtQuerySystemInformation को अन्य सभी प्रक्रियाओं के हैंडल बताएगी? मुझे केवल वह ही दिखाई देगा जो हमें बताएगा * कैसे कई * प्रत्येक प्रक्रिया को संभालती है। –

+2

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

+0

@ysc0de +1 SysInternals फोरम लिंक – rossmcm

0

आप JEDI परियोजना यहाँ से IFileIsInUse इंटरफेस के लिए एक स्रोत उदाहरण मिल सकते हैं: 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.