English 中文(简体)
如果调用了Bind,则当窗体关闭时,C++Builder-IAutoComplete实现会导致崩溃
原标题:C++Builder - IAutoComplete implementation causes crash when the Form is closed if Bind called
  • 时间:2023-08-02 03:34:31
  •  标签:
  • c++builder

如果在CodeProject,我将其改编为C++Builder。当它工作时,如果我调用了Bind()函数,它将在表单关闭时崩溃。如果我不释放对象并让它泄漏资源,也不会崩溃。

这是一个类(注意:还没有测试注册表内容,也没有计划使用它):

//--------------------------------------------------------------------------------------------
//  Name:           CCustomAutoComplete (CCUSTOMAUTOCOMPLETE.H)
//  Type:           Wrapper class
//  Description:    Matches IAutoComplete, IEnumString and the registry (optional) to provide
//                  custom auto-complete functionality for EDIT controls - including those in
//                  combo boxes - in WTL projects.
//
//  Author:         Klaus H. Probst [kprobst@vbbox.com]
//  URL:            http://www.vbbox.com/
//  Copyright:      This work is copyright © 2002, Klaus H. Probst
//  Usage:          You may use this code as you see fit, provided that you assume all
//                  responsibilities for doing so.
//  Distribution:   Distribute freely as long as you maintain this notice as part of the
//                  file header.
//
//
//  Updates:
//
//
//  Notes:
//
//
//  Dependencies:
//
//                  The usual ATL/WTL headers for a normal EXE, plus <atlmisc.h>
//
//--------------------------------------------------------------------------------------------

#if !defined(CCustomAutoComplete_INCLUDED)
#define CCustomAutoComplete_INCLUDED

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include <System.Classes.hpp>
#include <Registry.hpp>
#include <string.h>

// Bring in the GUID hack and the shell stuff
#include <initguid.h>
#include <shldisp.h>
#include <shlguid.h>

class CCustomAutoComplete : public IEnumString
{

private:

    TStringList *m_pasList;
    IAutoComplete *m_pac;
    TRegistry* m_reg;

    ULONG m_nCurrentElement;
    ULONG m_nRefCount;
    BOOL m_fBound;


public:


    // Constructors/destructors

    CCustomAutoComplete()
    {
        InternalInit();

    }

    CCustomAutoComplete(const HKEY p_hRootKey, const String& p_sSubKey)
    {
        InternalInit();

        SetStorageSubkey(p_hRootKey, p_sSubKey);

    }

    CCustomAutoComplete(TStringList* p_sItemList)
    {
        InternalInit();

        SetList(p_sItemList);
    }


    ~CCustomAutoComplete()
    {
        if (m_reg)
            delete m_reg;

        if (m_pac)
            m_pac->Release();

        if (m_pasList)
            delete m_pasList;
    }

    BOOL ListExists(bool create=false)
    {
        if (m_pasList)
            return TRUE;

        if (!create)
            return FALSE;

        return SetList(NULL);
    }

public:

    // Implementation

    BOOL SetList(TStringList* p_sItemList)
    {
        Clear();

        if (m_pasList)
            delete m_pasList;

        try {
            m_pasList=new TStringList;
            if (p_sItemList) {
                m_pasList->Assign(p_sItemList);
            }
        }
        catch(...) {
            return FALSE;
        }

        return TRUE;
    }

    BOOL SetStorageSubkey(LPCTSTR p_lpszSubKey, HKEY p_hRootKey = HKEY_CURRENT_USER)
    {
        return SetStorageSubkey(p_hRootKey, String(p_lpszSubKey));
    }

    BOOL SetStorageSubkey(HKEY p_hRootKey, const String& p_sSubKey)
    {
        assert(p_hRootKey);
        assert(p_sSubKey.Length());

        if (InitStorage(p_hRootKey, p_sSubKey))
            return LoadFromStorage();

        return FALSE;
    }

