The EX27B Example—A Persistent DLL Component

The EX27B program, which is used by EX27C, is a COM DLL that contains the CText component. This is a simple COM class that implements the IDispatch and IPersistStream interfaces. The IDispatch interface allows access to the component's one and only property, Text, and the IPersistStream interface allows an object to save and load that Text property to and from a structured storage file.

To prepare EX27B, open the \vcpp32\ex27b\ex27b.dsw workspace and build the project. Use regsvr32 or REGCOMP to register the DLL.

Figure 27-4 lists the code for the CText class in Text.h and Text.cpp.

TEXT.H

#ifndef __TEXT_H__
#define __TEXT_H__
// CText command target
class CText : public CCmdTarget
{
private:
    char* m_pchText;

    DECLARE_DYNCREATE(CText)

    CText();           // protected constructor used by dynamic creation

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CText)
    public:
    virtual void OnFinalRelease();
    //}}AFX_VIRTUAL

// Implementation
protected:
    virtual ~CText();
// Generated message map functions

    //{{AFX_MSG(CText)
        // NOTE the ClassWizard will add and remove member functions here.
    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()
    DECLARE_OLECREATE(CText)

    // Generated OLE dispatch map functions
    //{{AFX_DISPATCH(CText)
    afx_msg VARIANT GetText();
    afx_msg void SetText(const VARIANT FAR& newValue);
    //}}AFX_DISPATCH
    DECLARE_DISPATCH_MAP()
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(PersistStream, IPersistStream)
        STDMETHOD(GetClassID)(LPCLSID);
        STDMETHOD(IsDirty)();
        STDMETHOD(Load)(LPSTREAM);
        STDMETHOD(Save)(LPSTREAM, BOOL);
        STDMETHOD(GetSizeMax)(ULARGE_INTEGER FAR*);
    END_INTERFACE_PART(PersistStream)
};

//////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations
//  immediately before the previous line.

#endif // __TEXT_H__

TEXT.CPP

#include "stdafx.h"
#include "ex27b.h"
#include "Text.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] =__FILE__;
#endif
//////////////////////////////////////////////////////////////////////
// CText

IMPLEMENT_DYNCREATE(CText, CCmdTarget)

CText::CText()
{
    EnableAutomation();

    // To keep the application running as long as an OLE automation 
    //  object is active, the constructor calls AfxOleLockApp.

    AfxOleLockApp();
    m_pchText = NULL;
}

CText::~CText()
{
    // To terminate the application when all objects created
    //  with OLE automation, the destructor calls AfxOleUnlockApp.

    if(m_pchText != NULL) {
        delete [] m_pchText;
    }
    AfxOleUnlockApp();
}

void CText::OnFinalRelease()
{
    // When the last reference for an automation object is released,
    //  OnFinalRelease is called. The base class will automatically
    //  delete the object. Add additional cleanup required for your
    //  object before calling the base class.

    CCmdTarget::OnFinalRelease();
}

BEGIN_MESSAGE_MAP(CText, CCmdTarget)
    //{{AFX_MSG_MAP(CText)
        // NOTE - ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()
BEGIN_DISPATCH_MAP(CText, CCmdTarget)
    //{{AFX_DISPATCH_MAP(CText)
    DISP_PROPERTY_EX(CText, "Text", GetText, SetText, VT_VARIANT)
    //}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()

// Note: we add support for IID_IText to support typesafe binding
//  from VBA. This IID must match the GUID that is attached to the 
//  dispinterface in the ODL file.

// {4EBFDD71-5F7D-11D0-848F-00400526305B}
static const IID IID_IText =
{ 0x4ebfdd71, 0x5f7d, 0x11d0, { 0x84, 0x8f, 0x0, 0x40, 0x5, 0x26, 
    0x30, 0x5b } };

BEGIN_INTERFACE_MAP(CText, CCmdTarget)
    INTERFACE_PART(CText, IID_IPersistStream, PersistStream)
    INTERFACE_PART(CText, IID_IText, Dispatch)
END_INTERFACE_MAP()

