Listing Processes – Using Remote Desktop Services API

Using Remote Desktop Services API

We can call WTSEnumerateProcesses function to get information about the active processes on a specified Remote Desktop Session Host server. However, if pass WTS_CURRENT_SERVER_HANDLE in first argument (server handle), we can enumerate and get info about processes which are running on local machine.
The following example calls WTSEnumerateProcesses function, then fills an array of WTS_PROCESS_INFO structures.

// Windows Terminal Server API header & lib 
#include <wtsapi32.h&gt;
#pragma comment(lib, "Wtsapi32.lib")

DWORD RDSAPI_EnumProcesses(CArray<WTS_PROCESS_INFO>& arrProcInfo)
{
    // clear output array
    arrProcInfo.RemoveAll();

    // enumerate processes
    HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; // local machine
    PWTS_PROCESS_INFO pProcessInfo = NULL;
    DWORD dwCount = 0;
    if (!::WTSEnumerateProcesses(hServer, 0, 1, &pProcessInfo, &dwCount))
    {
        return ::GetLastError();
    }

    // fill output array
    arrProcInfo.SetSize(dwCount);
    for (DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++)
    {
        arrProcInfo[dwIndex] = pProcessInfo[dwIndex];
    }

    // free the memory allocated in WTSEnumerateProcesses
    ::WTSFreeMemory(pProcessInfo);
    return NO_ERROR;
}

If the target OS is Windows Vista or newer, we can alternatively use WTSEnumerateProcessesEx which can get additional info in WTS_PROCESS_INFO_EX structures.
Here is an example that enumerates processes, then fills an array of application-defined structures, (CProcessInfoEx) which contain the following info:

  • the process identifier;
  • the identifier of session associated with the process;
  • the name of the executable file associated with the process;
  • the user account name;
  • the user domain name;
  • the number of threads in the process;
  • the number of handles in the process;
  • the page file usage of the process, in bytes;
  • the peak page file usage of the process, in bytes;
  • the working set size of the process, in bytes;
  • the peak working set size of the process, in bytes;
  • the time, in milliseconds, the process has been running in user mode;
  • the time, in milliseconds, the process has been running in kernel mode.
DWORD RDSAPI_EnumProcessesEx(CArray<CProcessInfoEx>& arrProcInfo)
{
    // clear output array
    arrProcInfo.RemoveAll();

    // init WTSEnumerateProcessesEx parameters
    HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; // get local machine processes
    DWORD dwLevel = 1;                          // get array of WTS_PROCESS_INFO_EX
    DWORD dwSessionID = WTS_ANY_SESSION;        // get processes in all sessions
    DWORD/ dwCount = 0;                         // returns the number of processes
    PWTS_PROCESS_INFO_EX  pProcessInfo = NULL;  // output data

    if (!::WTSEnumerateProcessesEx(hServer, &dwLevel, dwSessionID, (LPTSTR*)&pProcessInfo, &dwCount))
    {
        return ::GetLastError();
    }

    // fill output array
    arrProcInfo.SetSize(dwCount);
    for (DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++)
    {
        CProcessInfoEx processInfoEx(pProcessInfo[dwIndex]); 
        arrProcInfo[dwIndex] = processInfoEx;
    }

    // free the memory allocated in WTSEnumerateProcessesEx
    if (!::WTSFreeMemoryEx(WTSTypeProcessInfoLevel1, pProcessInfo, dwCount))
    {
        return ::GetLastError();
    }
    return NO_ERROR;
}

Now, let’s fill a listview control with the processes info grabbed by RDSAPI_EnumProcessesEx.

