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.