
Introduction
Using COM technology to pass simple data like long, int, etc is easy.
And what about structured data like C++ classes?
The most developers know the way to pass that.
This way is based on passing data using VARIANT as SAFEARRAY.
And what does VARIANT mean?
The
VARIANT is perhaps the ultimate in general purpose functionality for passing data.
But its range of low-level features can be daunting. I did not find a suitable library for
the easy use of one in my projects.
To resolve this problem I built two classes to provide richer interfaces and easier semantics.
The first class called CDComObj. This class is responsible for Reading/Writing data into/from VARIANT.
The second class, CDcomObjArray, is responsible for passing collection of objects across DCOM.
By using both of these classes it is easy to implement any C++ class which has ability to pass itself across DCOM.
An Example Using the Objects
Client part:
bool CServerAccessPoint::ServerConnect(CConnection& connObj)
{
CComSafeArray conn;
connObj.Write(conn);
HRESULT hr = m_pDcomServer->Connect(&conn);
if (FAILED(hr))
{
return false;
}
return true;
}
Server part:
STDMETHODIMP CStreamingSrv::Connect(VARIANT *pConnection)
{
Lock();
CConnection conn;
conn.Read(pConnection);
m_connections.AddConnection(conn);
Unlock();
return S_OK;
}
Is not easy?
Classes
class CDcomObj : public CSLObject
{
public:
CDcomObj();
virtual ~CDcomObj();
virtual void Clear();
virtual void Copy(const CSLObject& objectSrc);
void Copy(const CDcomObj& objectSrc);
virtual bool IsEqual(const CSLObject& objectSrc);
virtual HRESULT WriteToStream(IStream* pStream);
virtual HRESULT ReadFromStream(IStream* pStream);
virtual long ElementSize();
void Write(VARIANT* pSafeArray);
void Write(CComSafeArray* safeArray);
virtual void Write(CComSafeArray* safeArray,long& index);
void Read(const CLSID& clsid);
void Read(VARIANT* pSafeArray);
void Read(CComSafeArray* safeArray);
virtual void Read(CComSafeArray* safeArray,long& index);
void ReadValue(const CComVariant& srcValue,CComVariant& destValue);
void ReadValue(const CComVariant& srcValue,CComPtr& destValue);
void ReadValue(const CComVariant& srcValue,IUnknown** destValue);
void ReadValue(const CComVariant& srcValue,CString& destValue);
void ReadValue(const CComVariant& srcValue,CComBSTR& destValue);
void ReadValue(const CComVariant& srcValue,LONG& destValue);
void ReadValue(const CComVariant& srcValue,CY& destValue);
void ReadValue(const CComVariant& srcValue,bool& destValue);
void ReadValue(const CComVariant& srcValue,UINT& destValue);
void ReadValue(const CComVariant& srcValue,DWORD& destValue);
void ReadValue(const CComVariant& srcValue,int& destValue);
void ReadValue(const CComVariant& srcValue,double& destValue);
void ReadValue(const CComVariant& srcValue,DATETIME_STRUCT& destValue);
void ProgIDFromCLSID(const CLSID& clsid,CComBSTR& comBSTR);
const CLSID GetCLSID();
const CComBSTR GetLastError();
const bool IsModified() const;
const bool IsModified(UINT value) const;
const UINT GetModified() const;
void AddModified(UINT uModified);
void SetModified(UINT uModified);
void RemoveModified(UINT uModified);
void ShowError(CString lpszError);
public:
CComBSTR m_strCLSID;
CComBSTR m_strProgID;
CComBSTR m_strObjectName;
protected:
UINT m_uModified;
CComBSTR m_bstrError;
};
Using the classes, step by step
The implementation of own DCOM class is very easy.
Step 1: Create your own class derived from CDComObj.
class CConnection : public CDcomObj
Step 2: Redefine the following virtual member functions :
virtual long ElementSize()
virtual void Write(CComSafeArray* safeArray,long& index);
virtual void Read(CComSafeArray* safeArray,long& index);
virtual void Copy(const CSLObject& objectSrc);
virtual void Clear();
The DComObj contains the set of macros. So, redefinitions of mentioned functions is easy
An Example
To demonstrate this technique I built two classes: CConnection and CConnectionArray
class CConnection : public CDcomObj
{
DCL_DCOMOBJ(CConnection)
public:
CConnection();
virtual ~CConnection();
virtual void Clear();
virtual void Copy(const CSLObject& objectSrc);
virtual bool IsEqual(const CSLObject& objectSrc);
virtual long ElementSize();
virtual void Write(CComSafeArray* safeArray,long& index);
virtual void Read(CComSafeArray* safeArray,long& index);
protected:
CString m_strComputerName;
CString m_strApplicationName;
CString m_strUserName;
CString m_strPassword;
CString m_strServerName;
CComBSTR m_strConnectionHandle;
};
Here is the implementation of ElementSize() member function
long CConnection::ElementSize()
{
return 6 + CDcomObj::ElementSize();
}
The implementation of Write and Read member function you can find here.
void CConnection::Write(CComSafeArray* safeArray,long& index)
{
SA_BEGIN_WRITE(CDcomObj);
SA_WRITE(m_strComputerName);
SA_WRITE(m_strUserName);
SA_WRITE(m_strPassword);
SA_WRITE(m_strServerName);
SA_WRITE(m_strApplicationName);
SA_WRITE(m_strConnectionHandle);
}
void CConnection::Read(CComSafeArray* safeArray,long& index)
{
SA_BEGIN_READ(CDcomObj);
SA_READ(m_strComputerName);
SA_READ(m_strUserName);
SA_READ(m_strPassword);
SA_READ(m_strServerName);
SA_READ(m_strApplicationName);
SA_READ(m_strConnectionHandle);
}
For streaming the collection of CConnection objects across DCOM it is enough to define class like here.
class CConnectionArray : public CDcomObjArray
{
DCL_DCOMOBJ_ARRAY(CConnectionArray,CConnection)
public:
CConnectionArray(){}
CConnectionArray( ccIndex aLimit, ccIndex aDelta , bool shouldDelete = true):
CDcomObjArray(aLimit,aDelta ,shouldDelete )
{
}
virtual ~CConnectionArray(){}
};
Notes
To avoid MFC at Server side I am using CString object from WTL V.3.1.
So, you should download one from Microsoft site.
In any case don't forget to setup path to this library in your Compiler: Tools/Options/Directories.
For easy manipulation of SafeArray's, I am using the CComSafeArray class from www.sellsbrothers.com
The Demo project is demonstrated as follows:
- Passing C++ class / collection of classes across COM/DCOM.
- Connection Point Technique
How can you see that?
- Register Server by starting StreamingServer.exe
with option 'RegServer' : StreamingServer.exe -RegServer.
- Start first instance of Client: StreamingClient.exe
- Connect to the server as some user
- Start another one instance of Client
- Connect to server with different user name
- After establishing connection you can see list of connected users in any
Client
- Start another one instance again and list of connected users will
grow
What's going on?
- Under connection stage the client is sending to the server a C++ class
CConnection.
- If the server accepted this request it sends a collection of
CConnection classes
to everything connected clients.