    BOOL Bind(HWND p_hWndEdit, DWORD p_dwOptions = 0, LPCTSTR p_lpszFormatString = NULL)
    {
        assert(::IsWindow(p_hWndEdit));

        if ((m_fBound) || (m_pac))
            return FALSE;


        HRESULT hr = S_OK;

        if (SUCCEEDED(CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_IAutoComplete, (void**)&m_pac))) {
            if (p_dwOptions) {
                IAutoComplete2 *pac2;
                if (SUCCEEDED(m_pac->QueryInterface(IID_IAutoComplete2, (void**)&pac2))) {
                    hr = pac2->SetOptions(p_dwOptions);
                    pac2->Release();
                }
            }

            hr = m_pac->Init(p_hWndEdit, this, NULL, p_lpszFormatString);

            if (SUCCEEDED(hr)) {
                m_fBound = TRUE;
                return TRUE;
            }

            m_pac->Release();
            m_pac=NULL;
        }

        return FALSE;
    }

    VOID Unbind()
    {
        if (!m_fBound)
            return;

        assert(m_pac);

        if (m_pac) {
            m_pac->Release();
            m_pac=NULL;
            m_fBound = FALSE;
        }
    }

    BOOL AddItem(String& p_sItem)
    {
        if (ListExists(true)) {
            if (p_sItem.Length() != 0) {
                if (m_pasList->IndexOf(p_sItem) == -1) {
                    m_pasList->Add(p_sItem);

                    if (m_reg)
                        return AddToStorage(p_sItem);

                        return TRUE;
                }
            }
        }

        return FALSE;
    }

    BOOL AddItem(LPCTSTR p_lpszItem)
    {
        String s=p_lpszItem;
        return AddItem(s);
    }

    INT GetItemCount()
    {
        if (m_pasList) {
            return m_pasList->Count;
        }
        return 0;
    }

    BOOL SetRecentItem(const String& p_sItem)
    {

        // Only supported if registry persistance is enabled.
        // Call this function after setting the registry root
        if ((!m_reg) || (!m_fBound))
            return FALSE;


        return WriteRecentItem(p_sItem);
    }

    BOOL GetRecentItem(String& p_sItem)
    {
        // Only supported if registry persistance is enabled.
        // Call this function after setting the registry root
        if ((!m_reg) || (!m_fBound))
            return FALSE;

        return ReadRecentItem(p_sItem);
    }

    BOOL RemoveItem(String& p_sItem)
    {
        if (m_pasList && p_sItem.Length() != 0) {
            int i;
            if ((i=m_pasList->IndexOf(p_sItem)) != -1) {
                m_pasList->Delete(i);
                if (m_reg)
                    return RemoveFromStorage(p_sItem);
            }
        }

        return FALSE;
    }

    BOOL RemoveItem(LPCTSTR p_lpszItem)
    {
        String s=p_lpszItem;
        return RemoveItem(s);
    }


    BOOL Clear()
    {
        if (m_pasList && m_pasList->Count != 0) {
            if (m_reg) {
                if (!ClearStorage())
                    return FALSE;
            }

            m_pasList->Clear();

            return TRUE;
        }

        return FALSE;
    }

    BOOL Disable()
    {
        if ((!m_pac) || (!m_fBound))
            return FALSE;

        return SUCCEEDED(EnDisable(FALSE));

    }

    BOOL Enable(VOID)
    {

        if ((!m_pac) || (m_fBound))
            return FALSE;

        return SUCCEEDED(EnDisable(TRUE));

    }

public:

    //
    //  IUnknown implementation
    //
    STDMETHODIMP_(ULONG) AddRef()
    {
        return ::InterlockedIncrement(reinterpret_cast<LONG*>(&m_nRefCount));
    }

    STDMETHODIMP_(ULONG) Release()
    {
        ULONG nCount = 0;
        nCount = (ULONG) ::InterlockedDecrement(reinterpret_cast<LONG*>(&m_nRefCount));

        if (nCount == 0)
            delete this;

        return nCount;

    }

    STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)
    {

        HRESULT hr = E_NOINTERFACE;

        if (ppvObject != NULL)
        {
            *ppvObject = NULL;

            if (IID_IUnknown == riid)
                *ppvObject = static_cast<IUnknown*>(this);

            if (IID_IEnumString == riid)
                *ppvObject = static_cast<IEnumString*>(this);

            if (*ppvObject != NULL)
            {
                hr = S_OK;
                ((LPUNKNOWN)*ppvObject)->AddRef();
            }

        }
        else
        {
            hr = E_POINTER;
        }

        return hr;

    }

