At the beginning of this year, Microsoft announced a “C++ renaissance”. Quoting from the description of a Channel 9 video with Craig Symonds and Mohsen Agsen:

C++ is currently undergoing a renaissance. This means that, by definition, the language, compilers and compositional tooling are evolving and coalescing into a state that maximizes native developer efficiency, productivity, and creativity across hardware and software domains.

Everybody agrees that Microsoft made C++ a sort of second class citizen in the past years, while the company invested a lot in the .NET framework. Many developers have switched from native development to managed (.NET) simply because it offers a more productive environment. And the postponing of the ISO standard committee in releasing the new C++0x standard only made things worse.

However, with the completion of the new C++ standard this year, Microsoft, apparently, plans to change that, and make C++ again appealing to developers. They already made C++0x features available in the VS2010 C++ compiler and are working on implementing most of the rest for Visual Studio vNext. They are also investing in tools (now labeled Application Lifecycle Management), and for instance are bringing intellisence to C++/CLI. One of the most important areas of development is parallelism, where they are developing the PPL and Agents libraries and now the C++ AMP that they just announced. And also recently the Kinect for Windows SDK beta that provides Kinect capabilities to developers who build applications with C++ (and other laguanges). And in the mean time they hired Erich Gamma in the Visual Studio team.

But this is not enough in my opinion. Improvements in language and tools are an important part, but not everything. It is equally necessary for Microsoft to evangelize it, using any necessary means. Unless they can spread the word, the work might pass unnoticed. To be honest, I was very reluctant about this part, half an year ago, when they announced the “renaissance”. However, looking back at what they done I’d say they are on the right track. Of course, there is still a lot of work to match the “advertising” effort put into .NET. But right now C++ is getting more attention at conferences such as PDC or TechEd, or their publishing assets, such as Channel 9, MSDN or their team blogs. So I tried to assemble a collection of videos, blogs, books and code samples related to C++ or native development that they published since the announcement of the renaissance. So far it looks good, in my opinion.

Channel 9
E2E: Herb Sutter and Erik Meijer – Perspectives on C++
Craig Symonds and Mohsen Agsen: C++ Renaissance
Windows 7 Taskbar Integration for MFC Applications
Tony Goodhew: VC++ Developer Communication – Questions and Answers
Talkin’ C++ with Kate Gregory
MVP Summit 2011: Meet C++ MVPs Angel, PJ, Tom and Sheng
Talkin’ C++ with Alon, Marius, Bruno, and Jim
Talkin’ C++ with Boris Jabes: C++ Intellisense, Game Development, and Boris Faces His Demons
Application Restart and Recovery on Windows 7 in Native Code
Parallel Programming for C++ Developers: Tasks and Continuations, Part 1 of 2
Parallel Programming for C++ Developers: Tasks and Continuations, Part 2 of 2
Conversation with Herb Sutter: Perspectives on Modern C++(0x/11)
First Look: New ALM Tools for VC++ Developers
Modern Native C++ Development for Maximum Productivity
Mohsen Agsen – C++ Today and Tomorrow
Herb Sutter: C++ Questions and Answers
Herb Sutter – Heterogeneous Computing and C++ AMP
Daniel Moth: Blazing-fast code using GPUs and more, with C++ AMP
C9 Lectures: Stephan T Lavavej – Advanced STL, 1 of n
C9 Lectures: Stephan T Lavavej – Advanced STL, 2 of n
C9 Lectures: Stephan T Lavavej – Advanced STL, 3 of n
C9 Lectures: Stephan T Lavavej – Advanced STL, 4 of n
C9 Lectures: Stephan T Lavavej – Advanced STL, 5 of n

Visual C++ Team Blog
Grr… My VC++ Project Is Building Slower in VS2010. What Do I Do Now? (A Step by Step Guide)
C++/CLI IntelliSense in Visual Studio vNext
Exception Boundaries: Working With Multiple Error Handling Mechanisms
Troubleshooting Tips for IntelliSense Slowness
Build Related Improvement in VS2010 SP1
Converting An MFC Ribbon To Designer Format
Enforcing Correct Concurrent Access of Class Data

Parallel Programming in Native Code Blog
Sorting in PPL
How to pick your parallel sort?
The Concurrency Runtime and Visual C++ 2010: Lambda Expressions
The Concurrency Runtime and Visual C++ 2010: Automatic Type Deduction
The Concurrency Runtime and Visual C++ 2010: The decltype Type Specifier
The Concurrency Runtime and Visual C++ 2010: Rvalue References
The Concurrency Runtime and Visual C++ 2010: Transporting Exceptions between Threads
Building Responsive GUI Applications with PPL Tasks

MSDN Magazine
Writing a Debugging Tools for Windows Extension
Writing a Debugging Tools for Windows Extension, Part 2: Output
Writing a Debugging Tools for Windows Extension, Part 3: Clients and Callbacks
Agile C++ Development and Testing with Visual Studio and TFS

Books & Publications
Parallel Programming with Microsoft Visual C++
The Visual C++ Weekly

Code & Samples
Code samples for the Concurrency Runtime and Parallel Pattern Library in Visual Studio 2010
Bing Maps Trip Optimizer
Hilo: Developing C++ Applications for Windows 7
All-in-One Code Framework

, , , Hits for this post: 11399 .

Finding applications installed on a machine (the ones that you see in Control Panel Add/Remove programs) could be a little bit tricky, because there isn’t a bulletproof API or method. Each of the available methods has its own weak points. WMI is slow and can actually be disabled on a machine. MSI API only shows applications installed with an MSI, and reading directly from the Windows Registry is not an officially supported alternative. Thus it is an open point which one is the most appropriate, though the official answer will probably be MSI API.

