【问题标题】:Getting the text of a all windows desktop shortcuts获取所有 Windows 桌面快捷方式的文本
【发布时间】:2015-05-21 09:19:12
【问题描述】:

我正在尝试制作一个程序,它可以记住桌面图标的位置并将它们恢复到正确的位置。

现在我在使用这段代码时遇到了一些实际问题(我在这里得到了这段代码:https://social.msdn.microsoft.com/Forums/windows/en-US/d7df8a4d-fc0f-4b62-80c9-7768756456e6/how-can-i-get-desktops-icons-information-):

private void button1_Click(object sender, EventArgs e)
{
    // get the handle of the desktop listview
    IntPtr vHandle = FindWindow("Progman", "Program Manager");
    vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SHELLDLL_DefView", null);
    vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SysListView32", "FolderView");

    // get total count of the icons on the desktop
    int vItemCount = SendMessage(vHandle, LVM_GETITEMCOUNT, 0, 0);
    this.label1.Text = vItemCount.ToString();

    uint vProcessId;
    GetWindowThreadProcessId(vHandle, out vProcessId);

    IntPtr vProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, vProcessId);
    IntPtr vPointer = VirtualAllocEx(vProcess, IntPtr.Zero, 4096,
        MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    try
    {
        for (int j = 0; j < vItemCount; j++)
        {
            byte[] vBuffer = new byte[256];
            LVITEM[] vItem = new LVITEM[1];

            vItem[0].mask = LVIF_TEXT;
            vItem[0].iItem = j;
            vItem[0].iSubItem = 0;
            vItem[0].cchTextMax = vBuffer.Length;
            vItem[0].pszText = (IntPtr)((int)vPointer + Marshal.SizeOf(typeof(LVITEM)));

            uint vNumberOfBytesRead = 0; 

            WriteProcessMemory(vProcess, vPointer,
                Marshal.UnsafeAddrOfPinnedArrayElement(vItem, 0),
                Marshal.SizeOf(typeof(LVITEM)), ref vNumberOfBytesRead);

            SendMessage(vHandle, LVM_GETITEMW, j, vPointer.ToInt32());
            ReadProcessMemory(vProcess,
                (IntPtr)((int)vPointer + Marshal.SizeOf(typeof(LVITEM))),
                Marshal.UnsafeAddrOfPinnedArrayElement(vBuffer, 0),
                vBuffer.Length, ref vNumberOfBytesRead);

            string vText = Encoding.Unicode.GetString(vBuffer, 0, (int)vNumberOfBytesRead);
            string IconName = vText;

            // get icon location
            SendMessage(vHandle, LVM_GETITEMPOSITION, j, vPointer.ToInt32());
            Point[] vPoint = new Point[1];

            ReadProcessMemory(vProcess, vPointer, Marshal.UnsafeAddrOfPinnedArrayElement(vPoint, 0),
                Marshal.SizeOf(typeof(Point)), ref vNumberOfBytesRead);

            string IconLocation = vPoint[0].ToString();

            // insert an item into the ListView
            this.listView1.Items.Add(new ListViewItem(new
              string[]{IconName,IconLocation}));
        }
    }
    finally
    {
        VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE);
        CloseHandle(vProcess);
    }

    this.listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
}

这段C#代码应该可以获取所有桌面图标的文字和药水。问题是我无法使用此代码获得正确的文本。在此处检索桌面图标的文本:

string vText = Encoding.Unicode.GetString(vBuffer, 0, (int)vNumberOfBytesRead);
string IconName = vText;

不知何故,此处未正确检索文本。 vBuffer 在它的所有 256 个字节中都没有任何值。

所以现在我想出了一个使用指针的解决方案,并像这样重写了上面的代码:

