我正在尝试使用python中的ReadDirectoryChangesW
(带有完成例程的异步模式)。
在MS documentation的备注之后,我了解到必须提供OVERLAPPED结构和回调。
使用该文档描述回调:
VOID CALLBACK FileIOCompletionRoutine(
_In_ DWORD dwErrorCode,
_In_ DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
);
所以我将ctypes
WINFUNCTYPE工厂实例化为:
RDCW_CALLBACK_F = ctypes.WINFUNCTYPE(None, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.POINTER(OVERLAPPED))
并尝试访问FileIOCompletionRoutine
来电:
ReadDirectoryChangesW = ctypes.windll.kernel32.ReadDirectoryChangesW
ReadDirectoryChangesW.restype = ctypes.wintypes.BOOL
ReadDirectoryChangesW.errcheck = _errcheck_bool
ReadDirectoryChangesW.argtypes = (
ctypes.wintypes.HANDLE, # hDirectory
LPVOID, # lpBuffer
ctypes.wintypes.DWORD, # nBufferLength
ctypes.wintypes.BOOL, # bWatchSubtree
ctypes.wintypes.DWORD, # dwNotifyFilter
ctypes.POINTER(ctypes.wintypes.DWORD), # lpBytesReturned
ctypes.POINTER(OVERLAPPED), # lpOverlapped
RDCW_CALLBACK_F # FileIOCompletionRoutine # lpCompletionRoutine
)
请注意,如果我尝试将FileIOCompletionRoutine
用作同步调用(将None
传递给其最后两个参数),则可以正常工作。
当我尝试传递完成例程时出现问题:
#Routine implementation
def dir_change_callback(dwErrorCode,dwNumberOfBytesTransfered,p):
print("dir_change_callback!")
#Get WINFUNC from factory
call2pass = RDCW_CALLBACK_F(dir_change_callback)
try:
event_buffer = ctypes.create_string_buffer(BUFFER_SIZE)
nbytes = ctypes.wintypes.DWORD()
overlapped_read_dir = OVERLAPPED()
ReadDirectoryChangesW(handle, ctypes.byref(event_buffer),
len(event_buffer), recursive,
WATCHDOG_FILE_NOTIFY_FLAGS,
ctypes.byref(nbytes),
ctypes.byref(overlapped_read_dir), call2pass)
调用有效,如果在程序执行期间我没有在受监视目录中执行更改,则它会在没有崩溃的情况下运行。 但是,如果发生更改,程序将崩溃:
hand = get_directory_handle(os.path.abspath("/test/"))
read_directory_changes(hand, False)
time.sleep(10)
崩溃报告
Nombre del evento de problema (Problem event name): APPCRASH
Nombre de la aplicación (Application Name): python.exe
Nombre del módulo con errores (Module name): ntdll.dll
Versión del módulo con errores (Module version): 6.1.7601.18247
Código de excepción (Exception code): c0000005
Desplazamiento de excepción (Exception offset): 000337a2
Versión del sistema operativo (OS Version): 6.1.7601.2.1.0.256.48
我认为问题出现在WinApi尝试调用回调代码时,我怀疑我没有正确构建传递给调用的WINFUNC
。
另一方面,我在python ctypes documentation site发现了以下警告:
注意确保只要保留对CFUNCTYPE()对象的引用 它们是从C代码中使用的。 ctypes没有,如果你不这样做,他们可能会 垃圾收集,在回调时崩溃您的程序。
但是我保留了对可调用语的全局引用,所以,除非我错了,否则我认为这不是问题。
你曾经调用kernel32
windows调用传递python中实现的回调吗?我试图做的正确吗?
修改
我只是想出了导致崩溃的原因:event_buffer
并非全局,而是在回调开始之前收集的。
虽然使全局修复崩溃,但消息"dir_change_callback!"
未在进程终端中打印。我试图从回调中创建一个新文件并向其写一些消息,但结果仍然相同:这就像回调没有被执行。这很奇怪,因为上面描述的崩溃应该与回调执行相关联。
ctypes
文档声明:
另外,请注意,如果在创建的线程中调用回调函数 在Python的控制范围之外(例如通过调用的外部代码) 回调),ctypes在每个上创建一个新的虚拟Python线程 调用。对于大多数用途,此行为是正确的,但这意味着 使用threading.local存储的值将无法生存 不同的回调,即使这些调用是从同一个C进行的 线程。
作为ctypes
的完全新手,我理解这个虚拟线程是执行系统调用的进程的线程,并且可能在执行调用之前终止此进程。
这个问题Using threading lock in ctypes callback function似乎支持这个想法。我试图在我的代码中重现那个问题锁定方案,但锁定仍然是解锁的,就像试图打印的版本一样:回调似乎没有执行:
lck = threading.Lock()
lck.acquire()
def dir_change_callback(dwErrorCode,dwNumberOfBytesTransfered,p):
lck.release()