void CDemoDlg::_FillProcessesList()
{
    // clear listview control
    m_listProcesses.DeleteAllItems();

    // get an array of CProcessInfoEx
    CArray<CProcessInfoEx> arrProcInfo;
    VERIFY(NO_ERROR == RDSAPI_EnumProcessesEx(arrProcInfo));

    // fill the listview control
    const INT_PTR nSize = arrProcInfo.GetSize();
    for (INT_PTR nItem = 0; nItem < nSize; nItem++)
    {
        // add new item to listview control
        m_listProcesses.InsertItem(nItem, NULL);

        // set list imtem text
        const CProcessInfoEx& processInfoEx = arrProcInfo.ElementAt(nItem);
        m_listProcesses.SetItemText(nItem, ePID, processInfoEx.GetProcessIdStr());
        m_listProcesses.SetItemText(nItem, eSessionID, processInfoEx.GetSessionIdStr());
        m_listProcesses.SetItemText(nItem, eImageName, processInfoEx.GetProcessNameStr());
        m_listProcesses.SetItemText(nItem, eUserName, processInfoEx.GetUserNameStr());
        m_listProcesses.SetItemText(nItem, eDomain, processInfoEx.GetDomainNameStr());
        m_listProcesses.SetItemText(nItem, eThreads, processInfoEx.GetNumberOfThreadsStr());
        m_listProcesses.SetItemText(nItem, eHandles, processInfoEx.GetHandleCountStr());
        m_listProcesses.SetItemText(nItem, ePagefileUsage, processInfoEx.GetPagefileUsageStr());
        m_listProcesses.SetItemText(nItem, ePeakPagefileUsage, processInfoEx.GetPeakPagefileUsageStr());
        m_listProcesses.SetItemText(nItem, eWorkingSetSize, processInfoEx.GetWorkingSetSizeStr());
        m_listProcesses.SetItemText(nItem, ePeakWorkingSetSize, processInfoEx.GetPeakWorkingSetSizeStr());
        m_listProcesses.SetItemText(nItem, eUserTime, processInfoEx.GetUserTimeStr());
        m_listProcesses.SetItemText(nItem, eKernelTime, processInfoEx.GetKernelTimeStr());
    }
}

More implementation details can be found in the attached demo project.

Notes

  • WTSEnumerateProcesses function requires at least Windows XP or Windows Server 2003;
  • WTSEnumerateProcessesEx function requires at least Windows 7 or Windows Server 2008 R2;
  • WTS prefix comes from Windows Terminal Services which is the former name of Remote Desktop Services.

Demo Project

The demo project is a simple MFC dialog-based application that uses the above functions.

Download: Listing_Processes_Using_RDS_API.zip (23 KB)

Using Remote Desktop Services API - Demo Application

References


Fill a List with Large Amount of Data – Part 3

The previous article demonstrates how to improve performance by using a virtual listview control (having LVS_OWNERDATA style set) and handling LVN_GETDISPINFO notification. That’s pretty cool! However, it still can be improved by caching input data. That means, instead of first loading the whole input data, we can load only how much is necessary at a time.
For that purpose, before sending LVN_GETDISPINFO, a virtual list control sends LVN_ODCACHEHINT, giving a “hint” of what range of items should be loaded. An application can verify if these items are already loaded into cache. If not, then can load them.
Let’s see a simple example!

