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

VirtualAllocEx内存预留未返回基地址问题求助

桌面图标名称提取项目中VirtualAllocEx返回0的问题

我正在开发一个C#小项目,目标是把桌面图标的名称保存到文本文件里。入门时参考了两篇关于桌面图标操作的技术文章,目前已经通过FindWindow、FindWindowEx和SendMessage成功获取到桌面图标数量(实际是115个),也用GetWindowThreadProcessId拿到了线程进程ID,OpenProcess也返回了有效值。

但现在遇到的问题是VirtualAllocEx始终返回0。根据VirtualAllocEx的官方文档说明,函数成功时会返回分配内存区域的基地址,失败则返回NULL。调用GetLastError却没有返回任何错误码。

我了解到自Windows Vista起内存管理机制有变化,部分访问参数的使用不像以前那么简单,会不会是这个原因导致的问题?


系统环境

  • Windows 10 22H2
  • Visual Studio 17.11.4(C#工具4.11.0)
  • .NET 8.0

代码

using System.Runtime.InteropServices;

namespace DesktopIcon
{
    partial class DesktopIcon
    {
        internal const int LVM_GETITEMCOUNT = 0x00001000 + 4;
        internal const int LVIF_TEXT = 0x00000001;
        internal const int PROCESS_VM_OPERATION = 0x00000008;
        internal const int PROCESS_VM_READ = 0x00000010;
        internal const int PROCESS_VM_WRITE = 0x00000020;
        internal const int MEM_RESERVE = 0x00002000;
        internal const int MEM_COMMIT = 0x00001000;
        internal const int PAGE_READWRITE = 0x00001000;

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern uint GetLastError();

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        static extern void SetLastError(uint dwErrorCode);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.DLL", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern int GetWindowThreadProcessId(IntPtr hWnd, out uint ProcessId);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, uint processId);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        static void Main(string[] args)
        {
            GetDesktopIcons();
        }

        public static void GetDesktopIcons()
        {
            // Reset last error
            SetLastError(0);

            // This buffer size will be enough.
            const int BUFFER_SIZE = 0x110;
            uint vProcessId = 0;

            try
            {
                // Find window handles via process names.
                IntPtr vHandle = FindWindow("Progman", "Program Manager");
                vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SHELLDLL_DefView", null);
                vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SysListView32", "FolderView");
                Console.WriteLine($"vHandle: {vHandle}");

                // Count subwindows of desktop => count of icons.
                int vIconCount = SendMessage(vHandle, LVM_GETITEMCOUNT, 0, 0);
                Console.WriteLine($"Count desktop icons (vIconCount): {vIconCount}");

                // Get the desktop window's process to enumerate child windows.
                int threadProcessId = GetWindowThreadProcessId(vHandle, out vProcessId);
                Console.WriteLine($"ThreadProcessId: {threadProcessId}");

                IntPtr vProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, vProcessId);
                Console.WriteLine($"vProcess: {vProcess}");

                // Allocate memory in the desktop process.
                IntPtr vPointer = VirtualAllocEx(vProcess, IntPtr.Zero, BUFFER_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
                Console.WriteLine($"vPointer: {vPointer}");
            }
            catch (Exception)
            {
                throw;
            }
            Console.WriteLine($"GetLastError: {GetLastError()}");
        }
    }
}

解决建议

  • 权限问题:Windows Vista及之后的UAC机制限制了普通权限进程对系统进程(如桌面所属的explorer.exe)的内存操作。需确保程序以管理员权限运行,可右键程序选择“以管理员身份运行”测试。
  • OpenProcess权限补充:在OpenProcess的权限参数中添加PROCESS_QUERY_INFORMATION,部分场景下需要该权限才能正常完成内存分配。
  • 错误检查时机修正:不要统一在最后调用GetLastError,应在VirtualAllocEx调用后立即使用Marshal.GetLastWin32Error()获取错误码(.NET的P/Invoke可能会在中间调用其他API覆盖错误码),同时移除自定义的GetLastError导入,改用.NET内置的方法。
  • 内存大小类型显式转换:将BUFFER_SIZE显式转换为uint类型后传入VirtualAllocEx的dwSize参数,避免隐式转换可能的问题。
  • 进程权限验证:确认当前进程是否真的拥有对explorer.exe的内存操作权限,可通过GetLastWin32Error()获取准确错误码后对照官方文档排查。

内容的提问来源于stack exchange,提问作者user27741222

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 11:34:56