C++ in Visual Studio 2005 - Some Basics

If you've ever written VB.NET code and you've tried your hand at creating a C++ app then you probably know the frustration of how hard it is to get a simple C++ project running and working. Oh sure, 'Hello World' isn't so bad (partially because it can be pre-built for you in Visual Studio) but try to implement a function out of an API and your simple half hour project could easily turn into a two day project.

The purpose of this blog is for those starting C++ in Visual Studio .NET 2005. For the purpose of this post, I'll include sample code that I wrote which makes use of msienumproducts from the Windows Installer API. This function will return a list, or enumerate, products installed by the Windows Installer on your machine. First, take a look at the function descriptor on this MSDN page: https://msdn2.microsoft.com/en-us/library/aa370101.aspx - we see the following to start:

 UINT MsiEnumProducts( DWORD iProductIndex, LPTSTR lpProductBuf );

Here we see the declarator for MsiEnumProducts is of type UINT, an unsigned integer. That means when this function runs, it's going to return an integer value that will signify one of five things. The MSDN page does tell you what those five things are, but not their corresponding integers, so I'll list them below:

  • 1610 - ERROR_BAD_CONFIGURATION
  • 87 - ERROR_INVALID_PARAMETER
  • 259 - ERROR_NO_MORE_ITEMS
  • 8 - ERROR_NOT_ENOUGH_MEMORY
  • 0 - ERROR_SUCCESS (not really an error, this actually means it worked)

This function takes two arguments to work properly. The first is iProductIndex, declared as a DWORD, and lpProductBuf, declared as a LPTSTR which is a typedef. Not providing these in your code will product a compilation error.

Okay, so now that we know what the function is all about we can just slap it into our code and assign variables to it, right? Well, not exactly. Remember, C++ gives the user 'ultimate' control over the environment, which also means a lot more work needs to be done to make simple things work - the same things VB programmers could easily take for granted. The next thing you should take a look at is the requirements.

First, the Version tells us what we need to even be able to use this method. Since this function is from the Windows API we need the Windows Installer.

We need to reference the library (.lib) file. On the MSDN page we see the filename that we need is MSI.lib. So now let's add a reference to it in our project. You do that in your project by right clicking the project name in the Solution Explorer or through the Project, <ProjectName> Properties menu. Expand configuration properties, then expand Linker and click on the Input line item. To the right you should see 'Additional Dependencies'. You want to include the path and file name in quotes here. With most API calls, it's a good idea to have the SDK that correlates to them installed. In this case, hopefully you have the latest Platform SDK installed. If so, you can find this file under C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib and the file is msi.lib. So the path in quotes in the Additional Dependencies line would be:
"C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib\msi.lib"

Failure to include the library file reference will result in an error when you build your project. In this case you get the following errors in the output window:

Error 1 error LNK2001: unresolved external symbol _MsiEnumProductsW@8 MsiEnumProduct_Sample.obj 

Error 2 fatal error LNK1120: 1 unresolved externals C:\Documents and Settings\<profile name>\My Documents\Visual Studio 2005\Projects\<Project Name>\Release\ProjectName.exe

The last requirement, the DLL file, should already be met as MSI.DLL exists in the Windows\System32 folder on most machines. If it's not there, the Windows Installer won't run and you won't be able to install any Windows Installer based programs. Time to head to Windows Update and get the latest Windows Installer, eh?

Now let's take a look at some sample code. In this example I created a Win32 console application.

#include "stdafx.h"
#include <windows.h>
#include <msiquery.h>
#include <iostream>
#include <string>
using namespace std;

int main(){
     TCHAR ProductCode[40];
     UINT nResult;
     string errResult;
     for (int nIndex = 0;;nIndex++)
     {
          nResult = MsiEnumProducts(nIndex,ProductCode);
          if (nResult != ERROR_SUCCESS)
          {
               if (nResult == 1610)
                    errResult = "ERROR_BAD_CONFIGURATION";
               if (nResult == 87)
                    errResult = "ERROR_INVALID_PARAMETER";
               if (nResult == 259)
                    errResult = "ERROR_NO_MORE_ITEMS";
               if (nResult == 8)
                    errResult ="ERROR_NOT_ENOUGH_MEMORY";
               cout << errResult << endl;
               break;
         }
       _tprintf(TEXT(" %4d - %s\r\n"), nIndex, ProductCode);
    }
   cin.ignore();
   return 0;
}

I'm using two different ways to display results, cout and _tprintf. The reason I'm doing this is because cout will let me display standard text and strings, but it won't be able to cast TCHAR types to strings for display. All this code is doing is using the msiEnumProducts function to display the product code (TCHAR ProductCode[40]) at index number (int nIndex). We cycle through these from index number 0 on up in increments of 1 until msiEnumProducts returns ERROR_NO_MORE_ITEMS. ErrResult is set to the value of whatever error code msiEnumProducts gives us.