Using a cached list

  1. Set LVS_OWNERDATA style.
    BOOL CCachedListView::PreCreateWindow(CREATESTRUCT& cs)
    {
        cs.style &= ~LVS_TYPEMASK; // clear old type
        cs.style |= LVS_REPORT;    // make it report type
        cs.style |= LVS_OWNERDATA; // make it virtual list
    
        return CListView::PreCreateWindow(cs);
    }
  2. Set the virtual list items count with CListCtrl::SetItemCountEx.
        // ...
        CListCtrl& listCtrl = GetListCtrl();
        listCtrl.SetItemCountEx(m_dwTotalItemsCount);
  3. Handle LVN_GETDISPINFO notification like in the previous example.
  4. Finally, handle LVN_ODCACHEHINT for loading cached data.
    // CachedListView.h
    
    class CCachedListView : public CListView
    {
        // ...
    
        afx_msg void OnLvnOdcachehint(NMHDR *pNMHDR, LRESULT *pResult);
    };
    // CachedListView.cpp
    
        // ...
        ON_NOTIFY_REFLECT(LVN_ODCACHEHINT, &CCachedListView::OnLvnOdcachehint)
    END_MESSAGE_MAP()
    
    void CCachedListView::OnLvnOdcachehint(NMHDR *pNMHDR, LRESULT *pResult)
    {
        // NOTE: NMLVCACHEHINT structure contains information 
        //       used to update the cached item for use with a virtual list view. 
        LPNMLVCACHEHINT pCacheHint = reinterpret_cast<LPNMLVCACHEHINT>(pNMHDR);
    
        const DWORD dwFromRow = pCacheHint->iFrom;        // starting index (not used in this example)
        const DWORD dwToRow = pCacheHint->iTo;            // ending index of the requested range of items 
        const DWORD dwFetchedRows = m_arrRows.GetSize();  // number of rows already fetched
    
        if(dwToRow >= dwFetchedRows) // new rows must be fetched, so let's do it!
        {
            const DWORD dwColCount = m_recordSet.GetColumnCount();
            m_arrRows.SetSize(dwToRow + 1);
            for(DWORD dwRow = dwFetchedRows; dwRow <= dwToRow; dwRow++)
            {
                 CDBRecord* pRecord = new CDBRecord;
                 pRecord->SetSize(dwColCount);
                 for(DWORD dwCol = 1; dwCol <= dwColCount; dwCol++)
                 {
                     CDBValue* pDBValue = new CDBValue(m_recordSet, dwCol);
                     pRecord->SetAt(dwCol - 1, pDBValue);
                 }
                 m_arrRows.SetAt(dwRow, pRecord);
                 m_recordSet.MoveNext(); // next row
            }
        }
        *pResult = 0;
    }

Notes

  • The caching mode presented here is as simple as posible. It presume the total number of items known. Also it adds data in cache each time is needed without care about memory. If necessary, that can be improved for handling an unspecified number of items and/or for keeping a maximum number of items in cache. That would be necessary if dealing with very large amounts of data.
  • See also the previous article notes.

Demo project


        NOTE: THE DEMO PROJECT IS OUTDATED AND NEEDS REVIEW!

The demo project uses the same database and performs the same query like the one presented in the previous article. Please, feel free to download and compare.
Download: [download id=”1809″]

Using Cached List - Demo Application
Using Cached List – Demo Application

Resources

See also

Fill a List with Large Amount of Data – Part 2

Inserting a large number of items in a listview control, may take a lot of time.
A virtual list does not internally keep items information (like text and so on). Instead, it notifies its parent, asking info only for the items/subitems which are currently displayed. That makes it a good choyce if dealing with a large number of items.

