What is it and Why is it!
This is basically a very simple DLL COM server implemented totally in C++ with no support from ATL or MFC and is very easy to write. Writing a COM object this way gave me an insight into the way inproc servers are handled by COM and how Class Factories are created. What I ended up with was a simple framework of classes that can be used to implement fairly basic COM components like Shell Extensions etc, with the whole purpose of publishing this code being to get some feed back from you people....
What to expect
In order to write your own COM object the first thing that we have to do is:
Step One
Write an Interface header file (sample file included in the project) and write methods that you want to implement as shown. A GUID for the object is defined and the interface is given an IID. We need to use this from the client side as shown:
HRESULT hr;
ImyInterface *pmine=(0);
hr = CoCreateInstance(CLSID_Mine,
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(ImyInterface),
(void**)&pmine
);
We can also get the CLSID from the registry using CLSIDFromProgId
passing it the ProgId
of the component.
_declspec(selectany) GUID CLSID_Mine = { 0xdc186800, 0x657f, 0x11d4,
{
0xb0,
0xb5,
0x0,
0x50,
0xba,
0xbf,
0xc9,
0x4
}
};
interface __declspec(uuid("F614FB00-6702-11d4-B0B7-0050BABFC904"))
ImyInterface : public IUnknown
{
STDMETHOD(Square)(long *pVal)PURE;
STDMETHOD(Cube)(long *pVal)PURE;
};
The sample interface just exposes two methods Square
and Cube
both taking a long pointer as parameter.
Step Two
Now we need to provide an implementation of the interface that we have defined, one way to do it is by creating a new class that inherits from interface like:
class CmyInterface : public CComBase<> ,
public InterfaceImpl<ImyInterface>
{
public:
CmyInterface();
virtual ~CmyInterface();
STDMETHOD(QueryInterface)(REFIID riid,LPVOID *ppv);
STDMETHOD(Square)(long *pVal);
STDMETHOD(Cube)(long *pVal);
};
The template class InterfaceImpl<>
provides the implementation of reference counting for the interfaces. Here we can inherit from multiple interfaces so that a single COM component can implement more than one interface.
Step Three
Writing Queryinterface
and the two interface methods is the only thing left before we finish with the object.
STDMETHODIMP CmyInterface::QueryInterface(REFIID riid,LPVOID *ppv)
{
*ppv = NULL;
if(IsEqualIID(riid,IID_IUnknown) || IsEqualIID(riid,__uuidof(ImyInterface)))
{
*ppv = (ImyInterface *) this;
_AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP CmyInterface::Square(long *pVal)
{
long value = *pVal;
*pVal = value * value;
return S_OK;
}
STDMETHODIMP CmyInterface::Cube(long *pVal)
{
long value = *pVal;
*pVal = value * value * value;
return S_OK;
}
Note that, we are using __uuidof(ImyInterface)
to get the interface IID of the interface. This is because we already have associated a uuid
with the interface in step One.
Final Step
COM DLLs have to export a function called DllGetClassObject
. DllGetClassObject
creates a Class Factory for CmyInterface
and returns a reference to it. When we call CoCreateInstance
for an inproc server, COM creates a class factory for the object by calling DllGetClassObject
. The class factory has a method CreateInstance
that creates the object and returns references to it.
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
{
*ppvOut = NULL;
if (IsEqualIID(rclsid, CLSID_Mine))
{
CClassFactory<CmyInterface>
*pcf = new CClassFactory<CmyInterface>;
return pcf->QueryInterface(riid,ppvOut);
}
return CLASS_E_CLASSNOTAVAILABLE;
}
Here we check to see, if the request is for Class Factory of objects identified by CLSID_Mine
and if not, we simply return an error code.
You might be asking where are we creating the actual objects of class CmyInterface
, this infact is handled by the template instantiation of CClassFactory<CmyInterface>
. Here is what the actual implementation of CClassFatory
is:
template<class comObj>
class CSingleCreator
{
protected:
CSingleCreator():m_pObj(0) {};
comObj *CreateObject()
{
if(!m_pObj)
{
m_pObj = new comObj;
}
return m_pObj;
}
comObj * m_pObj;
};
template<class comObj>
class CMultiCreator
{
protected:
CMultiCreator():m_pObj(0) {};
comObj *CreateObject()
{
return new comObj;
}
comObj * m_pObj;
};
class CClassFactory : public CComBase<>,
public InterfaceImpl<IClassFactory>,
public creatorClass
{
public:
CClassFactory() {};
virtual ~CClassFactory() {};
STDMETHOD(QueryInterface)(REFIID riid,LPVOID *ppv)
{
*ppv = NULL;
if(IsEqualIID(riid,IID_IUnknown) || IsEqualIID(riid,IID_IClassFactory))
{
*ppv = (IClassFactory *) this;
_AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid,
LPVOID *ppvObj)
{
*ppvObj = NULL;
if (pUnkOuter)
return CLASS_E_NOAGGREGATION;
m_pObj = CreateObject();
if (!m_pObj)
return E_OUTOFMEMORY;
HRESULT hr = m_pObj->QueryInterface(riid, ppvObj);
if(hr != S_OK)
{
delete m_pObj;
}
return hr;
}
STDMETHODIMP LockServer(BOOL) { return S_OK; }
};
CreateInstance
is called by COM to create an object of the required type, parameter riid
is the IID of the interface that is requested and if the object supports, it increments its reference count and returns a reference to itself.
About the Code
The details in this articles are the very brief and many aspects have been omitted. It just describes the bare details of how to implement a COM server from scratch. Having a look at the code gives you an idea of how the sequence works and if you are looking to write a COM server from scratch this may give you an idea of where to start. You may expect bugs in the code but this is because the sole purpose is to convey the ideas. You can modify it to suit your needs if you like.