C#调用NtQueryInformationProcess返回STATUS_ACCESS_VIOLATION错误
问题根因
你的代码触发STATUS_ACCESS_VIOLATION是多个内存相关错误共同导致的:
- P/Invoke签名完全错误:
NtQueryInformationProcess的第三个参数是你分配的缓冲区的起始地址,应直接传IntPtr类型,不需要加ref。你声明为ref IntPtr时,实际传入内核的是栈上存储ptrHandleData局部变量的内存地址(64位下仅8字节),而非你在堆上分配的缓冲区地址。内核写入句柄快照数据时会直接覆盖栈内存,超出栈空间范围的写入直接触发访问违例。 - 结构体定义存在多处错误:
PROCESS_HANDLE_INFORMATION中GrantedAccess、ObjectTypeIndex、HandleAttributes、Reserved四个字段均为32位无符号整数,你全部定义为64位ulong,导致结构体整体尺寸、字段内存偏移完全错误;PROCESS_HANDLE_SNAPSHOT_INFORMATION的NumberOfHandles、Reserved字段是和指针等宽的ULONG_PTR类型(32位下4字节、64位下8字节),你硬编码为ulong会导致32位程序下布局错误;且Handles不是指向句柄数组的指针,是结构体尾部内嵌的变长数组起始位置,定义为IntPtr会完全破坏内存解析逻辑;- 你给两个结构体加了
Pack=1强制1字节对齐,而系统原生结构体按平台默认自然对齐,64位下会进一步加剧字段错位问题。
- 冗余无效逻辑:首次调用接口探测长度时
ptrHandleData为IntPtr.Zero,直接调用Marshal.FreeHGlobal属于无意义操作;额外调用RtlZeroMemory清零缓冲区也无必要,内核API写入返回数据时会直接覆盖对应内存区域。
修复后代码
using System; using System.Runtime.InteropServices; public class Main { // 32/64位自适应的PROCESS_HANDLE_INFORMATION [StructLayout(LayoutKind.Sequential)] public struct PROCESS_HANDLE_INFORMATION { public IntPtr Handle; public IntPtr HandleCount; public IntPtr PointerCount; public uint GrantedAccess; public uint ObjectTypeIndex; public uint HandleAttributes; public uint Reserved; } // 仅定义快照头,尾部变长数组直接通过内存偏移读取 [StructLayout(LayoutKind.Sequential)] public struct PROCESS_HANDLE_SNAPSHOT_HEADER { public IntPtr NumberOfHandles; public IntPtr Reserved; } [DllImport("kernel32.dll")] public static extern IntPtr GetCurrentProcess(); // 修正P/Invoke签名:第三个参数直接传缓冲区指针,不需要ref [DllImport("ntdll.dll")] public static extern int NtQueryInformationProcess( IntPtr processHandle, uint processInformationClass, IntPtr processInformation, int processInformationLength, out int returnLength ); private const uint CNST_PROCESS_HANDLE_INFORMATION = 0x33; private const int STATUS_INFO_LENGTH_MISMATCH = -1073741820; // 0xC0000004 public static void DoThings() { int nLength = 0; IntPtr ptrHandleData = IntPtr.Zero; try { // 首次调用探测所需缓冲区大小 int status = NtQueryInformationProcess( GetCurrentProcess(), CNST_PROCESS_HANDLE_INFORMATION, IntPtr.Zero, 0, out nLength ); while (status == STATUS_INFO_LENGTH_MISMATCH) { // 释放之前分配的缓冲区(首次为Zero无需释放) if (ptrHandleData != IntPtr.Zero) { Marshal.FreeHGlobal(ptrHandleData); } // 分配探测到的长度的缓冲区 ptrHandleData = Marshal.AllocHGlobal(nLength); // 再次调用获取数据 status = NtQueryInformationProcess( GetCurrentProcess(), CNST_PROCESS_HANDLE_INFORMATION, ptrHandleData, nLength, out nLength ); } if (status >= 0) // NT_SUCCESS宏判断:返回值>=0即为调用成功 { // 解析快照头 var header = Marshal.PtrToStructure<PROCESS_HANDLE_SNAPSHOT_HEADER>(ptrHandleData); long handleCount = header.NumberOfHandles.ToInt64(); Console.WriteLine($"当前进程句柄总数:{handleCount}"); // 计算第一个句柄信息的内存偏移:跳过快照头大小 int headerSize = Marshal.SizeOf<PROCESS_HANDLE_SNAPSHOT_HEADER>(); int handleInfoSize = Marshal.SizeOf<PROCESS_HANDLE_INFORMATION>(); for (int i = 0; i < handleCount; i++) { IntPtr pItem = ptrHandleData + headerSize + i * handleInfoSize; var handleInfo = Marshal.PtrToStructure<PROCESS_HANDLE_INFORMATION>(pItem); // 按需处理每个句柄信息,比如打印句柄值、访问权限 Console.WriteLine($"句柄:0x{handleInfo.Handle.ToInt64():X}, 访问权限:0x{handleInfo.GrantedAccess:X}"); } } else { Console.WriteLine($"调用失败,NTSTATUS:0x{status:X}"); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { if (ptrHandleData != IntPtr.Zero) { Marshal.FreeHGlobal(ptrHandleData); } } } }
注意事项
- 该接口属于未公开的Native API,不同Windows版本的结构体布局可能存在调整,生产环境使用建议优先采用公开API
NtQuerySystemInformation的SystemHandleInformation类查询句柄信息,兼容性更好。 - 代码中通过
Marshal.SizeOf自动计算结构体尺寸,可自适应32/64位编译目标,不需要硬编码对齐参数。
内容的提问来源于stack exchange,提问作者Kurosh D.
相关产品推荐
相关产品推荐