Using a virtual list

  1. Set LVS_OWNERDATA style.
    First note this style cannot be set after control creation. For a listview control contained in a dialog resource, it can be done by setting True for Owner Data property. For a listview control kept by a CListView class, the right place is in the overridden PreCreateWindow

     

    >BOOL CVirtualListView::PreCreateWindow(CREATESTRUCT& cs)
    {
        cs.style &= ~LVS_TYPEMASK; // clear old type
        cs.style |= LVS_REPORT;    // make it report type
        cs.style |= LVS_OWNERDATA; // make it virtual list
    
       return CListView::PreCreateWindow(cs);
    }
  2. Load data.
    May be, for example, an array of database records as a result of an SQL query. See the attached demo project for a concrete example.
  3. Set the virtual list items count.
    UINT CVirtualListView::FillList(Recordset& rs)
    {
        // ...
        // Load data in m_arrRows array instead of actually fill the list control
        // ...
        CListCtrl& listCtrl = GetListCtrl();     // get listview control
        const int nCount = m_arrRows.GetCount(); // get number of rows
        listCtrl.SetItemCountEx(nCount);         // set list items count
        return nCount;
    }
  4. Finally, handle the LVN_GETDISPINFO notification.
    As said in the beginning, a virtual list doesn’t internally keep items info. Instead, it sends LVN_GETDISPINFO notification via WM_NOTIFY message, each time it needs info about the items to be actually displayed.
    For a listview control we can use the wizard to map LVN_GETDISPINFO in the parent dialog class or the reflected =LVN_GETDISPINFO notification, in a class derived from CListCtrl.
    In a class derived from CListView we can also use the wizard to map reflected =LVN_GETDISPINFO notification. 

     

    // VirtualListView.h
    // ...
    class CVirtualListView : public CListView
    {
       // ...
        DECLARE_MESSAGE_MAP()
        afx_msg void OnLvnGetdispinfo(NMHDR *pNMHDR, LRESULT *pResult);
    };

    First argument is a pointer to a NMLVDISPINFO structure. From its member of type LVITEM, we can get which info is required (text, image, etc) and the index of item and subitem that is being displayed. Next, we have to fill that structure with the information from an external data collection.

    // VirtualListView.cpp
        // ...
        ON_NOTIFY_REFLECT(LVN_GETDISPINFO, &CVirtualListView::OnLvnGetdispinfo)
    END_MESSAGE_MAP()
    // ...
    void CVirtualListView::OnLvnGetdispinfo(NMHDR *pNMHDR, LRESULT *pResult)
    {
        NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
        LV_ITEM* pItem = &(pDispInfo)->item;
    
       if(pItem->mask & LVIF_TEXT)
       {
          // Set the text for the given item 
          // Use pItem->iItem, pItem->iSubItem, pItem->pszText, and pItem->cchTextMax.
          CDBRecord* pRow = m_arrRows.GetAt(pItem->iItem); // recordset row -> item index
          CDBValue* pValue = pRow->GetAt(pItem->iSubItem); // recordset column -> subitem index
          _tcscpy_s(pItem->pszText, pItem->cchTextMax, pValue->GetString());
       }
       if(pItem->mask & LVIF_IMAGE)
       {
          // ...set an index in an imagelist 
       }
       // etc.
       *pResult = 0;
    }

Demo project


        NOTE: THE DEMO PROJECT IS OUTDATED AND NEEDS REVIEW!

Download: [download id=”1807″]

The demo project uses the same SQL query to fill a non-virtual and a virtual listview. As can be seen in the screenshot, the virtual list worked over ten times faster.

Using Virtual List - Demo Application
Using Virtual List – Demo Application

Additional notes

  • The demo application uses an SQL Server Compact 4.0 database. Its runtime engine usually comes with Visual Studio 2012. Anyway, if it isn’t already installed on your machine, you can download it for free, from Microsoft Download Center:
    http://www.microsoft.com/en-us/download/details.aspx?id=17876
  • A future article will show how to improve the virtual listview control in order to deal with much larger amount of data, by caching.

Resources

See also

Tiles-View List Control

Tiles View in Windows File Explorer

Using Windows File Explorer we can notice, aside others, the Tile View mode.

Tiles View (Windows Explorer)

It looks like icon views but, beside a title (that shows the file name), it displays some additional info (file type and size).
This articles describe how to set Tiles View in our own listview (SysListView32) Windows control.

Set Tiles View in a list control

Let’s say, we’ve placed a list control in a dialog template. Possible, the first attempt is to search for a “Tile” value of the “View” property. No luck: we can see only “Icon”, “Small icon”, “List”, and “Report” (LVS_ICON, LVS_SMALLICON, LVS_LIST, and LVS_REPORT types).

However, there is a solution: send LVM_SETVIEW message or use ListView_SetView macro or (if using MFC) call CListCtrl::SetView.

Examples

HWND hWndList = ::GetDlgItem(hWndDlg, IDC_LISTVIEW_DEMO);
::SendMessage(hWndList, LVM_SETVIEW, (WPARAM)LV_VIEW_TILE, 0);
HWND hWndList = ::GetDlgItem(hWndDlg, IDC_LISTVIEW_DEMO);
ListView_SetView(hWndList, LV_VIEW_TILE);
// CDemoListView is derived from CListView MFC class
void CDemoListView::OnViewTile()
{
    // get the list control
    CListCtrl& listCtrl = GetListCtrl();
    // set Tile View mode
    listCtrl.SetView(LV_VIEW_TILE);
}

