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: 7303 .

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: 7302 .

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: 9541 .

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: 4721 .

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: 10515 .

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: 9169 .

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: 8058 .

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: 6326 .

Colors Game

One of the games I like the most on my new phone is about covering a grid formed by cells of different colors with a single color within a limited number of moves. After playing it again and again for a week, I decided to write my own game for the PC.

The rules are:

  • the grid has an equal number of rows and columns, that can vary from 5 to 50
  • cells are colored with six colors
  • coloring the grid always starts from the top left cell
  • adjacent cells having the same color form a single shape; game ends when this shape covers the entire grid
  • to change the color of the growing shape, use the six buttons available on the right side of the grid
  • grid must be covered within a number of moves; if you exceed that number of moves you lose
  • when you win, you automatically get to the next level
  • you can change the size of the grid from the menu

Here are the available downloads: the sources, and the executable.

, Hits for this post: 5049 .

In the past months I have written about what’s new in Visual Studio 2010 with regard to Visual C++. In this post I will summarize these articles.

In addition, here are some more articles about Visual C++ 2010 that I published on codeguru.com.

Hits for this post: 10269 .