Debugging and the C++ Renaming Problem
(Today we have another installment of Jeff's Real Microsoft Debugging Stories.)
Recently a minor piece of an IE feature I own stopped working in our latest internal builds, so I debugged it. Issues where something that has worked for a long time and suddenly stopped working are always a special kind of fun, especially when I have never touched the code. The problem was subtle--when I stepped through the code everything appeared to suceed, yet it still did not work. Finally I went line by line comparing the execution path in our alpha code to what we shipped in Windows XP Service Pack 2. For a long time, everything was the same, until I stepped into a call to IConnectionPoint::Advise() and ended up in two different functions on the two different machines. What happened, as Raymond later pointed out, was we had encountered the Renaming Problem, defined in The Annotated C++ Reference Manual, section 10.11c. Consider:
Class CFoo : CConnectionPointImpl<...goo...>,
...,
public ISomeNewInterface,
...
The issue here was one of our classes inherited from a base class that did the dirty work of setting up the connection point. Then, for the next version of the product, someone added another interface (ISomeNewInterface) to CFoo and implemented all the necessary functions. Unfortunately, the new interface also had a method called Advise() that had the exact same footprint as IConnectionPoint's Advise() function. Thus the new implementation was overriding the old implementation.
So, the question is, what do we do about it? Of course there are several immediate solutions. Stroustrup's book gives an example where you have a class that inherits from two different base classes that happen to have a naming collision. He suggests implementing two wrapper classes for the two base classes with different method names that forward to their base class implementation appropriately. In my opinion this is a bit ugly. The easiest solution is to change the name of the method on the new interface, since we have not shipped it yet. I also found that explicitly declaring the new Advise() as being a member of the new interface and not IConnectionPoint in the header file worked, i.e.:
...
// ISomeNewInterface
HRESULT ISomeNewInterface::Advise(IUnknown *, DWORD *);
HRESULT SomeOtherMethod();
...
However, I have not been able to find one way or the other if this is standard and legal C++ syntax or if I am getting lucky with this compiler. Thoughts?
Edit: The MSDN team informs me they are actively tracking a bug that the IConnectionPoint documentation does not work.
(Thank you for joining us. Tune in next week when we talk more about how History works.)
Comments
- Anonymous
April 18, 2005
The comment has been removed - Anonymous
April 18, 2005
The way I've always seen it done is by prefixing the method name with the class name:
CFoo* p = new CFoo;
p->CConnectionPointImpl::Advise(...);
p->ISomeNewInterface::Advise(...); - Anonymous
April 18, 2005
Some of IE and the shell uses ATL, and that is the case with this particular class. Most new code uses smart pointers (CComPtr). Generally it is left to individual developers to decide whether or not something will use ATL. I generally avoid it if possible, but I do like CComPtr.
You cannot cast it at use time. In my example, once I had the IConnectionPoint pointer, calling Advise() would put me in the other interface's implementation because it was overriding the base class. The question is how do we get both into the vtables. - Anonymous
April 18, 2005
Jeff, it's a legal C++ idiom. -- Dejan