0

我有一个文件列表,一次只有一个文件在使用。所以我想知道特定程序正在使用哪个文件。因为我可以使用'unlocker'来找出一个正在使用的文件,就像这个问题提到的那样。但是我想要一种编程方式,以便我的程序可以帮助我找出答案。有什么办法吗?

特别是,任何 r/w 模式下的简单“打开”函数都可以访问使用文件,python 不会抛出任何异常。我可以知道哪个文件仅由“解锁器”使用。

我发现 w 模式下的 python 'open' 函数已经获得了该特定程序的访问权限,然后该程序就不能正常工作了。在这一刻,我打开解锁器,我可以看到两个进程正在访问该文件。是否有任何“弱”方法只能检测文件是否被使用?

4

2 回答 2

3

I'm not sure which of these two you want to find:

  • Are there are any existing HANDLEs for a given file, like the handle and Process Explorer tools shows?
  • Are there any existing locked HANDLEs for a given file, like the Unlocker tool shows.

But either way, the answer is similar.

Obviously it's doable, or those tools couldn't do it, right? Unfortunately, there is nothing in the Python stdlib that can help. So, how do you do it?

You will need to access Windows APIs functions—through pywin32, ctypes, or otherwise.

There are two ways to go about it. The first, mucking about with the NT kernel object APIs, is much harder, and only really needed if you need to work with very old versions of Windows. The second, NtQuerySystemInformation, is the one you probably want.

The details are pretty complicated (and not well documented), but they're explained in a CodeProject sample program and on the Sysinternals forums.

If you don't understand the code on those pages, or how to call it from Python, you really shouldn't be doing this. If you get the gist, but have questions, or get stuck, of course you can ask for help here (or at the sysinternals forums or elsewhere).

However, even if you have used ctypes for Windows before, there are a few caveats you may not know about:

Many of these functions either aren't documented, or the documentation doesn't tell you which DLL to find them in. They will all be in either ntdll or kernel32; in some cases, however, the documented NtFooBar name is just an alias for the real ZwFooBar function, so if you don't find NtFooBar in either DLL, look for ZwFooBar.

At least on older versions of Windows, ZwQuerySystemInformation does not act as you'd expect: You cannot call it with a 0 SystemInformationLength, check the ReturnLength, allocate a buffer of that size, and try again. The only thing you can do is start with a buffer with enough room for, say, 8 handles, try that, see if you get an error STATUS_INFO_LENGTH_MISMATCH, increase that number 8, and try again until it succeeds (or fails with a different error). The code looks something like this (assuming you've defined the SYSTEM_HANDLE structure):

STATUS_INFO_LENGTH_MISMATCH = 0xc0000004
i = 8
while True:
    class SYSTEM_HANDLE_INFORMATION(Structure):
        _fields_ = [('HandleCount', c_ulong),
                    ('Handles', SYSTEM_HANDLE * i)]
    buf = SYSTEM_HANDLE_INFORMATION()
    return_length = sizeof(buf)
    rc = ntdll.ZwQuerySystemInformation(SystemHandleInformation,
                                        buf, sizeof(buf),
                                        byref(return_length))
    if rc == STATUS_INFO_LENGTH_MISMATCH:
        i += 8
        continue
    elif rc == 0:
        return buf.Handles[:buf.HandleCount]
    else:
        raise SomeKindOfError(rc)

Finally, the documentation doesn't really explain this anywhere, but the way to get from a HANDLE that you know is a file to a pathname is a bit convoluted. Just using NtQueryObject(ObjectNameInformation) returns you a kernel object space pathname, which you then have to map to either a DOS pathname, a possibly-UNC normal NT pathname, or a \?\ pathname. Of course the first doesn't work files on network drives without a mapped drive letter; neither of the first two work for files with very long pathnames.


Of course there's a simpler alternative: Just drive handle, Unlocker, or some other command-line tool via subprocess.


Or, somewhere in between, build the CodeProject project linked above and just open its DLL via ctypes and call its GetOpenedFiles method, and it will do the hard work for you.

Since the project builds a normal WinDLL-style DLL, you can call it in the normal ctypes way. If you've never used ctypes, the examples in the docs show you almost everything you need to know, but I'll give some pseudocode to get you started.

First, we need to create an OF_CALLBACK type for the callback function you're going to write, as described in Callback functions. Since the prototype for OF_CALLBACK is defined in a .h file that I can't get to here, I'm just guessing at it; you'll have to look at the real version and translate it yourself. But your code is going to look something like this:

from ctypes import windll, c_int, WINFUNCTYPE
from ctypes.wintypes import LPCWSTR, UINT_PTR, HANDLE

# assuming int (* OF_CALLBACK)(int, HANDLE, int, LPCWSTR, UINT_PTR)
OF_CALLBACK = WINFUNCTYPE(c_int, HANDLE, c_int, LPWCSTR, UINT_PTR)
def my_callback(handle, namelen, name, context):
    # do whatever you want with each handle, and whatever other args you get
my_of_callback = OF_CALLBACK(my_callback)

OpenFileFinder = windll.OpenFileFinder
# Might be GetOpenedFilesW, as with most Microsoft DLLs, as explained in docs
OpenFileFinder.GetOpenedFiles.argtypes = (LPCWSTR, c_int, OF_CALLBACK, UINT_PTR)
OpenFileFinder.GetOpenedFiles.restype = None

OpenFileFinder.GetOpenedFiles(ru'C:\Path\To\File\To\Check.txt', 0, my_of_callback, None)

It's quite possible that what you get in the callback is actually a pointer to some kind of structure or list of structures you're going to have to define—likely the same SYSTEM_HANDLE you'd need for calling the Nt/Zw functions directly, so let me show that as an example—if you get back a SYSTEM_HANDLE *, not a HANDLE, in the callback, it's as simple as this:

class SYSTEM_HANDLE(Structure):
    _fields_ = [('dwProcessId', DWORD),
                ('bObjectType', BYTE),
                ('bFlags', BYTE),
                ('wValue', WORD),
                ('pAddress', PVOID),
                ('GrantedAccess', DWORD)]

# assuming int (* OF_CALLBACK)(int, SYSTEM_HANDLE *, int, LPCWSTR, UINT_PTR)
OF_CALLBACK = WINFUNCTYPE(c_int, POINTER(SYSTEM_HANDLE), c_int, LPWCSTR, UINT_PTR)
于 2013-08-03T02:35:36.310 回答
0

您可以使用 try/except 块。

检查文件是否在 Python 中打开

   try:
     f = open("file.txt", "r")
   except IOError:
     print "This file is already in use"
于 2013-08-03T02:06:32.303 回答