In this post I will go through all of these three methods and show how to query for the installed applications and display the name, publisher, vendor and installation location (if available). Notice these are just some samples, and if you want to use this in your applications you’ll probably want to do additional things like better error checking. Because I want the code to work both with ANSI and UNICODE I will use the following defines

#include 
#include 

#ifdef _UNICODE
#define tcout       wcout
#define tstring     wstring
#else
#define tcout       cout
#define tstring     string
#endif

WMI
Win32_Product is a WMI class that represents a product installed by Windows Installer. For fetching the list of installed applications with WMI I will reuse the WMIQuery class I first shown in this post. You need to include Wbemidl.h and link with wbemuuid.lib.

In the code shown below WmiQueryValue() is a function that reads a property from the current record and returns it as an STL string (UNICODE or ANSI). WmiEnum() is a function that fetches and displays in the console all the installed applications.

class WMIQuery
{
   IWbemLocator* m_pLocator;
   IWbemServices* m_pServices;

public:
   WMIQuery():
      m_pLocator(NULL),
      m_pServices(NULL)
   {
   }

   bool Initialize()
   {
      // Obtain the initial locator to WMI
      HRESULT hr = ::CoCreateInstance(
         CLSID_WbemLocator,
         0,
         CLSCTX_INPROC_SERVER,
         IID_IWbemLocator, (LPVOID *) &m_pLocator);

      if (FAILED(hr))
      {
         cerr << "Failed to create IWbemLocator object. Err code = 0x" << hex << hr << endl;
         return false;
      }

      // Connect to WMI through the IWbemLocator::ConnectServer method
      // Connect to the root\cimv2 namespace with the current user
      hr = m_pLocator->ConnectServer(
         _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
         NULL,                    // User name. NULL = current user
         NULL,                    // User password. NULL = current
         0,                       // Locale. NULL indicates current
         NULL,                    // Security flags.
         0,                       // Authority (e.g. Kerberos)
         0,                       // Context object
         &m_pServices             // pointer to IWbemServices proxy
         );

      if (FAILED(hr))
      {
         cerr << "Could not connect. Error code = 0x" << hex << hr << endl;
         m_pLocator->Release();
         m_pLocator = NULL;
         return false;
      }

      // Set security levels on the proxy
      hr = ::CoSetProxyBlanket(
         m_pServices,                 // Indicates the proxy to set
         RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
         RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
         NULL,                        // Server principal name
         RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx
         RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
         NULL,                        // client identity
         EOAC_NONE                    // proxy capabilities
         );

      if (FAILED(hr))
      {
         cerr << "Could not set proxy blanket. Error code = 0x" << hex << hr << endl;
         m_pServices->Release();
         m_pServices = NULL;
         m_pLocator->Release();
         m_pLocator = NULL;
         return false;
      }

      return true;
   }

   IEnumWbemClassObject* Query(LPCTSTR strquery)
   {
      IEnumWbemClassObject* pEnumerator = NULL;
      HRESULT hr = m_pServices->ExecQuery(
         bstr_t("WQL"),
         bstr_t(strquery),
         WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
         NULL,
         &pEnumerator);

      if (FAILED(hr))
      {
         cerr << "Query for operating system name failed. Error code = 0x" << hex << hr < endl;
         return NULL;
      }

      return pEnumerator;
   }

   ~WMIQuery()
   {
      if(m_pServices != NULL)
      {
         m_pServices->Release();
         m_pServices = NULL;
      }

      if(m_pLocator != NULL)
      {
         m_pLocator->Release();
         m_pLocator = NULL;
      }
   }
};

tstring WmiQueryValue(IWbemClassObject* pclsObj,
                      LPCWSTR szName)
{
    tstring value;

    if(pclsObj != NULL && szName != NULL)
    {
        VARIANT vtProp;

        HRESULT hr = pclsObj->Get(szName, 0, &vtProp, 0, 0);
        if(SUCCEEDED(hr))
        {
            if(vtProp.vt == VT_BSTR && ::SysStringLen(vtProp.bstrVal) > 0)
            {
#ifdef _UNICODE
                value = vtProp.bstrVal;
#else
                int len = ::SysStringLen(vtProp.bstrVal)+1;
                if(len > 0)
                {
                    value.resize(len);
                    ::WideCharToMultiByte(CP_ACP,
                                          0,
                                          vtProp.bstrVal,
                                          -1,
                                          &value[0],
                                          len,
                                          NULL,
                                          NULL);
                }
#endif
            }
        }
    }

    return value;
}

void WmiEnum()
{
    HRESULT hres;

    // Initialize COM.
    hres =  ::CoInitializeEx(0, COINIT_MULTITHREADED);
    if (FAILED(hres))
    {
        cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl;
        return;
    }

    // Set general COM security levels
    hres =  ::CoInitializeSecurity(
        NULL,
        -1,                          // COM authentication
        NULL,                        // Authentication services
        NULL,                        // Reserved
        RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
        RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
        NULL,                        // Authentication info
        EOAC_NONE,                   // Additional capabilities
        NULL                         // Reserved
        );

    if (FAILED(hres))
    {
        cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
        ::CoUninitialize();
        return;
    }
    else
    {
        WMIQuery query;
        if(query.Initialize())
        {
            IEnumWbemClassObject* pEnumerator = query.Query(_T("SELECT * FROM Win32_Product"));

            if(pEnumerator != NULL)
            {
                // Get the data from the query
                IWbemClassObject *pclsObj;
                ULONG uReturn = 0;

                while (pEnumerator)
                {
                    HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

                    if(0 == uReturn)
                    {
                        break;
                    }

                    // find the values of the properties we are interested in
                    tstring name = WmiQueryValue(pclsObj, L"Name");
                    tstring publisher = WmiQueryValue(pclsObj, L"Vendor");
                    tstring version = WmiQueryValue(pclsObj, L"Version");
                    tstring location = WmiQueryValue(pclsObj, L"InstallLocation");

                    if(!name.empty())
                    {
                        tcout << name << endl;
                        tcout << "  - " << publisher << endl;
                        tcout << "  - " << version << endl;
                        tcout << "  - " << location << endl;
                        tcout << endl;
                    }

                    pclsObj->Release();
                }

                pEnumerator->Release();
            }
        }
    }

    // unintializa COM
    ::CoUninitialize();
}

