FIX: ATL 7.0 control that you build by using Visual Studio .NET 2002 may crash when inserted on a VBA UserForm (815358)



The information in this article applies to:

  • Microsoft Visual Studio .NET (2002), Professional Edition
  • Microsoft Visual Basic for Applications 6.0
  • Microsoft Visual Basic for Applications 5.0

SYMPTOMS

You can use Visual Studio .NET 2002 to build an ActiveX Control with the ActiveX Template Library (ATL). However, these controls crash when they are inserted or resized on a Visual Basic for Applications (VBA) UserForm. The same control may work correctly in other containers, but the control fails in a Microsoft Office application or other VBA-enabled application.

The exact error and exact crash address varies by host product, by operating system, and by the version of VBA.

CAUSE

This problem occurs because of a regression in the code for IQuickActivate::QuickActivate. IQuickActivate::QuickActivate uses the ControlQueryInterface method to get the IConnectionPointContainer interface of the control. This is done to set up the IPropertyNotifySink callback that is passed by the caller in the QACONTAINER structure. In previous versions of ATL, ControlQueryInterface returned the internal interface for the control. In ATL 7.0, however, ControlQueryInterface returns the interface that is obtained from the outer unknown. This behavior occurs if the control is aggregated.

In VBA UserForms (FM20), the control is aggregated with a VBA control extender. As a result, the code for ATL 7.0 obtains the connection point container for the extender and then incorrectly sinks the IPropertyNotifySink to the wrong object. When a property sink notification is raised by the UserForm, the property sink notification is sent to the extender and not the control. The extender treats such notifications as if they came from the control and then notifies the host of the property change. VBA then raises the event again. This results is a recursive loop that eventually leads to a crash when the process runs out of stack space.

RESOLUTION

To resolve this problem, obtain the latest service pack for Microsoft Visual Studio .NET. For more information, click the following article number to view the article in the Microsoft Knowledge Base:

837234 List of bugs that are fixed in Visual Studio .NET 2002 Service Pack 1

WORKAROUND

To work around this problem, you must either override or modify the ATL implementation for IQuickActivate::QuickActivate. Then use _InternalQueryInterface to obtain the connection point for setting up the advise sink on IPropertyNotifySink. The following lines show the corrected code:
 CComPtr<IConnectionPointContainer> pCPC;
 // Do not use ControlQueryInterface for getting the IConnectionPointContainer
 // because it will get the copy of the extender, and not the internal interface you expect.
 // So use _InternalQueryInterface to get the right connection point on this object...
 _InternalQueryInterface(__uuidof(IConnectionPointContainer), (void**)&pCPC);

STATUS

Microsoft has confirmed that this is a bug in the Microsoft products that are listed at the beginning of this article. This problem was first corrected in Visual Studio .NET 2002 Service Pack 1.

MORE INFORMATION