In the same way, we can switch to the other view types, by passing LV_VIEW_ICON, LV_VIEW_SMALLICON, LV_VIEW_LIST, or LV_VIEW_DETAILS.
That’s pretty easy. However there are a little bit more things to do.

Set information for the list-view control in Tile View

For this purpose we can send LVM_SETTILEVIEWINFO message or use ListView_SetTileViewInfo or call CListCtrl::SetTileViewInfo.

Example

// Set the maximum number of text lines in each item label, not including the title
BOOL CDemoListView::_SetTilesViewLinesCount(int nCount)
{
    CListCtrl& listCtrl = GetListCtrl();

     LVTILEVIEWINFO lvtvwi = {0};
     lvtvwi.cbSize = sizeof(LVTILEVIEWINFO);
     lvtvwi.dwMask = LVTVIM_COLUMNS;
     lvtvwi.cLines = nCount;

     return listCtrl.SetTileViewInfo(&lvtvwi);
}

Set information for each item in Tile View

This can be done when the item is inserted or later, by sending LVM_SETTILEINFO, using ListView_SetTileInfo or calling CListCtrl::SetTileInfo.

Example

// Displays columns 1, 3 and 5 for the item 2
void CDemoListView::_InitAndFillList()
{
   // ...
   UINT arrColumns[3] = {1, 3, 5};
   VERIFY(_SetItemTileLines(2, arrColumns, 3));
}

BOOL CDemoListView::_SetItemTileLines(int nItem, UINT* parrColumns, UINT nCount)
{
    CListCtrl& listCtrl = GetListCtrl();

    LVTILEINFO lvti = {0};
    lvti.cbSize = sizeof(LVTILEINFO);
    lvti.cColumns = nCount;
    lvti.iItem = nItem;
    lvti.puColumns = parrColumns;

    return listCtrl.SetTileInfo(&lvti);
}

Demo Application

Download: Tiles-View-List-Control-Demo.zip (110 KB)

This is just a brief presentation. You can find more hints and details in the links below and in the demo project attached here.

Tiles View (Demo Application)

Resources


Color Picker Listview Control

Among other common Windows dialogs we can find Color Dialog Box that allows to pick a color for our purposes. It can simply be created by a call of ChooseColor WinAPI function or, if using MFC, with the help of CColorDialog class.
Also, the MFC versions shipped with Visual Studio 2008 or newer, include CMFCColorDialog class which shows a little bit more fancy color selection dialog.

However, let’s say we have to make our own custom color picker control (let’s say, looking similar tho one used in Photoshop). Beside making a custom or ActiveX control from scratch, a faster approach is to customize the Windows listview (“SysListView32“) control, kept in MFC by CListCtrl class. There are two ways to customize a listview control: making it custom-draw or making it owner-draw.

Custom Draw Color Picker Listview

Color Picker Listview (custom-draw)

Color Picker Listview (custom-draw)

First we have to handle NM_CUSTOMDRAW notification, sent via WM_NOTIFY.
In subitem pre-paint phase, we can indicate which color has to be used to paint whatever list subitem.
Example

void CColorListCtrl::_HandleSubitemPrePaint(LPNMLVCUSTOMDRAW pNMCD, LRESULT *pResult)
{
   // Note: for a listview, nmcd.dwItemSpec keeps the item index 
   const int nItem = (int)pNMCD->nmcd.dwItemSpec;
   const int nSubItem = pNMCD->iSubItem;
   const int nColor = nItem * m_nColCount + nSubItem;
   if(nColor < m_arrColors.GetSize())    
   {       
      pNMCD->clrTextBk = m_arrColors[nColor]; // m_arrColors is an array of COLORREFs
   }
   // ...
}

Finally, hadle NM_CLICK notification, get the color of the clicked item and send a user message to the parent.
Example

