Fix debugging QIs in ATL code

I have been doing a lot of VSIP package writing lately. While I was writing a project for VS I wanted to find out which interfaces VS is looking for on my package so I could implement those interfaces. I decided to turn on ATL’s QI tracking feature to make tracking down these interfaces easier. You can turn this feature on with a simple bit of code such as the following in the stdafx.h file:

 

#ifdef _DEBUG

#define _ATL_DEBUG_QI

#endif

 

What will happen is, with this code, every time an object in your project has its QueryInterface method called, it will dump a line of text such as the following into the VS output window:

 

CProjectHier – IUnknown

This means that the method CProjectHier::QueryInterface was called and it was passed an IID of IID_IUnknown (meaning that the caller wants the IUnknown interface). When the QI is called and it cannot supply the requested interface, it dumps out a line of text such as:

CProjectHier - IVsPersistHierarchyItem – failed

Because I have not yet implemented IVsPersistHierarchyItem on the CProjectHier class, ATL kindly tells me that the QI failed. I can then browse the text of the output window, see which interfaces I need to implement, and then implement them. Great! It saves me the time of wading through MSDN!

 

However, there is a bug in this mechanism. Since the bug is in a debugging feature and not in something that will affect production code (you should not ship code with this feature on, hence the #ifdef _DEBUG in the code above), this is not a serious problem. When ATL looks up the interface name of the IID that is passed to QI, it looks in the registry under HKEY_CLASSES_ROOT\Interface\{IID}, and takes the default value under this key as the name of the interface. These values are placed there when you register a typelib with the system; the API call LoadRegTypeLib will look at all the interfaces, coclasses, etc. in a tlb and place the correct entries under HKCR\Interface.

 

Many of the interfaces for the VSIP SDK are not defined in a tlb, they are defined only in .h or a metadata assembly if you are using the Extras SDK, and since they are not in a tlb the IIDs for an interface are never registered. The ATL code has a small bug in it that if the IID for the interface is not registered then it will print something like this for a successful QI:

 

            CProjectHier –

 

or something like this if the QI failed:

 

CProjectHier – - failed

 

As you can see here, the name of the interface is missing. You can easily change the code in ATL to fix this. First, browse to the ATL include directory (“C:\program Files\microsoft visual studio .net 2003\vc7\atlmfc\include” if you used the standard install path) and open the file atlbase.h. Next, search for AtlDumpIID and replace it with the following code:

 

#if defined(_ATL_DEBUG_INTERFACES) || defined(_ATL_DEBUG_QI)

__forceinline HRESULT WINAPI AtlDumpIID(REFIID iid, LPCTSTR pszClassName, HRESULT hr) throw()

{

      USES_CONVERSION_EX;

      CRegKey key;

      TCHAR szName[100];

      DWORD dwType;

      DWORD dw = sizeof(szName);

      LPOLESTR pszGUID = NULL;

      if (FAILED(StringFromCLSID(iid, &pszGUID)))

            return hr;

      OutputDebugString(pszClassName);

      OutputDebugString(_T(" - "));

      LPTSTR lpszGUID = OLE2T_EX(pszGUID, _ATL_SAFE_ALLOCA_DEF_THRESHOLD);

#ifndef _UNICODE

      if(lpszGUID == NULL)

      {

            CoTaskMemFree(pszGUID);

            return hr;

      }

#endif

      // Attempt to find it in the interfaces section

      if (key.Open(HKEY_CLASSES_ROOT, _T("Interface"), KEY_READ) == ERROR_SUCCESS)

      {

            if (key.Open(key, lpszGUID, KEY_READ) == ERROR_SUCCESS)

            {

                  *szName = 0;

                  if (RegQueryValueEx(key.m_hKey, (LPTSTR)NULL, NULL, &dwType, (LPBYTE)szName, &dw) == ERROR_SUCCESS)

                  {

                        OutputDebugString(szName);

                  }

                  else

                  {

                        goto Error;

                  }

            }

            else

            {

                  goto Error;

            }

      }

      // Attempt to find it in the clsid section

      else if (key.Open(HKEY_CLASSES_ROOT, _T("CLSID"), KEY_READ) == ERROR_SUCCESS)

      {

            if (key.Open(key, lpszGUID, KEY_READ) == ERROR_SUCCESS)

            {

                  *szName = 0;

                  if (RegQueryValueEx(key.m_hKey, (LPTSTR)NULL, NULL, &dwType, (LPBYTE)szName, &dw) == ERROR_SUCCESS)

                  {

                        OutputDebugString(_T("(CLSID\?\?\?) "));

                        OutputDebugString(szName);

                  }

                  else

                  {

                        goto Error;

                  }

            }

            else

            {

                  goto Error;

            }

      }

      else

            OutputDebugString(lpszGUID);

      if (hr != S_OK)

            OutputDebugString(_T(" - failed"));

      OutputDebugString(_T("\n"));

      CoTaskMemFree(pszGUID);

      return hr;

Error:

      OutputDebugString(lpszGUID);

      OutputDebugString(_T("\n"));

      return hr;

}

#endif      // _ATL_DEBUG_INTERFACES || _ATL_DEBUG_QI

 

 

Now, when an interface name cannot be found, it will display the IID of the interface in the name’s place. It is still not as convenient as printing the name of the interface, but at least you will get something. You can then search over the header files for that IID to see what interface you need. This is much better than putting a breakpoint on your COM map and watching every single QI, then checking to see if it succeeds or fails.

 

And remember, this code is not just for VSIP (although that is how I found the problem), you can use this code for all projects.