Steps to Reproduce the Problem

  1. Run Visual Studio .NET (2002). Create a new Microsoft Visual C++ project by using the ATL Project type. Name the project ATLFM20Crash. Accept the default settings for the ATL wizard.
  2. On the Project menu, click Add Class and then click ATL Control for the class type. When the wizard appears, name the control with a short name of AtlFm20CrashCtl. Click Finish.
  3. On the Build menu, click Build Solution to make the control.
  4. Run Microsoft Word (or Microsoft Excel). Press ALT+F11 to show the VBA editor. Click Insert and then click UserForm to add a form to the VBA project.
  5. In the Control Toolbox window, right-click and then click Additional Controls from the pop-up menu. Find CAtlFm20CrashCtl Object in the list, click to select it, and then click OK. Add an instance of the control to the form.
  6. Select the control and then resize it. The Office application locks up in a tight loop and then crashes when the application runs out of stack space.
  7. To correct the problem, add the following code to your CAtlFm20CrashCtl class:
    // This overrides the whole QuickActivate method for this control. You can also
    // find the code for CComControlBase::IQuickActivate_QuickActivate in Atlctl.h and
    // modify the line indicated by the comment to address all new controls. However, 
    // a reinstall or an update of the ATL includes files that can undo that change
    // and break the control again. The safest approach is a complete override as 
    // demonstrated here.
    STDMETHOD(QuickActivate)(QACONTAINER* pQACont,QACONTROL* pQACtrl)
    {
        ATLASSERT(pQACont!=NULL);
        ATLASSERT(pQACtrl!=NULL);
        if (!pQACont || !pQACtrl)
            return E_POINTER;
    
        __if_exists(m_clrForeColor)
        {
            m_clrForeColor = pQACont->colorFore;
        }
        __if_exists(m_clrBackColor)
        {
            m_clrBackColor = pQACont->colorBack;
        }
        __if_exists(m_nAppearance)
        {
            m_nAppearance = static_cast<AppearanceType>(pQACont->dwAppearance);
        }
    
        HRESULT hRes = E_FAIL;
        ULONG uCB = pQACtrl->cbSize;
        memset(pQACtrl,0,uCB);
        pQACtrl->cbSize = uCB;
    
        // Get all interfaces you are going to require.
        CComPtr<IOleObject> pOO;
        ControlQueryInterface(__uuidof(IOleObject), (void**)&pOO);
        CComPtr<IViewObjectEx> pVOEX;
        ControlQueryInterface(__uuidof(IViewObjectEx), (void**)&pVOEX);
        CComPtr<IPointerInactive> pPI;
        ControlQueryInterface(__uuidof(IPointerInactive), (void**)&pPI);
        CComPtr<IProvideClassInfo2> pPCI;
        ControlQueryInterface(__uuidof(IProvideClassInfo2), (void**)&pPCI);
    
        if (pOO == NULL || pVOEX == NULL)
            return E_FAIL;
    
        pOO->SetClientSite(pQACont->pClientSite);
    
        if (pQACont->pAdviseSink != NULL)
        {
            ATLTRACE(atlTraceControls,2,_T("Setting up IOleObject Advise\n"));
            pVOEX->SetAdvise(DVASPECT_CONTENT, 0, pQACont->pAdviseSink);
        }
    
        CComPtr<IConnectionPointContainer> pCPC;
        // Do not use ControlQueryInterface to get the IConnectionPointContainer
        // because it will get the copy of the extender and not the internal interface you expect.
        // Use _InternalQueryInterface to get the right connection point on this object...
        _InternalQueryInterface(__uuidof(IConnectionPointContainer), (void**)&pCPC);
    
        if (pQACont->pPropertyNotifySink)
        {
            ATLTRACE(atlTraceControls,2,_T("Setting up PropNotify CP\n"));
            CComPtr<IConnectionPoint> pCP;
            if (pCPC != NULL)
            {
                hRes = pCPC->FindConnectionPoint(__uuidof(IPropertyNotifySink), &pCP);
                if (SUCCEEDED(hRes))
                    pCP->Advise(pQACont->pPropertyNotifySink, &pQACtrl->dwPropNotifyCookie);
            }
        }
    
        if (pPCI)
        {
            GUID iidDefaultSrc;
            if (SUCCEEDED(pPCI->GetGUID(GUIDKIND_DEFAULT_SOURCE_DISP_IID,
                &iidDefaultSrc)))
            {
                if (pQACont->pUnkEventSink)
                {
                    ATLTRACE(atlTraceControls,2,_T("Setting up Default Out Going Interface\n"));
                    CComPtr<IConnectionPoint> pCP;
                    if (pCPC != NULL)
                    {
                        hRes = pCPC->FindConnectionPoint(iidDefaultSrc, &pCP);
                        if (SUCCEEDED(hRes))
                            pCP->Advise(pQACont->pUnkEventSink, &pQACtrl->dwEventCookie);
                    }
                }
            }
        }
        // Give information to container.
        if (pOO != NULL)
            pOO->GetMiscStatus(DVASPECT_CONTENT, &pQACtrl->dwMiscStatus);
    
        if (pVOEX != NULL)
            pVOEX->GetViewStatus(&pQACtrl->dwViewStatus);
    
        if (pPI != NULL)
            pPI->GetActivationPolicy(&pQACtrl->dwPointerActivationPolicy);
    
        return S_OK;
    }
  8. Recompile the control and then test the code again. This modified code avoids the problem. The control runs as expected.

Modification Type:MajorLast Reviewed:8/9/2005
Keywords:kbvs2002sp1fix kbvs2002sp1sweep kbbug KB815358 kbAudDeveloper