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 below:
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 it is no longer required. An example is shown below:
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.