Handling SQL-DMO Events

SQL-DMO

SQL-DMO

Handling SQL-DMO Events

The SQL-DMO Backup, BulkCopy, Replication, Restore, SQLServer, and Transfer objects are connectable COM objects, supporting callback to the client application.

For connectable objects, COM defines the responsibilities for servers and clients. A connectable object exposes the IConnectionPointContainer interface, through which the client obtains the IConnectionPoint interface. The client implements functions to handle callbacks from the server, called a sink. Using the IConnectionPoint interface, the client notifies the server of its ability to handle callbacks, providing its sink implementation as an argument.

The client-implemented sink is a COM object. As with any COM application development task, implementing a sink for any SQL-DMO connectable object is fairly painless when using C++. The client application defines a class, inheriting from a defined SQL-DMO sink interface definition, then implements members to handle the callbacks of interest. The example below illustrates class definition and partial inline implementation for a COM object that can be connected to a SQLServer object instance:

class CSQLServerSink : public ISQLDMOServerSink
{
public:
    CSQLServerSink();

    ~CSQLServerSink()
        { ; }

    // IUnknown interface on all COM objects.
    STDMETHOD(QueryInterface) (THIS_ REFIID riid, LPVOID* ppvObj);

    // AddRef has an inline implementation.
    STDMETHOD_(ULONG, AddRef) (THIS)
        {return (++m_uiRefCount);}

    STDMETHOD_(ULONG, Release) (THIS);


    // Sink properties and methods. Implement CommandSent,
    // ConnectionBroken, QueryTimeout and RemoteLoginFailed as no
    // operation.
    STDMETHOD(CommandSent) (THIS_ SQLDMO_LPCSTR strSQL)
        {return (NOERROR);}

    STDMETHOD(ConnectionBroken) (THIS_ SQLDMO_LPCSTR strMsg,
        LPBOOL pbRetry)
        {return (NOERROR);}

    STDMETHOD(QueryTimeout) (THIS_ SQLDMO_LPCSTR strMsg,
        LPBOOL pbContinue)
        {return (NOERROR);}

    STDMETHOD(RemoteLoginFailed) (THIS_ long lMsgSeverity,
        long lMsgNumber, long MsgState, SQLDMO_LPCSTR strMsg)
        {return (NOERROR);}

    // Code implementing sink method ServerMessage is shown elsewhere.
    STDMETHOD(ServerMessage) (THIS_ long lMsgSeverity, long lMsgNumber,
        long MsgState, SQLDMO_LPCSTR strMsg);

private:
    // Keeping track of ourselves.
    UINT            m_uiRefCount;

    // Used to format status messages from handled ServerMessage event.
    TCHAR           m_acMessage[2048];
};

Implementing the QueryInterface and Release functions is done in standard fashion as:

HRESULT STDMETHODCALLTYPE CSQLServerSink::QueryInterface(
    THIS_ REFIID riid, LPVOID* ppvObj)
    {
    if ((riid == IID_IUnknown) || (riid == IID_IWSQLDMOServerSink))
        {
        AddRef();
        *ppvObj = this;

        return (NOERROR);
        }

    return (E_NOINTERFACE);
    }

and:

ULONG STDMETHODCALLTYPE CSQLServerSink::Release(THIS)
    {
    --m_uiRefCount;

    if (m_uiRefCount == 0)
        delete this;

    return (m_uiRefCount);
    }

Reference counting on COM objects implies a constructor such as the following:

CSQLServerSink::CSQLServerSink()
    {
    m_uiRefCount = 0;
    }

And finally, the implementation of the function handling the ServerMessage callback. The example shows using a message box to display the status messages received by the application:

HRESULT STDMETHODCALLTYPE CSQLServerSink::ServerMessage
    (
    THIS_ long lMsgSeverity,
    long lMsgNumber,
    long MsgState,
    SQLDMO_LPCSTR szMsg
    )
    {
#ifdef UNICODE
    swprintf(m_acMessage, L"%s", szMsg);
#else
    sprintf(m_acMessage, "%S", szMsg);
#endif

    MessageBox(NULL, m_acMessage, _T("SQLServer Status Message"),
        MB_OK | MB_ICONINFORMATION);

    return (NOERROR);
    }

With the class defined and its members implemented, an object instance of the class can be connected to a SQLServer object instance, as shown here:

BOOL CSQLServerHandler::InstallConnectionPoint(
    LPSQLDMOSQLSERVER pSQLServer)
    {
    LPCONNECTIONPOINTCONTAINER  piCPContainer = NULL;
    HRESULT             hr;
    CSQLServerSink*     pSQLServerSink;

    // Create an instance of the SQLServer sink.
    pSQLServerSink = new CSQLServerSink;

    if (pSQLServerSink != NULL)
        {
        hr = pSQLServer->QueryInterface(
            IID_IConnectionPointContainer, (void**) &piCPContainer);

        if (SUCCEEDED(hr))
            {
            // m_pCP is a CSQLServerHandler member variable (a pointer
            // to an IConnectionPoint). The connection point will be
            // used both to advise the SQLServer object of event
            // handling and to terminate event handling later. For that
            // reason, the variable is not local in scope to this
            // function.
            hr = piCPContainer->FindConnectionPoint(
                IID_ISQLDMOServerSink, &m_pCP);

            if (SUCCEEDED(hr))
                m_pCP->Advise(pSQLServerSink, &m_dwCookie);

            piCPContainer->Release();
            }
        }

    // If anything fails, delete the instance of CSQLServerSink that
    // was created. Otherwise, the self-destruct mechanism in
    // CSQLServerSink::Release will handle object destruction.
    if (FAILED(hr))
        {
        hrDisplayError(hr);
        
        delete pSQLServerSink;
        }

    return (SUCCEEDED(hr));
    }

When an application connects to a connectable object, it becomes responsible for breaking that connection when no longer required. An example is shown here:

void CSQLServerHandler::ReleaseConnectionPoint()
    {
    if (m_dwCookie != _BAD_COOKIE)
        m_pCP->Unadvise(m_dwCookie);

    if (m_pCP != NULL)
        {
        m_pCP->Release();
        m_pCP = NULL;
        }
    }

Note  The details of COM connectable object implementation are beyond the scope of this documentation. For more information about COM connectable objects, IConnectionPointContainer, and IConnectionPoint, see a reliable COM/OLE reference.