Search code examples
c#interoptooltipintptr

Kernel32 VirtualAllocEx returning IntPtr.Zero Intermittently


We are trying to read the ToolTips from system tray icons and the code is working but is returning zero intermittently for the the method below calling Kernel32.VirtualAllocEx

    IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
    hProcess,
    IntPtr.Zero,
    new UIntPtr(BUFFER_SIZE),
    MemAllocationType.COMMIT,
    MemoryProtection.PAGE_READWRITE);

  if (ipRemoteBuffer == IntPtr.Zero)
    return String.Empty;

It seems to work absolutely fine then suddenly stops working and returns IntPtr.Zero consistently. When checking Marshal.GetLastWin32Error() it returns 8 (not enough memory). Below is the full code:

public static string GetTooltip(string search)
{
  IntPtr _ToolbarWindowHandle = GetSystemTrayHandle();

  UInt32 count = User32.SendMessage(_ToolbarWindowHandle, TB.BUTTONCOUNT, 0, 0);

  List<string> tooltips = new List<string>();

  for (int i = 0; i < count; i++)
  {
    TBBUTTON tbButton = new TBBUTTON();
    string text = String.Empty;
    IntPtr ipWindowHandle = IntPtr.Zero;

    text = GetTBButtonText(_ToolbarWindowHandle, i, ref tbButton, ref text, ref ipWindowHandle);

    if (!String.IsNullOrWhiteSpace(text) && text.ToLowerInvariant().Contains(search.ToLowerInvariant()))
      return text;
  }

  return String.Empty;
}

static unsafe string GetTBButtonText(IntPtr hToolbar, int i, ref TBBUTTON tbButton, ref string text, ref IntPtr ipWindowHandle)
{
  const int BUFFER_SIZE = 0x1000;

  byte[] localBuffer = new byte[BUFFER_SIZE];

  UInt32 processId = 0;
  UInt32 threadId = User32.GetWindowThreadProcessId(hToolbar, out processId);

  IntPtr hProcess = Kernel32.OpenProcess(ProcessRights.ALL_ACCESS, false, processId);
  if (hProcess == IntPtr.Zero)
    return String.Empty;

  IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
    hProcess,
    IntPtr.Zero,
    new UIntPtr(BUFFER_SIZE),
    MemAllocationType.COMMIT,
    MemoryProtection.PAGE_READWRITE);

  if (ipRemoteBuffer == IntPtr.Zero)
  {
    var error = Marshal.GetLastWin32Error();
    return String.Empty;
  }

  // TBButton
  fixed (TBBUTTON* pTBButton = &tbButton)
  {
    IntPtr ipTBButton = new IntPtr(pTBButton);

    int b = (int)User32.SendMessage(hToolbar, TB.GETBUTTON, (IntPtr)i, ipRemoteBuffer);
    if (b == 0)
      return String.Empty;

    // this is fixed
    Int32 dwBytesRead = 0;
    IntPtr ipBytesRead = new IntPtr(&dwBytesRead);

    bool b2 = Kernel32.ReadProcessMemory(
      hProcess,
      ipRemoteBuffer,
      ipTBButton,
      new UIntPtr((uint)sizeof(TBBUTTON)),
      ipBytesRead);

    if (!b2)
      return String.Empty;
  }

  // button text
  fixed (byte* pLocalBuffer = localBuffer)
  {
    IntPtr ipLocalBuffer = new IntPtr(pLocalBuffer);

    int chars = (int)User32.SendMessage(hToolbar, TB.GETBUTTONTEXTW, (IntPtr)tbButton.idCommand, ipRemoteBuffer);
    if (chars == -1) { Debug.Assert(false); return ""; }

    // this is fixed
    Int32 dwBytesRead = 0;
    IntPtr ipBytesRead = new IntPtr(&dwBytesRead);

    bool b4 = Kernel32.ReadProcessMemory(
      hProcess,
      ipRemoteBuffer,
      ipLocalBuffer,
      new UIntPtr(BUFFER_SIZE),
      ipBytesRead);

    if (!b4)
      return String.Empty;

    text = Marshal.PtrToStringUni(ipLocalBuffer, chars);

    return text;
  }
}

Solution

  • OK if I make a call to release the memory like so the problem is solved.

        const uint MEM_RELEASE = 0x8000;
    
        UIntPtr uintPtr = UIntPtr.Zero;
        var successfullyReleased = Kernel32.VirtualFreeEx(hProcess, ipRemoteBuffer, uintPtr, MEM_RELEASE);
        if (!successfullyReleased)
        {
    
        }