public:

    //
    //  IEnumString implementation
    //
    STDMETHODIMP Next(ULONG celt, LPOLESTR* rgelt, ULONG* pceltFetched)
    {

        HRESULT hr = S_FALSE;

        if (!celt)
            celt = 1;

        ULONG i;
        for (i = 0; i < celt; i++)
        {

            if (m_nCurrentElement >= GetItemCount())
                break;

            rgelt[i] = (LPWSTR)::CoTaskMemAlloc((ULONG) sizeof(WCHAR) * (m_pasList->Strings[m_nCurrentElement].Length() + 1));
            wcscpy(rgelt[i], m_pasList->Strings[m_nCurrentElement].c_str());

            if (pceltFetched)
                (*pceltFetched)++;

            m_nCurrentElement++;
        }

        if (i == celt)
            hr = S_OK;

        return hr;
    }

    STDMETHODIMP Skip(ULONG celt)
    {

        m_nCurrentElement += celt;

        if (m_nCurrentElement > GetItemCount())
            m_nCurrentElement = 0;

        return S_OK;
    }

    STDMETHODIMP Reset(void)
    {

        m_nCurrentElement = 0;
        return S_OK;
    }

    STDMETHODIMP Clone(IEnumString** ppenum)
    {

        if (!ppenum)
            return E_POINTER;

        try {
            CCustomAutoComplete* pnew = new CCustomAutoComplete();

            if (m_pasList) {
                pnew->SetList(m_pasList);
            }
            pnew->AddRef();
            *ppenum = pnew;

            return S_OK;
        }
        catch(...) { };
        return S_FALSE;

    }

private:

    // Internal implementation

    void InternalInit()
    {
        m_pac=NULL;
        m_nCurrentElement = 0;
        m_nRefCount = 0;
        m_fBound = FALSE;
        m_reg = NULL;
        m_pasList = NULL;
    }

    HRESULT EnDisable(BOOL p_fEnable)
    {

        HRESULT hr = S_OK;

        assert(m_pac);

        hr = m_pac->Enable(p_fEnable);

        if (SUCCEEDED(hr))
            m_fBound = p_fEnable;

        return hr;


    }

    BOOL InitStorage(HKEY p_hRootKey, const String& p_sSubKey)
    {
        if (m_reg)
        {
            delete m_reg;
            m_reg=NULL;
        }

        m_reg = new TRegistry(KEY_READ|KEY_WRITE);
        if (m_reg) {
            if (m_reg->OpenKey(p_sSubKey, true)) {
                return TRUE;
            }
            delete m_reg;
            m_reg=NULL;
        }

        return FALSE;
    }


    BOOL RemoveFromStorage(const String& p_sItem)
    {
        assert(m_reg);

        BOOL result=FALSE;
        try {
            result=m_reg->DeleteValue(p_sItem);
        }
        catch(...) { };

        return result;
    }


    BOOL LoadFromStorage()
    {
        assert(m_reg);

        if (GetItemCount() != 0)
            m_pasList->Clear();

        BOOL result=TRUE;

        try {
            TStringList *values=new TStringList;
            m_reg->GetValueNames(values);

            for (UINT i=0;i<values->Count;i++) {
                try {
                    String s=m_reg->ReadString(values->Strings[i]);
                    if (!s.IsEmpty()) {
                        if (!m_pasList->Add(s)) {
                            result=FALSE;
                        }
                    }
                }
                catch(...) {
                    result=FALSE;
                };
            }

            delete values;
        }
        catch (...) {
            result=FALSE;
        }

        return result;
    }

    BOOL ClearStorage()
    {

        // Since we do not hold the parent key to our
        // own HKEY, we need to iterate through the
        // array and delete each one.

        if (m_pasList) {
            for (int i = 0; i < m_pasList->Count; i++)
            {
                if (!RemoveFromStorage(m_pasList->Strings[i]))
                    return FALSE;
            }
        }

        return TRUE;

    }


    BOOL AddToStorage(const String& p_sName)
    {

        assert(m_reg);

        try {
            m_reg->WriteString(p_sName, p_sName);
            return TRUE;
        }
        catch(...) {};

        return FALSE;
    }


    BOOL WriteRecentItem(const String& p_sItem)
    {
        assert(m_reg);

        try {
            m_reg->WriteString(_D("_Recent"), p_sItem);
            return TRUE;
        }
        catch(...) {};

        return FALSE;
    }

    BOOL ReadRecentItem(String& p_sItem)
    {

        assert(m_reg);

        try {
            p_sItem=m_reg->ReadString(_D("_Recent"));
            return TRUE;
        }
        catch (...) {};

        return FALSE;
    }
};

