为什么在 kernel32.TerminateProcess 的第一个参数的进程句柄对象上使用整数转换?

Why use integer casting on process handle object for the 1st param of kernel32.TerminateProcess?

好的,我正在使用 windows 系统调用的 python 绑定来杀死 windows 进程,我正在使用两种方法,但它们的做法不同ly,一些细节让我感到困惑。

第一种方法是使用 win32api

import win32api,subprocess
proc = subprocess.Popen([ 'notepad' ])
win32api.TerminateProcess(proc._handle, -1)

第二种方法是使用 ctypes

import ctypes, subprocess
proc = subprocess.Popen([ 'notepad' ])
ctypes.windll.kernel32.TerminateProcess( proc._handle, -1 )

在第二种方法中,它不会工作,除非你像这样对 proc._handle 进行整数转换:

proc = subprocess.Popen([ 'notepad' ])
int_handle = int(proc._handle)
ctypes.windll.kernel32.TerminateProcess(int_handle, -1 )

this int_handle,大概是某种进程句柄号,在我的机器上通常值在 1-1000 之间。 通过使用这些内置函数检查 proc._handle 对象,我没有得到任何非常有用的信息:

print "dir() of handle >>",dir(proc._handle)    # ['Close', 'Detach']
print "type() of handle >>",type(proc._handle)  # <type '_subprocess_handle'>

Sysinternals handle.exe 也没有提供太多信息。

notepad.exe pid: 8688 HOST\user
4: Event         
8: WaitCompletionPacket 
C: IoCompletion  
10: TpWorkerFactory 
14: IRTimer       
   ...

我的问题:

  1. 这个整数存储在 int_handle 中到底是什么?
  2. 为什么对proc._handle对象进行整型转换可以得到句柄值?
  3. 以及为什么将此数字传递给 kernel32.TerminateProcess?

请参阅 Handles and Objects for an overview of the various types of objects in the system and how handles are used to reference them. Kernel objects such as Process objects exist in kernel address space. User-mode programs cannot directly reference kernel memory. Instead each process has a kernel-object handle table that maps handle values such as 4, 8, 12 and so on to kernel objects. For example, when you call CreateProcess, the call returns handles for the Process and its primary Thread. You can also call OpenProcess or OpenThread to open a handle for an existing process or thread by ID. Call CloseHandle 上的 MSDN 主题以关闭不再需要的句柄。

(内核 File 对象,例如调用 CreateFile,也通过句柄引用。但是,CPython 使用 C 运行时的低 I/O 文件描述符相反,例如 0、1、2 等等,即 Unix/POSIX 文件描述符。在 Unix 上,一切都是文件,而在 Windows 上,一切都是对象。C 运行时维护文件的私有映射Windows File 句柄的描述符。其他对象类型未映射到 Unix 文件描述符。使用低 I/O 文件描述符和 C 标准 I/O 可以更简单地支持 Unix和 Windows。也就是说,至少有一个提议的补丁,其雄心勃勃的目标是重写 Python 3 对 Windows 的 I/O 支持,以使用 Windows API 和 File 直接处理。)


在 Python 2 中,子进程在 Windows 上使用 _subprocess_handle 类型来在句柄不再被引用时自动关闭它。此类型定义了一个 __int__ 方法来支持将句柄值转换为整数,但它不是 int 的子类,因此它本身不是整数。对于 ctypes 的特定问题,默认参数转换仅将整数参数转换为 C int 值,这就是为什么您必须使用 int(proc._handle).

但是,使用默认的整数转换是错误的。您应该定义 TerminateProcess.argtypes = (ctypes.c_void_p, ctypes.c_uint)。虽然真正的内核句柄总是在 32 位范围内,即使在 64 位系统上,假句柄如 (HANDLE)-1(用于引用当前进程而不必打开真正的句柄)确实使用全指针范围。因此在 64 位系统上,真实内核句柄的上 DWORD 应该为零,而对于 (HANDLE)-1 等伪句柄,必须保留非零上 DWORD。在 2.x 中,ctypes 不会将参数的堆栈内存归零,并且使用默认的 C int 类型只会写入堆栈 QWORD 的较低 DWORD。为了可靠性,您必须手动将参数包装为指针或将所有 HANDLE 类型参数(包括 HMODULEHWND 等)定义为指针类型,例如 ctypes.c_void_pwintypes.HANDLE.

使用ctypes.windll也是有问题的,因为它缓存了DLL,缓存了函数指针。这会使您的脚本或模块受到全局状态的支配,并可能导致冲突的 argtypesrestypeerrcheck 定义。它还不允许为最后一个错误值启用每线程保护存储。这是一个更可靠的示例 ctypes 定义:

import ctypes
from ctypes import wintypes

kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
kernel32.TerminateProcess.argtypes = (wintypes.HANDLE, wintypes.UINT)

if __name__ == '__main__':
    import subprocess

    p = subprocess.Popen('notepad')       
    if not kernel32.TerminateProcess(int(p._handle), 1):
        raise ctypes.WinError(ctypes.get_last_error())