A sample from the output of this WmiEnum() function looks like this:

Java(TM) 6 Update 25
– Oracle
– 6.0.250
– C:\Program Files\Java\jre6\

Java(TM) SE Development Kit 6 Update 25
– Oracle
– 1.6.0.250
– C:\Program Files\Java\jdk1.6.0_25\

Microsoft .NET Framework 4 Client Profile
– Microsoft Corporation
– 4.0.30319
-

Microsoft Sync Framework Services v1.0 SP1 (x86)
– Microsoft Corporation
– 1.0.3010.0
-

Microsoft ASP.NET MVC 2 – Visual Studio 2010 Tools
– Microsoft Corporation
– 2.0.50217.0
-

Adobe Reader X (10.0.1)
– Adobe Systems Incorporated
– 10.0.1
– C:\Program Files\Adobe\Reader 10.0\Reader\

One can notice that the code is relatively long, but most important it is very slow.

MSI API
Two of the MSI API functions can help fetching the list of installed applications:

  • MsiUnumProductsEx: enumerates through one or all the instances of products that are currently advertised or installed (requires Windows Installer 3.0 or newer)
  • MsiGetProductInfoEx: returns product information for advertised and installed products

In order to use these functions you need to include msi.h and link to msi.lib.

In the code below, MsiQueryProperty() is a function that returns the value of product property (as a tstring as defined above) by calling MsiGetProductInfoEx. MsiEnum() is a function that iterates through all the installed applications and prints in the console the name, publisher, version and installation location.

tstring MsiQueryProperty(LPCTSTR szProductCode,
                         LPCTSTR szUserSid,
                         MSIINSTALLCONTEXT dwContext,
                         LPCTSTR szProperty)
{
    tstring value;

    DWORD cchValue = 0;
    UINT ret2 = ::MsiGetProductInfoEx(
        szProductCode,
        szUserSid,
        dwContext,
        szProperty,
        NULL,
        &cchValue);

    if(ret2 == ERROR_SUCCESS)
    {
        cchValue++;
        value.resize(cchValue);

        ret2 = ::MsiGetProductInfoEx(
            szProductCode,
            szUserSid,
            dwContext,
            szProperty,
            (LPTSTR)&value[0],
            &cchValue);
    }

    return value;
}

void MsiEnum()
{
    UINT ret = 0;
    DWORD dwIndex = 0;
    TCHAR szInstalledProductCode[39] = {0};
    TCHAR szSid[128] = {0};
    DWORD cchSid;
    MSIINSTALLCONTEXT dwInstalledContext;

    do
    {
        memset(szInstalledProductCode, 0, sizeof(szInstalledProductCode));
        cchSid = sizeof(szSid)/sizeof(szSid[0]);

        ret = ::MsiEnumProductsEx(
            NULL,           // all the products in the context
            _T("s-1-1-0"),  // i.e.Everyone, all users in the system
            MSIINSTALLCONTEXT_USERMANAGED | MSIINSTALLCONTEXT_USERUNMANAGED | MSIINSTALLCONTEXT_MACHINE,
            dwIndex,
            szInstalledProductCode,
            &dwInstalledContext,
            szSid,
            &cchSid);

        if(ret == ERROR_SUCCESS)
        {
            tstring name = MsiQueryProperty(
                szInstalledProductCode,
                cchSid == 0 ? NULL : szSid,
                dwInstalledContext,
                INSTALLPROPERTY_INSTALLEDPRODUCTNAME);

            tstring publisher = MsiQueryProperty(
                szInstalledProductCode,
                cchSid == 0 ? NULL : szSid,
                dwInstalledContext,
                INSTALLPROPERTY_PUBLISHER);                

            tstring version = MsiQueryProperty(
                szInstalledProductCode,
                cchSid == 0 ? NULL : szSid,
                dwInstalledContext,
                INSTALLPROPERTY_VERSIONSTRING);

            tstring location = MsiQueryProperty(
                szInstalledProductCode,
                cchSid == 0 ? NULL : szSid,
                dwInstalledContext,
                INSTALLPROPERTY_INSTALLLOCATION);            

            tcout << name << endl;
            tcout << "  - " << publisher << endl;
            tcout << "  - " << version << endl;
            tcout << "  - " << location << endl;
            tcout << endl;

            dwIndex++;
        }

    } while(ret == ERROR_SUCCESS);
}

And this is a sample for the WmiEnum() function.

Java(TM) 6 Update 25
– Oracle
– 6.0.250
– C:\Program Files\Java\jre6\

Java(TM) SE Development Kit 6 Update 25
– Oracle
– 1.6.0.250
– C:\Program Files\Java\jdk1.6.0_25\

Microsoft .NET Framework 4 Client Profile
– Microsoft Corporation
– 4.0.30319
-

Microsoft Sync Framework Services v1.0 SP1 (x86)
– Microsoft Corporation
– 1.0.3010.0
-