void CColorListCtrl::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult)
{
   LVHITTESTINFO lvhti = {0};
   lvhti.flags = LVHT_ONITEM;
   ::GetCursorPos(&lvhti.pt);
   ScreenToClient(&lvhti.pt);
   int nItem = this->SubItemHitTest(&lvhti);
   if(-1 != nItem)
   {
      const UINT nColor = lvhti.iItem * m_nColCount + lvhti.iSubItem;
      if(nColor < (UINT)m_arrColors.GetSize())       
      {          
         // notify parent          
         CWnd* pWndParent = GetParent();
         if(NULL != pWndParent)
         {             
            pWndParent->SendMessage(CColorListCtrl::WM_COLORPICK, 
               (WPARAM)m_arrColors[nColor]);
         }
      }
   }
   *pResult = 0;
}

This method is pretty simple but it has a disadvantage: we cannot modify item/subitem height. So, if this is necessary we gave to make an owner-draw listview control.

Owner Draw Color Picker Listview

Color Picker Listview (owner-draw)

Color Picker Listview (owner-draw)

An owner-draw listview control must have LVS_OWNERDRAWFIXED style set (“Owner Draw Fixed” property must be set to “True” in the resource editor).
Next, we can handle WM_MEASUREITEM to set item size and override CListCtrl::DrawItem in order to perform custom drawing. Just to note: would be necessary to map by hand the WM_MEASUREITEM message; also DrawItem isn’t called for each subitem.
Example

void CColorListCtrl::MeasureItem(LPMEASUREITEMSTRUCT lpMIS)
{
   lpMIS->itemHeight = m_nColorItemHeight;
   lpMIS->itemWidth = m_nColorItemWidth * m_nColCount;
}
void CColorListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
   CDC* pDC = CDC::FromHandle(lpDIS->hDC);
   const UINT nColorCount = (UINT)m_arrColors.GetSize();

   for(UINT nColor = 0; nColor < nColorCount; nColor++)    
   {       
      const UINT nItem = nColor / m_nColCount;       
      if(nItem == lpDIS->itemID)
      {
         UINT nSubItem = nColor % m_nColCount;
         CRect rcColor(0, 0, 0, 0);
         rcColor.top = lpDIS->rcItem.top;
         rcColor.bottom = lpDIS->rcItem.bottom;
         rcColor.left = lpDIS->rcItem.left + nSubItem * m_nColorItemWidth;
         rcColor.right = rcColor.left + m_nColorItemWidth;
         pDC->FillSolidRect(rcColor, m_arrColors[nColor]);
      }
   }
}

Notes

  • Implementation details can be found in the attached demo projects
  • The color pickers presented here are just draft versions intended to demonstrate how to do. You can further improve them by adding new features like loading a custom color palette, adding custom colors on the fly, and so on.
  • A brief presentation of custom-draw and owner draw controls, can be found in this article: Custom Draw vs. Owner Draw Controls.

Downloads

  • Demo project: [download id=”1795″]
  • Demo project: [download id=”1797″]

Fill a List with Large Amount of Data – Part 1

In my opinion, making database queries in separate threads in order to avoid UI blocking is the last option a Windows developer should have. Of course, that’s an option like any others but, to avoid headaches of thread synchronization and so on, we have to consider first the following:

  • 1. Optimize the database, index fields if necessary.
  • 2. Choose the right database access technology.

Well, let’s say we are just “poor developers” and both database designer and project manager are idiots. Then, there’s no choice and we have to live with them without resolving #1 and #2. In most cases, UI is blocked as a result of populating a list control with a large number of rows, from a recordset. Have we to make this in a separate thread? Not really, wait a minute!

  • 3. Use a virtual list. A virtual list does not keep the UI busy until it’s filled with thousands, tens of thousands or even hundreds of thousands items. It just asks info about items which have to be currently displayed.
  • 4. Not enough? We have to deal with queries which return millions, tens of millions or even more results? No problem, can use a cache mechanism.

Both #3 and #4 are supported in the Windows common listview (SysListView32) control.