#endif // !defined(CCustomAutoComplete_INCLUDED)

我是这样使用它的:

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    try {
        m_pAutoComplete=new CCustomAutoComplete();
        m_pAutoComplete->Bind(ButtonedEdit1->Handle, ACO_AUTOSUGGEST);

        TStringList* sl=new TStringList;
        sl->Add(_D("Test@xyz.com"));
        sl->Add(_D("Tester@yahoo.com"));
        sl->Add(_D("darpa@hotmail.com"));

        m_pAutoComplete->SetList(sl);
        delete sl;
    }
    catch(...) { };
}


void __fastcall TForm1::FormDestroy(TObject *Sender)
{
    // Note: I tried putting this in the ~TForm1 destructor as well.
    if (m_pAutoComplete) {
        if (m_pAutoComplete->Release()) {
            m_pAutoComplete=NULL;
        }
    }

}

怎么了?

问题回答

您在m_pAutoComplete变量中持有对CCustomAutoComplete对象的引用,但您没有增加对象的引用计数,因此当调用Bind()时,它仍处于初始值0。

然后,Bind()this传递给m_pac->;Init(),这将使CCustomAutoComplete对象的引用计数增加到1,但实际上有2个引用。

稍后,当清理时,CCustomAutoComplete对象的引用计数会过早地降至0,从而破坏该对象,而m_pAutoComplete变量仍在引用该对象。当您尝试Release()对象时,您的代码就会崩溃。

要解决此问题,需要在将对象引用分配给m_pAutoComplete变量后调用对象的AddRef()方法,例如:

m_pAutoComplete = new CCustomAutoComplete();
m_pAutoComplete->AddRef(); // <-- ADD THIS!
...

AddRef()Release()需要平衡AddRef递增引用计数,Release()递减引用计数。

一个更好的选择是首先停止使用原始接口指针和手动引用计数。请使用智能接口包装类来处理这些详细信息,例如CComPtrTComInterfaceDelphiInterface

除了Remy的答案外,还按如下方式更改FormDestory()以确保对象被释放:

void __fastcall TForm1::FormDestroy(TObject *Sender)
{
    if (m_pAutoComplete) {
        m_pAutoComplete->Unbind();
        m_pAutoComplete->Release();
        m_pAutoComplete=NULL;
    }
}

将析构函数移到private也是一个好主意,以确保必须使用Release()





相关问题
Borland Assertion failed in local_unwind()

I have a comms server that is supposed to run for an indefinite amount of time. However, it sometimes errors with Assertion failed: !"bogus context in Local_unwind()", file xx.cpp, line 2262 ...

Install a .bpk into Borland C++ Builder from the command line

I am attempting to install a .bpk package into the Borland C++ Builder 5 IDE from the command line. I am sure that this is possible, as we have some third party components that manage to do so, but I ...

How can I add libCurl to a Borland C++ Builder 6 Project?

How can I add libCurl to a Borland C++ Builder 6 Project? I tried including its directory in the project s compiler and linker search paths, then I made a lib from the libcurl dll and I added it to ...