Microsoft ASP.NET MVC 2 – Visual Studio 2010 Tools
– Microsoft Corporation
– 2.0.50217.0
-

Adobe Reader X (10.0.1)
– Adobe Systems Incorporated
– 10.0.1
– C:\Program Files\Adobe\Reader 10.0\Reader\

Windows Registry
Installed applications are listed in Windows Registry under HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall. The KB247501 article explains the structure of the information under this Registry key. Make sure you read it if you decide to use this approach.

In the code shown below, RegistryQueryValue() is a function that queries the value of a name/value pair in the registry and returns the value as a tstring. RegistryEnum() is a function that prints to the console all the installed application as found in the registry.

tstring RegistryQueryValue(HKEY hKey,
                           LPCTSTR szName)
{
    tstring value;

    DWORD dwType;
    DWORD dwSize = 0;

    if (::RegQueryValueEx(
        hKey,                   // key handle
        szName,                 // item name
        NULL,                   // reserved
        &dwType,                // type of data stored
        NULL,                   // no data buffer
        &dwSize                 // required buffer size
        ) == ERROR_SUCCESS && dwSize > 0)
    {
        value.resize(dwSize);

        ::RegQueryValueEx(
            hKey,                   // key handle
            szName,                 // item name
            NULL,                   // reserved
            &dwType,                // type of data stored
            (LPBYTE)&value[0],      // data buffer
            &dwSize                 // available buffer size
            );
    }

    return value;
}

void RegistryEnum()
{
    HKEY hKey;
    LONG ret = ::RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,     // local machine hive
        _T("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"), // uninstall key
        0,                      // reserved
        KEY_READ,               // desired access
        &hKey                   // handle to the open key
        );

    if(ret != ERROR_SUCCESS)
        return;

    DWORD dwIndex = 0;
    DWORD cbName = 1024;
    TCHAR szSubKeyName[1024];

    while ((ret = ::RegEnumKeyEx(
        hKey,
        dwIndex,
        szSubKeyName,
        &cbName,
        NULL,
        NULL,
        NULL,
        NULL)) != ERROR_NO_MORE_ITEMS)
    {
        if (ret == ERROR_SUCCESS)
        {
            HKEY hItem;
            if (::RegOpenKeyEx(hKey, szSubKeyName, 0, KEY_READ, &hItem) != ERROR_SUCCESS)
                continue;

            tstring name = RegistryQueryValue(hItem, _T("DisplayName"));
            tstring publisher = RegistryQueryValue(hItem, _T("Publisher"));
            tstring version = RegistryQueryValue(hItem, _T("DisplayVersion"));
            tstring location = RegistryQueryValue(hItem, _T("InstallLocation"));

            if(!name.empty())
            {
                tcout << name << endl;
                tcout << "  - " << publisher << endl;
                tcout << "  - " << version << endl;
                tcout << "  - " << location << endl;
                tcout << endl;
            }

            ::RegCloseKey(hItem);
        }
        dwIndex++;
        cbName = 1024;
    }
    ::RegCloseKey(hKey);
}

And a sample output of the RegistryEnum() function:

Java(TM) SE Development Kit 6 Update 25

– Oracle
– 1.6.0.250
– C:\Program Files\Java\jdk1.6.0_25\

Microsoft Visual Studio 2005 Tools for Office Runtime

– Microsoft Corporation
– 8.0.60940.0
-

MSDN Library for Visual Studio 2008 – ENU

– Microsoft
– 9.0.21022
– C:\Program Files\MSDN\MSDN9.0\

Microsoft SQL Server Compact 3.5 SP2 ENU

– Microsoft Corporation
– 3.5.8080.0
– C:\Program Files\Microsoft SQL Server Compact Edition\

Microsoft .NET Framework 4 Client Profile

– Microsoft Corporation
– 4.0.30319
-

, , , , Hits for this post: 8268 .

