The Books
So I have completed reading one ATL book, Beginning ATL Programming by Richard Grimes et al (Wrox 1999) and in the final stages of reading another, Inside ATL by King and Shepherd (MSPress 1999). So it is worth putting up some revision notes so I don't forget all that I have learnt. Whilst the prose of the books was better than the dry Microsoft documentation, the MS website remains the place to link to. To follow these notes you'll need to understand key C++ features such as templates and multiple inheritance. You also need to be very familiar with the COM specification, we shall not here explain the role of IUnknown or IDispatch. We'll limit the focus to in-process DLLs and ignore .EXEsWalkthrough
So I am walking through creating a new ATL project, my project name is ATLProject2. Once through the new ATL project wizard one is confronted with a great many files but don't be intimidated, there are more wizards from the Class View so ensure the Class View is visible.From the Class View go to right-click menu and take Add Class and then take ATL Simple Object which throws the ATL Simple Object Wizard. On the Names Dialog, in the C++ Short Name type "CoolCode" and the other fields are auto-generated for you. Click through the File Type Handler Options to the last dialog, Options which looks like this
So I have checked Support ISupportErrorInfo because we will want to throw rich error information from C++ to VBA. Click Finish and some code is generated for you.
// CCoolCode
class ATL_NO_VTABLE CCoolCode :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CCoolCode, &CLSID_CoolCode>,
public ISupportErrorInfo,
public IDispatchImpl<ICoolCode, &IID_ICoolCode, &LIBID_ATLProject2Lib,
/*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
CCoolCode()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_COOLCODE)
BEGIN_COM_MAP(CCoolCode)
COM_INTERFACE_ENTRY(ICoolCode)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
public:
};
OBJECT_ENTRY_AUTO(__uuidof(CoolCode), CCoolCode)
So it's worth showing the inheritance hierarchy
And we can give information as to what each class in the hierarchy does
ClassName | Purpose |
---|---|
CComObjectRootBase | Holds the reference count member |
CComObjectRootEx | Handles reference counting based on the threading model |
CComSingleThreadModel | Passed as template parameter to CComObjectRootEx. This means reference counting need not be thread safe. |
CComCoClass | Implements IClassFactory with the help of a creator class. |
ISupportErrorInfo | Drives rich error information familiar to VBA devs. |
IDispatchImpl | If you selected a Dual interface (I recommend) then you get an implementation of IDispatch driven off the type library hence the parameters |
CCoolCode | Your class and your logic but never gets directly instantiated |
CComObject<CCoolCode> | What gets instantiated and what implements IUnknown::QueryInterface |
Never new your class
So your class never gets instantiated with the new keyword, it can't because it has no vtable because of the ATL_NO_VTABLE macro. Instead, a creator class such creates an instance of CComObject (when not aggregated) with your class as a template.To illustrate, it is worth looking at what happens when a client gets hold of IClassFactory and calls IClassFactory::CreateInstance, so find the definition of CComCoClass (select, F12) to get to this (abridged) code
template <class T, const CLSID* pclsid = &CLSID_NULL>
class CComCoClass
{
public:
DECLARE_CLASSFACTORY()
DECLARE_AGGREGATABLE(T)
typedef T _CoClass;
...
template <class Q>
static HRESULT CreateInstance(
_Inout_opt_ IUnknown* punkOuter,
_COM_Outptr_ Q** pp)
{
return T::_CreatorClass::CreateInstance(punkOuter, __uuidof(Q),
(void**) pp);
}
template <class Q>
static HRESULT CreateInstance(_COM_Outptr_ Q** pp)
{
return T::_CreatorClass::CreateInstance(NULL, __uuidof(Q),
(void**) pp);
}
};
So in the above code one can see CreateInstance being called in two use cases, (i) where there is an aggregating object and (ii) where there isn't but the code shares a common element of T::_CreatorClass::CreateInstance. It is worth knowing that T::_CreatorClass is defined by the DECLARE_AGGREGATABLE macro which was generated by your choice in the wizard to allow aggregation. This macro is defined as
#define DECLARE_AGGREGATABLE(x) public:\
typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >,
ATL::CComCreator< ATL::CComAggObject< x > > > _CreatorClass;
Wow, that is really a complicated syntax and I won't try to explain it because that would replicate the book/documentation. Suffice to say one can see the CComObject as referred to in the class diagram above. I will give some links though ...
ClassName | Purpose |
---|---|
CComObject | This class implements IUnknown for a nonaggregated object. |
CComAggObject | This class implements the IUnknown interface for an aggregated object. By definition, an aggregated object is contained within an outer object. The CComAggObject class is similar to the CComObject Class, except that it exposes an interface that is directly accessible to external clients. |
CComCreator & CComCreator2 | These are undocumented though referenced in a Don Box article |
If not new then what?
So I mentioned above that one doesn't use new on your class. Let's suppose you have a use case where you have two com classes in your server project and the method on one returns an instance of the other. Without calling the COM API CoCreateInstance (which would be the long way round) how do you create an instance of your com class and return it to a client? The answer is use (some of) the same classes as the class factory above. So here is some sample code
STDMETHODIMP CUncoolCode::CreateCoolCode(ICoolCode ** ppCool)
{
// From Grimes et al (1999) p. 143
*ppCool = NULL;
return CComCreator< CComObject<CCoolCode> >::CreateInstance(
NULL, IID_ICoolCode, reinterpret_cast<void**>(ppCool) ) ;
}
No comments:
Post a Comment