private void button1_Click(object sender, EventArgs e)
{
    // get the handle of the desktop listview
    IntPtr vHandle = FindWindow("Progman", "Program Manager");
    vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SHELLDLL_DefView", null);
    vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SysListView32", "FolderView");

    // get total count of the icons on the desktop
    int vItemCount = SendMessage(vHandle, LVM_GETITEMCOUNT, 0, 0);
    this.label1.Text = vItemCount.ToString();

    uint vProcessId;
    GetWindowThreadProcessId(vHandle, out vProcessId);

    IntPtr vProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, vProcessId);
    IntPtr vPointer = VirtualAllocEx(vProcess, IntPtr.Zero, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    try
    {
        for (int j = 0; j < vItemCount; j++)
        {
            byte[] vBuffer = new byte[256];
            LVITEM[] vItem = new LVITEM[1];

            vItem[0].mask = LVIF_TEXT;
            vItem[0].iItem = j;
            vItem[0].iSubItem = 0;
            vItem[0].cchTextMax = vBuffer.Length;
            vItem[0].pszText = (IntPtr)((int)vPointer + Marshal.SizeOf(typeof(LVITEM)));

            uint vNumberOfBytesRead = 0;

            WriteProcessMemory(vProcess, vPointer,
                Marshal.UnsafeAddrOfPinnedArrayElement(vItem, 0),
                Marshal.SizeOf(typeof(LVITEM)), ref vNumberOfBytesRead);
            SendMessage(vHandle, LVM_GETITEMW, j, vPointer.ToInt32());

            unsafe
            {
                // IntPtr baseaddress = n;
                int nsize = (int)((LVITEM*)vPointer)->cchTextMax;
                IntPtr baseaddress = (IntPtr)((LVITEM*)vPointer)->pszText;

                ReadProcessMemory(vProcess, baseaddress, Marshal.UnsafeAddrOfPinnedArrayElement(vBuffer, 0), nsize, ref vNumberOfBytesRead);
            }

            string vText = Encoding.Unicode.GetString(vBuffer, 0, (int)vNumberOfBytesRead);
            string IconName = vText;

            // get icon location
            SendMessage(vHandle, LVM_GETITEMPOSITION, j, vPointer.ToInt32());
            Point[] vPoint = new Point[1];

            ReadProcessMemory(vProcess, vPointer,
                Marshal.UnsafeAddrOfPinnedArrayElement(vPoint, 0),
                Marshal.SizeOf(typeof(Point)), ref vNumberOfBytesRead);

            string IconLocation = vPoint[0].ToString();

            // insert an item into the ListView
            this.listView1.Items.Add(new ListViewItem(new string[]{IconName,IconLocation}));
        }
    }
    finally
    {
        VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE);
        CloseHandle(vProcess);
    }
}

现在的问题是,当我运行我的代码时,出现以下错误:

“试图读取或写入受保护的内存。这通常表明其他内存已损坏”

所以这似乎也不起作用。有人知道我如何成功获取每个桌面项目的文本吗? 而且我以前从未使用过指针,所以我想我可能做错了什么。有人看到上面的代码出了什么问题吗?

【问题讨论】:

  • 请记住,由于桌面实际上是多个目录的组合,因此桌面上可以有多个名称完全相同的项目。
  • 是的,我牢记这一点,但上面的代码实际上检索了公共用户地图和私人用户地图中的所有桌面图标,因此不存在问题。不过谢谢你的提及。
  • 是的,但我想说的是,如果您仅根据图标名称记住图标的放置位置,那么如果有两个同名图标,您肯定会遇到问题吗?
  • 是的,我知道,但我在代码的另一部分处理了这个问题。
  • 有人知道如何解决这个问题吗?

标签: c# windows desktop shortcut


【解决方案1】:

我知道这是一个老问题,但由于人们仍然会找到这个问题,而且我在其他地方几乎找不到任何确凿的信息,我想提供我想出的答案。

原始代码中缺少的元素是LVITEM.pszText 是一个指向文本的指针,但指针地址处的文本不是从虚拟缓冲区中获取的。

在下面的代码中,我尝试不使用不安全的代码。您将通过您选择的搜索引擎找到我作为 DLLImports 封装在 NativeMethods 类中的所有方法。使用的LVITEMA 结构可以在at the Win32 api documentation 找到并在下面实现。