The MVP Global Summit 2011 took place in Redmond and Bellevue at the beginning of March. This year I attended for the fifth time, and as usual it was a great time. Fellow MVPs, discussions with the Microsoft product groups, parties, everything made it worth it. And this year it was also a little bit special because I was named C++ MVP of the Year, a distinction shared with Kate Gregory. A 3rd C++ MVP, Sheng Jiang, was also named MVP of the Year as top answerer in the MSDN forums. As an MVP of the Year I was invited to attend a dinner held by S. Somasegar, senior vice president of the Developer Division at Microsoft, where I joined the other MVPs of the year in the awarded categories (such as C#, VB, ASP.NET, etc.), but also top figures from Microsoft, such as Scott Guthrie, Jason Zander, Anders Hejlsberg or Scott Hanselman and the Microsoft community leads. This is a picture from the event, showing, from left to right, Sheng Jiang, myself, Diego Dagum – Windows C++ community PM, and Kate Gregory.

Another special moment at this summit was being interviewed by Charles Torre for channel9. He did several interviews with C++ MVPs and these interviews were posted on channel9 recently. Here you can find the original post for the interview with Alon Fliess, Bruno Boucard, Jim Berveridge and myself. We talked mostly about C++, but also the MVP program.

The other interviews that I mentioned can be found here:

Looking forward for the next summit experience.

, , , Hits for this post: 9886 .

Channel9 recently posted a video with the Parallel Computing Concurrency Runtime team talking, mainly, about tasks and continuations, new features to the Parallel Patterns Library. These are already available through the ConcRT Extra’s Sample Pack. You can watch the half hour interview with the team here.

Besides the new stuff they shown, I particularly liked two things that Artur Laksberg said. The first was about the difference between parallelism and concurrency:

Parallelism is doing the same amount of work faster by utilizing multiple cores, multiple processors. Concurrency is understood as doing more work in the same amount of time.

The other one was about threads and tasks:

We want people to stop thinking about threads and start thinking in terms of independent, or not independent, units of work. You have one piece of work, you compose it with another piece of work and you have two tasks, you join together and what you get as a result is another task. And then, concurrency, as somebody said, just happens. It just happens if the runtime decides it’s beneficial for you, that it is safe to execute those two chunks, tasks, in parallel.

Hopefully people will start understanding that threads are obsolete and they should be thinking in tasks.

UPDATE: Microsoft Technical Computing group announced yesterday the availability of a book called Parallel Programming with Microsoft Visual C++: Design patterns for Decomposition, and Coordination on Multicore Architectures, describing six key patterns for data and task parallelism and how to implement them in VC++ using the Parallel Patterns Library and Asynchronous Agents Library, which shipped with Visual Studio 2010. There is also a printed version for the book. You can read more about it on VC++ team’s blog.

, , , , , Hits for this post: 9611 .

I’ve ran recently across this question: how to find (using C++) if a computer is a laptop? That is possible with WMI and many answers (such as this) point to the Win32_SystemEnclosure class. This class has a member called ChassisTypes, which is an array of integers indicating possible chassis types. At least one of them should indicate a laptop. However, there might be several problems with this solution. First, there are several values for “laptops”:

  • 8 – Portable
  • 9 – Laptop
  • 10 – Notebook

Different machines might return different values. And more important, this property might not be defined on all computers. A more reliable solution is explained in this TechNet article Finding Computers That Are Laptops. The solution described there suggests checking for several properties:

  • Win32_SystemEnclosure, ChassisTypes(1)=10.
  • Win32_Battery or Win32_PortableBattery.
  • Win32_PCMCIAController
  • Win32_DriverVXD.Name = “pccard”
  • Win32_ComputerSystem.Manufacturer
  • Win32_ComputerSystem.Model

The following code shows how one can query for the chassis types using C++. Run queries for the other properties to make sure you are running on a laptop.

#define _WIN32_DCOM

#include <iostream>
using namespace std;

#include <comdef.h>
#include <Wbemidl.h>

#pragma comment(lib, "wbemuuid.lib")

class WMIQuery
{
   IWbemLocator* m_pLocator;
   IWbemServices* m_pServices;

public:
   WMIQuery():
      m_pLocator(NULL),
      m_pServices(NULL)
   {
   }

   bool Initialize()
   {
      // Obtain the initial locator to WMI
      HRESULT hr = ::CoCreateInstance(
         CLSID_WbemLocator,
         0,
         CLSCTX_INPROC_SERVER,
         IID_IWbemLocator, (LPVOID *) &m_pLocator);

      if (FAILED(hr))
      {
         cerr << "Failed to create IWbemLocator object. Err code = 0x" << hex << hr << endl;
         return false;
      }

      // Connect to WMI through the IWbemLocator::ConnectServer method
      // Connect to the root\cimv2 namespace with the current user
      hr = m_pLocator->ConnectServer(
         _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
         NULL,                    // User name. NULL = current user
         NULL,                    // User password. NULL = current
         0,                       // Locale. NULL indicates current
         NULL,                    // Security flags.
         0,                       // Authority (e.g. Kerberos)
         0,                       // Context object
         &m_pServices             // pointer to IWbemServices proxy
         );

      if (FAILED(hr))
      {
         cerr << "Could not connect. Error code = 0x" << hex << hr << endl;
         m_pLocator->Release();
         m_pLocator = NULL;
         return false;
      }

      // Set security levels on the proxy
      hr = ::CoSetProxyBlanket(
         m_pServices,                 // Indicates the proxy to set
         RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
         RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
         NULL,                        // Server principal name
         RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx
         RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
         NULL,                        // client identity
         EOAC_NONE                    // proxy capabilities
         );

      if (FAILED(hr))
      {
         cerr << "Could not set proxy blanket. Error code = 0x" << hex << hr << endl;
         m_pServices->Release();
         m_pServices = NULL;
         m_pLocator->Release();
         m_pLocator = NULL;
         return false;
      }

      return true;
   }

   IEnumWbemClassObject* Query(LPCTSTR strquery)
   {
      IEnumWbemClassObject* pEnumerator = NULL;
      HRESULT hr = m_pServices->ExecQuery(
         bstr_t("WQL"),
         bstr_t(strquery),
         WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
         NULL,
         &pEnumerator);

      if (FAILED(hr))
      {
         cerr << "Query for operating system name failed. Error code = 0x" << hex << hr << endl;
         return NULL;
      }

      return pEnumerator;
   }

   ~WMIQuery()
   {
      if(m_pServices != NULL)
      {
         m_pServices->Release();
         m_pServices = NULL;
      }

      if(m_pLocator != NULL)
      {
         m_pLocator->Release();
         m_pLocator = NULL;
      }
   }
};

int _tmain(int argc, _TCHAR* argv[])
{
   HRESULT hres;

   // Initialize COM.
   hres =  ::CoInitializeEx(0, COINIT_MULTITHREADED);
   if (FAILED(hres))
   {
      cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl;
      return 1;
   }

   // Set general COM security levels
   hres =  ::CoInitializeSecurity(
      NULL,
      -1,                          // COM authentication
      NULL,                        // Authentication services
      NULL,                        // Reserved
      RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
      RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
      NULL,                        // Authentication info
      EOAC_NONE,                   // Additional capabilities
      NULL                         // Reserved
      );

   if (FAILED(hres))
   {
      cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
      ::CoUninitialize();
      return 1;
   }
   else
   {
      WMIQuery query;
      if(query.Initialize())
      {
         IEnumWbemClassObject* pEnumerator = query.Query(_T("SELECT * FROM Win32_SystemEnclosure"));

         if(pEnumerator != NULL)
         {
            // Get the data from the query
            IWbemClassObject *pclsObj;
            ULONG uReturn = 0;

            while (pEnumerator)
            {
               HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

               if(0 == uReturn)
               {
                  break;
               }

               VARIANT vtProp;

               hr = pclsObj->Get(L"Name", 0, &vtProp, 0, 0);
               wcout << "Name:    " << vtProp.bstrVal << endl;

               hr = pclsObj->Get(L"ChassisTypes", 0, &vtProp, 0, 0);
               wcout << "Chassis: ";
               SAFEARRAY* parrValues = NULL;

               if (vtProp.vt & VT_ARRAY)
               {
                  if (VT_BYREF & vtProp.vt)
                     parrValues = *vtProp.pparray;
                  else
                     parrValues = vtProp.parray;
               }

               if (parrValues != NULL)
               {
                  SAFEARRAYBOUND arrayBounds[1];
                  arrayBounds[0].lLbound = 0;
                  arrayBounds[0].cElements = 0;

                  SafeArrayGetLBound(parrValues, 1, &arrayBounds[0].lLbound);
                  SafeArrayGetUBound(parrValues, 1, (long*)&arrayBounds[0].cElements);
                  arrayBounds[0].cElements -= arrayBounds[0].lLbound;
                  arrayBounds[0].cElements += 1;

                  if (arrayBounds[0].cElements > 0)
                  {
                     for (ULONG i = 0; i < arrayBounds[0].cElements; i++)
                     {
                        LONG lIndex = (LONG)i;
                        INT item;

                        HRESULT hr = ::SafeArrayGetElement(parrValues, &lIndex, &item);

                        if(SUCCEEDED(hr))
                        {
                           LPCTSTR szType = NULL;
                           switch(item)
                           {
                           case 1: szType = _T("Other"); break;
                           case 2: szType = _T("Unknown"); break;
                           case 3: szType = _T("Desktop"); break;
                           case 4: szType = _T("Low Profile Desktop"); break;
                           case 5: szType = _T("Pizza Box"); break;
                           case 6: szType = _T("Mini Tower"); break;
                           case 7: szType = _T("Tower"); break;
                           case 8: szType = _T("Portable"); break;
                           case 9: szType = _T("Laptop"); break;
                           case 10:szType = _T("Notebook"); break;
                           case 11:szType = _T("Hand Held"); break;
                           case 12:szType = _T("Docking Station"); break;
                           case 13:szType = _T("All in One"); break;
                           case 14:szType = _T("Sub Notebook"); break;
                           case 15:szType = _T("Space-Saving"); break;
                           case 16:szType = _T("Lunch Box"); break;
                           case 17:szType = _T("Main System Chassis"); break;
                           case 18:szType = _T("Expansion Chassis"); break;
                           case 19:szType = _T("SubChassis"); break;
                           case 20:szType = _T("Bus Expansion Chassis"); break;
                           case 21:szType = _T("Peripheral Chassis"); break;
                           case 22:szType = _T("Storage Chassis"); break;
                           case 23:szType = _T("Rack Mount Chassis"); break;
                           case 24:szType = _T("Sealed-Case PC"); break;
                           }
                           wcout << szType;
                           if(i+1 < arrayBounds[0].cElements)
                              wcout << ", ";
                        }
                     }

                     wcout << endl;
                  }
               }

               VariantClear(&vtProp);

               pclsObj->Release();
            }

            pEnumerator->Release();
         }
      }
   }

   ::CoUninitialize();

   return 0;
}

On my laptop, the program output was:

Name: System Enclosure
Chassis: Notebook

, , , Hits for this post: 12231 .

A window’s system menu (now called simply window menu) features by default commands like Move, Size or Close. (When the user selects one of these commands a WM_SYSCOMMAND message is sent to the window.) What if you want to remove and add these commands on the fly? Here is how you can do it.

This is how a default Window menu looks for a dialog window.

To remove the Move command:

   CMenu* pSysMenu = GetSystemMenu(FALSE);
   if(pSysMenu != NULL)
      pSysMenu->RemoveMenu(SC_MOVE, MF_BYCOMMAND);

To add the Move command, you can do something like this (you can also insert it at a specific position, other than 0):

   CMenu* pSysMenu = GetSystemMenu(FALSE);
   if(pSysMenu != NULL)
      pSysMenu->InsertMenu(0, MF_BYCOMMAND, SC_MOVE, _T("Move"));

, Hits for this post: 5522 .

It is possible to register both 32-bit and 64-bit versions of the same COM server on 64-bit machine. This leads to several questions such as how are they registered and which one of the two is used. I will try to answer them below. But first, let’s start with an example.

Example
Let’s say we have the a simple COM local server called COM3264Server.exe. There is just one interface called ICoCOM3264Server. Here is the IDL file:

[
	object,
	uuid(733C70A7-F7EC-4C15-85D2-5CDB14F4110B),
	dual,
	nonextensible,
	pointer_default(unique)
]
interface ICoCOM3264Server : IDispatch{
   [id(1), helpstring("Says hello")] HRESULT SayHello(void);
};
[
	uuid(2F25FC66-2380-42FD-8476-8B5917FB1BF1),
	version(1.0),
]
library COM3264ServerLib
{
	importlib("stdole2.tlb");
	[
		uuid(9268A299-E817-4C5D-8627-C2582B66F16D)
	]
	coclass CoCOM3264Server
	{
		[default] interface ICoCOM3264Server;
	};
};

The implementation of the method SayHello() is straight forward; it just displays a message box with a text that varies between the two architectures, x64 and x86.

STDMETHODIMP CCoCOM3264Server::SayHello(void)
{
#ifdef _WIN64
   ::MessageBox(NULL, _T("Hello from 64-bit COM server!"), _T("COM3264Server"), MB_OK);
#else
   ::MessageBox(NULL, _T("Hello from 32-bit COM server!"), _T("COM3264Server"), MB_OK);
#endif

   return S_OK;
}

Registry
When you register the COM server, the 32-bit and 64-bit versions are registered in different parts of the registry. On 64-bit machine, the registry has two views (or modes):

  • a native view, for 64-bit application; (e.g. registry path for CLSIDs is HKEY_CLASSES_ROOT\CLSID\)
  • a WOW64 view, which enables redirections for 32-bit applications, a process transparent to the user (e.g. registry path for CLSIDs is HKEY_CLASSES_ROOT\Wow6432Node\CLSID\)

Here is the Registry registration of the (native) 64-bit COM server (notice the registry key in the status bar of the editor, and the path to the server executable).

On, the other hand, the 32-bit COM server is registered under the Wow6432 node.

So if both versions are registered in Windows Registry, which one is picked? Well, both the server and the client can specify which architecture to use:

  • the COM server can do this via the PreferredServerBitness Registry value
  • the client can do this using one of the flags CLSCTX_ACTIVATE_32_BIT_SERVER and CLSCTX_ACTIVATE_64_BIT_SERVER, which overrides the server preference

If neither the client nor the server specifies a preference, then:

  • If the computer that hosts the server is running Windows Server 2003 with Service Pack 1 (SP1) or a later system, then COM will try to match the server architecture to the client architecture. In other words, for a 32-bit client, COM will activate a 32-bit server if available; otherwise it will activate a 64-bit version of the server. For a 64-bit client, COM will activate a 64-bit server if available; otherwise it will activate a 32-bit server.
  • If the computer that hosts the server is running Windows XP or Windows Server 2003 without SP1 or later installed, then COM will prefer a 64-bit version of the server if available; otherwise it will activate a 32-bit version of the server.

Server Preference
The server can specify its preferred architecture in the PreferredServerBitness value under AppId (available only on 64-bit Windows). This integer value can be:

  • 1: Match the server architecture to the client architecture. For example, if the client is 32-bit, use a 32-bit version of the server, if it is available. If not, the client’s activation request will fail.
  • 2: Use a 32-bit version of the server. If one does not exist, the client’s activation request will fail.
  • 3: Use a 64-bit version of the server. If one does not exist, the client’s activation request will fail.

Here is the value set in Registry to specify the 64-bit architecture.

When you run the client, it launches the 64-bit version and in my example the following window pops-up:

If I change the value to indicate the 32-bit architecture, the other COM server is launched and the displayed message is:

Client Preference
The client code I used so far looked like this:

   ICoCOM3264Server* pServer;

   HRESULT hr = ::CoCreateInstance(
      CLSID_CoCOM3264Server,
      NULL,
      CLSCTX_LOCAL_SERVER|CLSCTX_REMOTE_SERVER,
      IID_ICoCOM3264Server,
      (void**)&pServer);

   if(SUCCEEDED(hr))
   {
      pServer->SayHello();

      pServer->Release();
   }

However, the 64-bit version of Windows added new flags to the CLSCTX enumeration.

  • CLSCTX_ACTIVATE_32_BIT_SERVER: used to activate or connect to a 32-bit version of the server; fail if one is not registered.
  • CLSCTX_ACTIVATE_64_BIT_SERVER: used to activate or connect to a 64 bit version of the server; fail if one is not registered.

As I mentioned earlier, if the client uses one of these flags, it overrides the server preference (specified via the PreferredServerBitness Registry value).

In the following example, the client requests the 64-bit COM server:

   HRESULT hr = ::CoCreateInstance(
      CLSID_CoCOM3264Server,
      NULL,
      CLSCTX_LOCAL_SERVER|CLSCTX_REMOTE_SERVER|CLSCTX_ACTIVATE_64_BIT_SERVER,
      IID_ICoCOM3264Server,
      (void**)&pServer);

And if you run it, no matter what the server specified, the 64-bit COM server is launched.

To read more about the subject see the MSDN links above.

, , , Hits for this post: 13785 .

I’m working on a project to port a 32-bit application for the x64 platform. The first errors that came up when building for x64 were related to inline ASM code, which is no longer supported in VC++ for x64. VC++ has an inline assembler built within the compiler, so you could write assembly code directly in C++ without the need of an external assembler (such as MASM). However, this works only on x86. For x64 and IA64 this is no longer possible. There are several workaround for this:

  • put the assembly code into a separate file and use MASM for x64
  • use compiler intrinsics, which are functions that basically wrap assembly instructions or sequence of intructions
  • rewrite in C++, use Windows APIs, etc.

One error that I had was related to this assembly code:

   __asm {
      int 3
   }

This was used to generate a breakpoint. This can be easily replaced with compiler intrinsic __debugbreak(), which has an identical effect, except that is available on all platforms.

Other errors I had were due to assembly code used to retrieve the value of the EIP and EBP registers. They were used for walking the stack.

__declspec(naked) DWORD_PTR* GetEip()
{
   __asm {
      pop   eax
      push  eax
      ret
   }
}

__declspec(naked) DWORD_PTR* GetEbp()
{
   __asm {
      mov   eax, ebp
      ret
   }
}

The naked specifier is another thing that is not supported on x64. One way to retrieve the value of these registers that works both with x86 and x64 is using RtlCaptureContext, except that this won’t work on operating system previous to Windows XP. In you don’t care about those operating systems, you could write something like this:

      CONTEXT context;
      RtlCaptureContext(&context);

#ifdef _WIN64
      DWORD_PTR* ebp = (DWORD_PTR*)context.Rsp;
      DWORD_PTR* eip = (DWORD_PTR*)context.Rip;
#else
      DWORD_PTR* ebp = (DWORD_PTR*)context.Ebp;
      DWORD_PTR* eip = (DWORD_PTR*)context.Eip;
#endif

Attention, on x64, register EBP (actually RBP) is no longer used. You should use RSP for getting the stack frame.

However, if you want to build a portable stack walking, use StackWalk64. Despite the 64 suffix, this function works on all platforms (x86, x64, IA64). Here is an article that shows how to walk the stack using StackWalk64.

, , , Hits for this post: 11587 .

If you want to port an existing 32-bit application for the x64 platform (especially since 32-bit processors will soon be history), or if you want to target x64 for a new application, the first step in building for x64 is setting up your solution. In this post I will explain what are the steps in configuring x64 as target platform for your projects. Of course you must have the 64-bit compiler and tools installed to make this possible.

Here is an example of a solution with two VC++ project (a Win32 DLL and a console application).

The VC++ wizards add only Win32 (i.e. x86) as target platform for your projects and for your solution (I believe this will change in the future). You can see this by expanding the platforms combo.

If you open the Configuration Manager window for the solution you can see the available solution platforms, and for each project, the available project platforms (these might not be the same).

The configuration manager allows you to set for each pair of solution configuration and solution platform the platform for each individual project. So in theory you can configure for Debug with Win32 at solution level to have, in my demo solution, DemoProject targeting Win32 and DemoLibrary targeting x64. Of course, in practice you probably want to have all projects targeting Win32 in this case, and for the pair Debug with x64 at solution level, have all projects targeting the x64 platform.

To make this platform available for the solution, expand the Active solution platform combo and select . The New Solution Platform dialog will open and allow you to define a new target platform for the solution. Select x64 and for Copy settings from select Win32.

Then make sure you check the Create new project platforms checkbox. In this case the x64 platform will also be defined for every project included in the solution.

After you do this, the x64 platform will be available in the configuration manager for the solution and the projects, so that you can configure the relationships.

Also, the solution platforms combo from the toolbar now displays both Win32 and x64.

But now let’s say you add a new project to the solution. Let’s call this DemoLibrary2.

Since the project was added after defining x64 as a target for the solution and all the projects, the x64 platform won’t be defined for this project (remember, by default, the VC++ wizards don’t add x64 as target platform). You can see this in the configuration manager, where only Win32 is available for the new project.

To define the x64 target for the new project, use the project platform combo and select (see the image above) to open the New Project Platform dialog. Select x64, as new platform, but make sure the checkbox Create new solution platforms remains unselected. Otherwise you’ll get an error, since the platform is already available at the solution level.

After that, the x64 platform will be available for the new project too, and you can make the appropriate configurations in the Configuration Manager window.

And with that you’re done. All you have to do now is configure the settings for each project, for each configuration and platform.

, , Hits for this post: 10251 .

Two days ago I posted a simple implementation of a game of colors. Though it was intended only as an exercise, someone has criticizes the use of an int** to hold the grid information, mainly for two reasons:

  • the footprint on 64-bit platforms can get nasty
  • the explicitly allocated memory, instead of using a std::vector

So this is the code:

int** m_pCells; 

void Create()
{
   m_pCells = new int*[m_nSize];
   for(int i = 0; i < m_nSize; ++i)
      m_pCells[i] = new int[m_nSize];
}

Let’s see how much memory it takes. The total size should be:

totalsize = sizeof(m_pCells) + sizeof(m_pCells[0]) * m_nSize + m_nSize * m_nSize * sizeof(int);

On 32-bit platforms, the size of a pointer is the same with the size of int and is 4 bytes. For the maximum size allowed for my grid, which is 50, the total size in bytes for the grid is: 4 + 4*50 + 50*50*4 = 10204.

On 64-bit platforms, the size of a pointer is 8 bytes, but the size of int is still 4 bytes. So for a grid with 50 rows and columns it needs: 8 + 8*50 + 50*50*4 = 10408 bytes. That’s a 2% increase of required memory.

The memory footprint was the last thing I had in mind when I wrote this simple exercise. Well, of course there is a way to require only 4 more bytes on 64-bit platforms. And that is using an int* allocating m_nSize*m_nSize elements.

int* m_pCells;

void Create()
{
   m_pCells = new int[m_nSize * m_nSize];
}

void Destroy()
{
   delete [] m_pCells;
   m_pCells = NULL;
}

With this implementation, when you need to access the element at row i and column j, you have to use m_pCells[i * m_nSize + j].

As for the second argument, that explicitly using operator new[] to allocate memory instead of using a vector of vectors, well, what could I say? Sure, why not. Different people use different programming styles. As long as all implementation are correct and achieve the same goal with similar performance, I guess everyone is entitle to code as he/she wants. But if we go back to the memory footprint, I would also guess that the use of vectors would take more memory than pointers to int, because the size of a vector is several times the size of a pointer. But I wouldn’t say that’s an important issue here.

Anyway, these arguments remember me the joke (or maybe it’s serious) about the rules of optimization:

  1. Don’t optimize.
  2. Don’t optimize yet (for experts only).

(Of course there are super experts that can ignore these rules.)

, , , , Hits for this post: 6966 .