// {4EBFDD72-5F7D-11D0-848F-00400526305B}
IMPLEMENT_OLECREATE(CText, "Ex27b.Text", 0x4ebfdd72, 0x5f7d, 
    0x11d0, 0x84, 0x8f, 0x0, 0x40, 0x5, 0x26, 0x30, 0x5b)

//////////////////////////////////////////////////////////////////////
// CText message handlers

VARIANT CText::GetText() 
{
    return COleVariant(m_pchText).Detach();
}

void CText::SetText(const VARIANT FAR& newValue) 
{
    CString strTemp;
    ASSERT(newValue.vt == VT_BSTR);
    if(m_pchText != NULL) {
        delete [] m_pchText;
    }
    strTemp = newValue.bstrVal; // converts to narrow chars
    m_pchText = new char[strTemp.GetLength() + 1];
    strcpy(m_pchText, strTemp);
}
//////////////////////////////////////////////////////////////////////

STDMETHODIMP_(ULONG) CText::XPersistStream::AddRef()
{
    METHOD_PROLOGUE(CText, PersistStream)
    return (ULONG) pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) CText::XPersistStream::Release()
{
    METHOD_PROLOGUE(CText, PersistStream)
    return (ULONG) pThis->ExternalRelease();
}

STDMETHODIMP CText::XPersistStream::QueryInterface(REFIID iid,
    void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CText, PersistStream)
    // ExternalQueryInterface looks up IID in the macro-generated tables
    return (HRESULT) pThis->ExternalQueryInterface(&iid, ppvObj);
}
//////////////////////////////////////////////////////////////////////

STDMETHODIMP CText::XPersistStream::GetClassID(LPCLSID lpClassID)
{
    TRACE("Entering CText::XPersistStream::GetClassID\n");
    METHOD_PROLOGUE(CText, PersistStream)
    ASSERT_VALID(pThis);

    *lpClassID = CText::guid;
    return NOERROR;
}

STDMETHODIMP CText::XPersistStream::IsDirty()
{
    TRACE("Entering CText::XPersistStream::IsDirty\n");
    METHOD_PROLOGUE(CText, PersistStream)
    ASSERT_VALID(pThis);

    return NOERROR;
}
STDMETHODIMP CText::XPersistStream::Load(LPSTREAM pStm)
{
    ULONG nLength;
    STATSTG statstg;
    METHOD_PROLOGUE(CText, PersistStream)
    ASSERT_VALID(pThis);
    if(pThis->m_pchText != NULL) {
        delete [] pThis->m_pchText;
    }
    // don't need to free statstg.pwcsName because of NONAME flag
    VERIFY(pStm->Stat(&statstg, STATFLAG_NONAME) == NOERROR);
    int nSize = statstg.cbSize.LowPart; // assume < 4 GB
    if(nSize > 0) {
        pThis->m_pchText = new char[nSize];
        pStm->Read(pThis->m_pchText, nSize, &nLength);
    }
    return NOERROR;
}

STDMETHODIMP CText::XPersistStream::Save(LPSTREAM pStm, BOOL fClearDirty)
{
    METHOD_PROLOGUE(CText, PersistStream)
    ASSERT_VALID(pThis);
    int nSize = strlen(pThis->m_pchText) + 1;
    pStm->Write(pThis->m_pchText, nSize, NULL);
    return NOERROR;
}

STDMETHODIMP CText::XPersistStream::GetSizeMax(ULARGE_INTEGER FAR* pcbSize)
{
    TRACE("Entering CText::XPersistStream::GetSizeMax\n");
    METHOD_PROLOGUE(CText, PersistStream)
    ASSERT_VALID(pThis);
    pcbSize->LowPart = strlen(pThis->m_pchText) + 1;
    pcbSize->HighPart = 0; // assume < 4 GB
    return NOERROR;
}

Figure 27-4. The code listing for the CText class in Text.h and Text.cpp.

ClassWizard generated the CText class as an ordinary Automation component. The IPersistStream interface was added manually. Look carefully at the XPersistStream::Load and XPersistStream::Save functions. The Load function allocates heap memory and then calls IStream::Read to load the contents of the stream. The Save function copies the object's data to the stream by calling IStream::Write.