public void GetDesktopIcons()
{
    // This buffer size will be enough.
    const int BUFFER_SIZE = 0x110;

    // 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");

    // TODO: Do error handling.
    if (vHandle == IntPtr.Zero) {
        return;
    }

    // Count subwindows of desktop => count of icons.
    int vIconCount = (int) NativeMethods.SendMessage(desktopWindowHandle, NativeMethods.LVM.GETITEMCOUNT, IntPtr.Zero, IntPtr.Zero);

    // Get the desktop window's process to enumerate child windows.
    NativeMethods.GetWindowThreadProcessId(desktopWindowHandle, out uint vProcessId);
    IntPtr vProcess = NativeMethods.OpenProcess(NativeMethods.PROCESS_VM.OPERATION | NativeMethods.PROCESS_VM.READ | NativeMethods.PROCESS_VM.WRITE, false, vProcessId);

    // Allocate memory in the desktop process.
    IntPtr vPointer = VirtualAllocEx(vProcess, IntPtr.Zero, new UintPtr(BUFFER_SIZE), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    // Initialize loop variables.
    NativeMethods.LVITEMA currentDesktopIcon = new NativeMethods.LVITEMA();
    byte[] vBuffer = new byte[BUFFER_SIZE];
    uint bytesRead = 0;

    // Instantiate an item to write to the remote buffer and be filled out there.
    NativeMethods.LVITEMA[] remoteBufferDesktopIcon = new NativeMethods.LVITEMA[1];

    // Initialize basic structure.
    // We want to get the icon's text, so set the mask accordingly.
    remoteBufferDesktopIcon[0].mask = NativeMethods.LVIF.TEXT;

    // Set maximum text length to buffer length minus offset used in pszText.
    remoteBufferDesktopIcon[0].cchTextMax = vBuffer.Length - Marshal.SizeOf(typeof(NativeMethods.LVITEMA));

    // Set pszText at point after this structure in the remote process's buffer.
    remoteBufferDesktopIcon[0].pszText = (IntPtr) ((int) bufferPointer + Marshal.SizeOf(typeof(NativeMethods.LVITEMA)));

    try
    {
        // Loop through available desktop icons.
        for (int i = 0; i < iconCount; i++)
        {
            remoteBufferDesktopIcon[0].iItem = i;

            // Write to desktop process the structure we want to get.
            NativeMethods.WriteProcessMemory(desktopProcessHandle, bufferPointer, Marshal.UnsafeAddrOfPinnedArrayElement(remoteBufferDesktopIcon, 0), new UIntPtr((uint) Marshal.SizeOf(typeof(NativeMethods.LVITEMA))), ref bytesRead);

            // Get i-th item of desktop and read its memory.
            NativeMethods.SendMessage(desktopWindowHandle, NativeMethods.LVM.GETITEMW, new IntPtr(i), bufferPointer);
            NativeMethods.ReadProcessMemory(desktopProcessHandle, bufferPointer, Marshal.UnsafeAddrOfPinnedArrayElement(vBuffer, 0), new UIntPtr((uint) Marshal.SizeOf(currentDesktopIcon)), ref bytesRead);

            // TODO: Error handling. This error is really unlikely.
            if (bytesRead != Marshal.SizeOf(currentDesktopIcon))
            {
                throw new Exception("Read false amount of bytes.");
            }

            // Get actual struct filled from buffer.
            currentDesktopIcon = Marshal.PtrToStructure<NativeMethods.LVITEMA>(Marshal.UnsafeAddrOfPinnedArrayElement(vBuffer, 0));

            // Use the now set pszText pointer to read the icon text into the buffer. Maximum length is 260, more characters won't be displayed.
            NativeMethods.ReadProcessMemory(desktopProcessHandle, currentDesktopIcon.pszText, Marshal.UnsafeAddrOfPinnedArrayElement(vBuffer, 0), new UIntPtr(260), ref bytesRead);

            // Read from buffer into string with unicode encoding, then trim string.
            currentIconTitle = Encoding.Unicode.GetString(vBuffer, 0, (int) bytesRead);
            currentIconTitle = currentIconTitle.Substring(0, currentIconTitle.IndexOf('\0'));

            // TODO: Do something with the icon title.
        }
    }
    finally
    {
        // Clean up unmanaged memory.
        NativeMethods.VirtualFreeEx(vProcess, bufferPointer, UIntPtr.Zero, NativeMethods.MEM.RELEASE);
        NativeMethods.CloseHandle(vProcess);
    }

这是我在 NativeMethods 中使用的 LVITEMA 结构:

[StructLayout(LayoutKind.Sequential)]
internal struct LVITEMA
{
    public int mask;
    public int iItem;
    public int iSubItem;
    public int state;
    public int stateMask;
    public IntPtr pszText;
    public int cchTextMax;
    public int iImage;
    public IntPtr lParam;
    public int iIndent;
    public uint iGroupId;
    public UIntPtr puColumns;
}

【讨论】:

  • 使用记录在案的 IFolderView::GetItemPosition 而不是依赖未记录的实现细节(可能会改变)。
  • 据我了解,问题不在于查找项目的位置(我的解决方案没有明确地这样做),而是检索它们的文本。现在还有其他解决方案,但根据具体情况,它们都有各自的缺点。
  • 第一句:“我正在尝试制作一个程序,它可以记住桌面图标的位置并将它们恢复到正确的位置。”该代码尝试获取每个项目的文本和位置,大概是为了以后可以使用该文本来识别相同的项目。这是一个XY问题。尽管如此,如果你想要文本,你可以调用 GetDisplayName 来获取它。
  • @RaymondChen 嗨,Raymond,我试图让你建议的方法在 C# 中工作,但还没有成功。你能给我一些关于 C# 文档的指示吗?我找到了您的一些博客文章,但它们都是针对 C++ 的,而且很难翻译成 C#。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-01
  • 1970-01-01
  • 2013-03-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多