You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#调用NtQueryInformationProcess返回STATUS_ACCESS_VIOLATION错误

问题根因

你的代码触发STATUS_ACCESS_VIOLATION是多个内存相关错误共同导致的:

  • P/Invoke签名完全错误:NtQueryInformationProcess的第三个参数是你分配的缓冲区的起始地址,应直接传IntPtr类型,不需要加ref。你声明为ref IntPtr时,实际传入内核的是栈上存储ptrHandleData局部变量的内存地址(64位下仅8字节),而非你在堆上分配的缓冲区地址。内核写入句柄快照数据时会直接覆盖栈内存,超出栈空间范围的写入直接触发访问违例。
  • 结构体定义存在多处错误:
    1. PROCESS_HANDLE_INFORMATION中GrantedAccess、ObjectTypeIndex、HandleAttributes、Reserved四个字段均为32位无符号整数,你全部定义为64位ulong,导致结构体整体尺寸、字段内存偏移完全错误;
    2. PROCESS_HANDLE_SNAPSHOT_INFORMATION的NumberOfHandles、Reserved字段是和指针等宽的ULONG_PTR类型(32位下4字节、64位下8字节),你硬编码为ulong会导致32位程序下布局错误;且Handles不是指向句柄数组的指针,是结构体尾部内嵌的变长数组起始位置,定义为IntPtr会完全破坏内存解析逻辑;
    3. 你给两个结构体加了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版本的结构体布局可能存在调整,生产环境使用建议优先采用公开APINtQuerySystemInformation的SystemHandleInformation类查询句柄信息,兼容性更好。
  • 代码中通过Marshal.SizeOf自动计算结构体尺寸,可自适应32/64位编译目标,不需要硬编码对齐参数。

内容的提问来源于stack exchange,提问作者Kurosh D.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 23:36:08