Search code examples
c++winapiinterprocesscommon-controls

Getting column names from SysHeader32 control in another process


I am making a program (program1) that will read the name of the columns of a header (SysHeader32) that is located in a report-style List (SysListView32) in another program (program2).

So basically I want my program to go into another program and read what the title name is for all the headers (SysHeader32) that I find. Since the program has a lot of different lists and headers for each list, I decided to use the EnumChildWindows function with an EnumChildProc callback function to look through all the handles of the child window. With those handles I use GetClassName() to see what the class name is and when I see it is a SysHeader32, I know I found a header that can contain various title names... but I have no idea what code I can use to get the text from these various titles, nor do I know how to identify each title...

Here is the code I have so far that will find the handle for each SysHeader32 header found:

BOOL CALLBACK EnumChildProc (HWND hWnd, LPARAM lParam)
{
  char myBuffer [100];//buffer that will get the class name
  GetClassName(hWnd, myBuffer, 100);
  string myString (myBuffer);//converting myBuffer into a readable string

  if (myString == "SysHeader32")
  {
    ///here is where I am currently lost
    ///I just don't know how to get the text from the different titles/items
    ///in the header found
  }
}

Question 1:: How do I check how many different titles / items there are in the header?

Question 2:: How do I get the text for each of the titles / items found in the header?

Please provide some sample code.


Solution

  • Unfortunately, this is not easily possible when accessing a window created by another program, because the system doesn't do the necessary window message marshalling of pointers. You would need to do this from a shared DLL file (create some system-wide Windows Hook in it to load it into other processes) or using other hacks like inter-process memory access.


    If accessing a SysHeader32 window in the same program, it would be simple as this:

    1. Send message HDM_GETITEMCOUNT, it returns the number of items.

    2. Send message HDM_GETITEM with wParam set to the index of the item to retrieve and lParam set to a pointer to appropriately set HDITEM structure. Particularly set mask to HDI_TEXT and prepare a buffer for pszText and set its length in to cchTextMax.

    Example:

    int count = SendMessage(hWnd, HDM_GETITEMCOUNT, 0, 0);
    std::cout << "There are " << count << " items.\n";
    
    for (int i = 0; i < count; i++) {
      TCHAR name[260];
      HDITEM hdi;
      hdi.mask = HDI_TEXT;
      hdi.pszText = name;
      hdi.cchTextMax = 260;
      SendMessage(hWnd, HDM_GETITEM, i, reinterpret_cast<LPARAM>(&hdi));
      std::cout << "  " << i << ") " << hdi.pszText << "\n";
    } 
    

    Because we need to store the input and output memory in another program's space, something like this is necessary (please add error checking etc. according to your liking):

    struct InterProcessData {
      HDITEM hdi;
      TCHAR buffer[260];
    };
    
    // Open the owning process and allocate a buffer big enough for
    // our inter-process communication
    DWORD dwProcessId;
    GetWindowThreadProcessId(hWnd, &dwProcessId);
    HANDLE hProcess = OpenProcess(
      PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
      FALSE, dwProcessId);
    InterProcessData* pRemoteData = reinterpret_cast<InterProcessData*>(
      VirtualAllocEx(hProcess, NULL, sizeof(InterProcessData), MEM_COMMIT, PAGE_READWRITE));
    
    int count = SendMessage(hWnd, HDM_GETITEMCOUNT, 0, 0);
    std::cout << "There are " << count << " items.\n";
    
    for (int i = 0; i < count; i++) {
      InterProcessData data;
      data.hdi.mask = HDI_TEXT;
      data.hdi.pszText = pRemoteData->buffer;
      data.hdi.cchTextMax = 260;
    
      // Write the HDITEM structure to the space in the remote process
      // (without the buffer, its contents are undefined anyway)
      WriteProcessMemory(hProcess, pRemoteData, &data, sizeof(data.hdi), NULL);
    
      // Send the message itself, passing the remote address in lParam
      SendMessage(hWnd, HDM_GETITEM, i, reinterpret_cast<LPARAM>(&pRemoteData->hdi));
    
      // Read the data back, HDITEM and the buffer
      ReadProcessMemory(hProcess, pRemoteData, &data, sizeof(data), NULL);
    
      // The documentation says that the pszText can point elsewhere -
      // copy it to our buffer in that case
      if (data.hdi.pszText != pRemoteData->buffer)
        ReadProcessMemory(hProcess, data.hdi.pszText, data.buffer, data.hdi.cchTextMax * sizeof(TCHAR), NULL);
    
      std::cout << "  " << i << ") " << data.buffer << "\n";
    }
    
    // Cleanup
    VirtualFreeEx(hProcess, pRemoteData, 0, MEM_RELEASE);
    CloseHandle(hProcess);