Wednesday, April 11, 2007

Fix Debugging QIs in ATL Code Under Vista

I think I have whiplash: in the space of a few hours I've gone from writing web services in C# to implementing COM stuff in C++. I don't do much C++ any more, so the adjustment has been somewhat extremely painful.

 

See, I'm trying to implement a new protocol handler to integrate with Windows Search. I have some content in a database, and I want to surface it along with other search results using Windows Desktop Search or the native Vista stuff. In the process, this set of posts has been an excellent resource. In fact, it's nearly the only resource for writing protocol handlers as far as I can tell.

 

While I was reading the posts and (slowly) implementing away, I decided that it would be a good idea to follow the suggestion turn on ATL QueryInterface debugging. Especially given that my code appeared to be doing nothing. So I was a bit annoyed to see output like this in DebugView:

 

[3552] CProtocolHandler
[3552]  -
[3552]  - failed

Those dashes are supposed to have interface names or IDs before them, and they're supposed to tell me what interfaces are being QueryInterfaced for. What's worse is that when I followed the link in the articles and read this, it looked as if the problem was fixed in VS2005, which I'm using. But it obviously wasn't.

 

Soon enough, I was down in the bowels of the ATL code (atlbase.h, no less), trying to figure out what's up. Well, eventually I spotted it: the code change they made to AtlDumpIID to fix the problem in VS2003 is still broken in the case where the registry keys can't be read. In that case, instead of dumping the raw ID, they dump…nothing.

 

I'm guessing that this is a Vista (which I'm using) problem that has to do with security being a lot more locked down. At any rate, if you encounter a similar problem, you'll want to change AtlDumpIID to something like the code below. I'm sure you can do better - this sometimes prints IDs twice, but it was the smallest change I could make to get the effect I wanted.

 

#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
            {
                OutputDebugString(lpszGUID);
            }
  
      }
        else
        {
            OutputDebugString(lpszGUID);
        }
 
   }
    // Attempt to find it in the clsid section
    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
            {
                OutputDebugString(lpszGUID);
            }

 

        }
        else
        {
            OutputDebugString(lpszGUID);
        }

 

    }
    else
        OutputDebugString(lpszGUID);

 

        if (hr != S_OK)
                OutputDebugString(_T(" - failed"));
        OutputDebugString(_T("\n"));
        CoTaskMemFree(pszGUID);

 

        return hr;
}
#endif  // _ATL_DEBUG_INTERFACES || _ATL_DEBUG_QI

 

6 comments:

  1. There's something to be said for having all that code there in your face. ...but it's not very nice, and should probably be left unsaid. :)

    ReplyDelete
  2. Not sure I follow. Obviously the code as written is sub-optimal ;) but then again if I didn't have access to the source I would have been totally screwed.

    ReplyDelete
  3. It was just a flippant throw back to your comment regarding Keith's Template Method code, where you said "there's something to be said for having all the code there in your face."



    I was just teasing, is all. I didn't actually read all that ATL code you posted, I just skimmed it and saw character noise and special-cases. Yuk.



    Sorry for the tangent. :P



    I'm happy to hear you got your Debug.WriteLine working. :)



    Btw, it's not my world, but ATL is just in the process of recovering from 'attributed programming', isn't it? I suppose there is something to be said for having all that code there in your face.

    ReplyDelete
  4. Yes indeed: good ol' printf debugging. The ultimate fallback.



    Heh - my position on attributed code is well-documented.



    :)

    ReplyDelete
  5. Craig, I found your post about BindToStream problem a few months back (https://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1670123&SiteID=1). Eric Wolz responded, but it does not look it was helpful (was it?).



    I *totally* stuck on the BindToStream problem (I pass the IStream back, nothing gets indexed) so I was wondering if you managed to get it working. *Any* kind of help would be appreciated.



    Thanks.



    Sanin



    Ping me at: dotnetguru at gmail dot com

    ReplyDelete
  6. Yeah, sorry about not posting any followup on that. Long story short: forums.microsoft.com is badly broken and they're too clueless to fix it.



    I did eventually get this to work. I had to implement both BindToStream and BindToFilter. BindToFilter returns an IFilter that reports metadata (basically a NOP implementation) and BindToStream returns the actual stream. I had to additionally implement one of the other methods - GetFileName, I think - so that the system would know what type of data I was returning.



    Working with the windows search stuff was probably the hardest thing I've done in years. It sucked.

    ReplyDelete