The Microsoft Active Template Library (ATL) is a set of template-based C++ classes that you can use to create small, fast Component Object Model (COM) objects. OSAX supports ATL as a way to create COM interfaces for persistent objects stored in ObjectStore. The ATL approach is an alternative to the approach described in Chapter 2, Building OSAX Object Servers, which is based on a type description file. Using ATL requires more C++ expertise, but ATL provides more flexibility for customizing COM interfaces.
When using OSAX with the ATL interfaces, you define COM interfaces using normal ATL techniques, classes, and interfaces. You then connect them to ObjectStore objects using template classes provided by the ActiveX interface. The resulting control is linked with the ObjectStore and OSAX run-time libraries.
OSAX and ATL Concepts
The following sections define the terms and concepts used in this chapter. Terminology
The following terms are used to describe OSAX and ATL:
An OSAX object server is the same as a standard ATL object server, except that a different module class is used, additional header files are needed, and there is an additional library. An OSAX object server may be a DLL file, an .exe file, or an NT service.
STDMETHODIMP CAuthor::get_Name(IOSAXString * * pVal) { OSAX_BEGIN_METHOD(IID_IAuthor) // method body OSAX_TRANSLATE_EXCEPTIONS OSAX_END_METHOD }
For a client, an OSAX instance container is like an OSAX instance, except that the reference to the C++ object can be changed.
OSAX instances and instance containers are both defined by a single OSAX class. If the OSAX class supports a class factory, then the class factory will create instance containers. In addition, the normal ATL methods, such as CreateObject, can be used to allocate instance containers. OSAX methods call the internal SetDataImpl method to change the C++ object being referenced. SetDataImpl will fail if called on an instance instead of an instance container.
The following illustrates OSAX instances:
An OSAX instance class is an object that supports constructors and methods applicable to the entire C++ class, rather than to specific objects of the class. The OSAX class for the OSAX instance includes the public template base CInstanceImpl and adds the interface IOSAXInstanceInformation, in addition to the standard additional OSAX base classes and interfaces. Instance implementations access the referenced C++ object through a member called data. data behaves like a pointer to the referenced C++ object. The public nested class CDataPtr implements data. The CDataPtr has a number of useful constructors that let it be used to access the referenced C++ object from the methods of other OSAX objects, and to obtain OSAX instances for C++ pointers.
Implementing an Object Server with ATL
The following procedure implements an OSAX object server directly in ATL:
Step 1. Use the ATL COM AppWizard.
Use the ATL COM AppWizard to create the skeleton ATL project. In the example for this procedure, the server is implemented as a dynamic link library (DLL). Step 2. Make project-level changes.
For each configuration you build, make the following changes:
In the stdafx.h file, make these changes:
In books.cpp, change:
CComModule _Module;to:
OSAX::COSAXModule _Module;Click on File|Save All to save the changes to your project.
For the top-level object, we use the name OSAXBooks and a ProgID of OSAX.ATLBooks. We use slightly different interfaces from those used in the type description version of the Books example. Under attributes, we select a threading model of Both and support ISupportErrorInfo. We won't support aggregation, and the dual interface is optional. We use the same settings for the other objects.
COM_INTERFACE_ENTRY(IOSAXContextInformation)to the COM_MAP section of the class definition.
Every ATL class that implements an OSAX instance class needs to include the template base class CInstanceClassImpl. Since we are using one ATL class to implement each instance and instance class, each of these classes will require CInstanceClassImpl. It takes two template arguments, the ATL class that implements the instance class, and the ATL class that implements the instance. In the Books example, these are the same.
The template base class implements the interface IOSAXType, so you must add:
COM_INTERFACE_ENTRY(IOSAXType)to the COM_MAP section of your classes. Also, because IOSAXType is a dispatch interface, you must modify the entry for IDispatch to tell it which IDispatch interface to use for your class, by using:
COM_INTERFACE_ENTRY2(IDispatch,IMyClass)where IMyClass is the dispatch interface you want to serve as the default IDispatch interface.
Each instance class should add:
OSAX_CLASS_INTERFACE(IInstanceClass)to its class definition. The area above the COM_MAP is a good place to add macros like this. The macro defines an accessor for the instance class.
Add:
COM_INTERFACE_ENTRY(IOSAXInstanceInformation)to the COM_MAP section of your class.
Each instance class should also add:
OSAX_TYPE_NAME("C++ type")This defines an accessor the name of the type, which can be used to obtain an os_typespec for persistent allocation. Use of OSAX_TYPE_NAME is optional.
Every ATL class that implements an OSAX instance needs to include the template base class CInstanceImpl, which takes three arguments:
Each instance class should add the following to its definition:
OSAX_INTERFACE_CONVERSION(IInstance)The part of the class definition near the COM_MAP is a good place for this. OSAX_INTERFACE_CONVERSION defines a conversion from the ATL class to the specified interface. This conversion is used when writing methods.
The ATL class definitions for your instance classes should add:
DECLARE_GET_CONTROLLING_UNKNOWN()You can add it just before the COM_MAP section.
Finally, you must include the header files for your C++ classes since the ATL instance classes need to see the types. In this example, the file persistent.h was added to the project.
Add the following line to the .idl file:
import "osaxint.idl";
The Name method will access the char* value of the data associated with the IAuthor interface pointer. The method's implementation must first access the C++ Author*, then get the char* name, get an IOSAXString* for the char*, and return an appropriate HRESULT. At the same time, any exceptions from ObjectStore must be translated to COM exceptions, and the ObjectStore fault handler must be enabled.
OSAX provides three macros to perform fault-handling and exception translation. At the beginning of your method you use OSAX_BEGIN_METHOD, which needs the interface IID as an argument. Your method body follows, and then you must provide the macros OSAX_TRANSLATE_EXCEPTIONS and OSAX_END_METHOD. OSAX_END_METHOD will return an appropriate HRESULT, so you do not need to do anything special to return a value from the method. The method looks like this:
STDMETHODIMP CAuthor::get_Name(IOSAXString * * pVal) { OSAX_BEGIN_METHOD(IID_IAuthor) OSAX_TRANSLATE_EXCEPTIONS OSAX_END_METHOD }OSAX provides a member variable called data that is a smart pointer that acts like a pointer to the data referenced by the OSAX object. To access the m_strName member of the object, all you need to write is:
data->m_strNameThe OSAX instance class implementation provides useful methods for obtaining OSAX instances for C++ values. The OSAX_INTERFACE_CONVERSION macro defines a conversion function from the ATL class to an interface pointer. C++ constructors and conversion operators permit the conversion function to work directly on the C++ object to be referenced by the OSAX instance. The class OSAX::COSAXString implements IOSAXString, and its GetInterfacePtr member takes a char* and an IOSAXString** as arguments and returns an HRESULT. If it succeeds, the contents of the IOSAXString** argument will be set to the OSAX instance for the char* pointer. The complete get_Name method looks like:
STDMETHODIMP CAuthor::get_Name(IOSAXString * * pVal) { OSAX_BEGIN_METHOD(IID_IAuthor) return COSAXString::GetInterfacePtr(data->m_strName, pVal); OSAX_TRANSLATE_EXCEPTIONS OSAX_END_METHOD }The methods for CAuthor, CBook, and CBookElt are all similar.
The OSAX_CLASS_INTERFACE macro defines the accessor GetClassInterfacePtr, so the method just needs to call it (the object server in the Books example exposes only the BookElt instance class):
STDMETHODIMP COSAXBooks::get_BookEltClass(IBookElt * * pVal) { OSAX_BEGIN_METHOD(IID_IOSAXATLBooks) return CBookElt::GetClassInterfacePtr(pVal); OSAX_TRANSLATE_EXCEPTIONS OSAX_END_METHOD }The top-level server object needs a method to return an IOSAXObjectStore interface pointer. OSAX provides a function for this, so the method is defined as:
STDMETHODIMP COSAXBooks::get_ObjectStore(IOSAXObjectStore * * pVal) { OSAX_BEGIN_METHOD(IID_IOSAXATLBooks) return GetOSAXObjectStore(pVal); OSAX_TRANSLATE_EXCEPTIONS OSAX_END_METHOD }In the file stdafx.h, add:
#include <ostore/ostore.hh>just before the include directive for osax.h to enable ObjectStore-specific definitions in osax.h.
In the header file persistent.h, add:
static os_typespec* get_os_typespec()to each of the C++ classes.
Finally, add a custom build rule for ossg. To add the custom build rule, add a text file to the project. In this example, the name of the text file is schema.scm. Under Project Settings, set the settings for All Configurations, and select the file schema.scm. Add the following ossg line:
ossg -asdb $(OutDir)\schema.adb -asof $(IntDir)\schema.obj $(InputDir)\$(InputPath) /I $(InputDir) /MD /D_DLL /DWIN32For the targets, use:
$(IntDir)\schema.obj $(OutDir)\schema.adbThe schema file lists the C++ types in your database. If you want to allow more than one OSAX object server to be used at a time, mark the schema file as a DLL schema by including
OS_SCHEMA_DLL_ID(id)where id can be in either of the following:
The server will have a top-level object method that creates an empty book list, represented by a BookElt that has a NULL Book and a NULL next element in the list.
The CinstanceImpl template base class used for OSAX instances defines a nested base class called CDataPtr. The CDataPtr class is the class of the smart pointer called data. It has constructors that will initialized it from variants, C++ pointers, and interface pointers. In this example, we have an IOSAXDatabase* interface pointer, and need an os_database*. COSDatabase is the class that implements IOSAXDatabase for ObjectStore databases, so we can use the CDataptr to get a smart pointer and the smart pointer can be converted to an os_database*, as follows:
os_database* pDatabase = COSDatabase::CDataPtr(pDb);The complete method is:
STDMETHODIMP COSAXBooks::CreateBookList(IOSAXDatabase * pDb, BSTR bstrName, IBookElt * * ppBookElt) { USES_CONVERSION; OSAX_BEGIN_METHOD(IID_IOSAXATLBooks) os_database* pDatabase = COSDatabase::CDataPtr(pDb); os_database_root* pRoot = pDatabase->create_root(OLE2A(bstrName)); BookElt* pBookElt = new(pDatabase,BookElt::get_os_typespec()) BookElt; pRoot->set_value(pBookElt,BookElt::get_os_typespec()); return CBookElt::GetInterfacePtr(pBookElt,ppBookElt); OSAX_TRANSLATE_EXCEPTIONS OSAX_END_METHOD }OSAX provides special overloadings of the new operator that can be used with IUnknown and VARIANT placement arguments. The object server in the Books example exposes a method for creating a new Author in a segment or database, passed in an optional VARIANT pointer.
The method definition is:
STDMETHODIMP COSAXBooks::CreateAuthor(BSTR bstrName, VARIANT where, IAuthor * * ppAuthor) { USES_CONVERSION; OSAX_BEGIN_METHOD(IID_IOSAXATLBooks) // Copy the ANSI version of the name to its permanent location char* strNameIn = OLE2A(bstrName); ULONG nLengthName = strlen(strNameIn)+1; char* strNameAuthor = new(where, COSAXString::InstanceClass(), nLengthName) char[nLengthName]; strcpy(strNameAuthor,strNameIn); // Allocate a new Author Author* pAuthor = new(where,CAuthor::InstanceClass()) Author(strNameAuthor); return CAuthor::GetInterfacePtr(pAuthor,ppAuthor); OSAX_TRANSLATE_EXCEPTIONS OSAX_END_METHOD }
In the following example, Aut is an uninitialized Author OSAX instance. The Storage property specifies where the persistent state is allocated.
Dim Aut As New Author Set Aut.Storage = MyDatabase Aut.Name = "Samuel Clemens"You can also use other approaches. In the following example, an OSAX instance is initialized to another OSAX instance:
Dim Aut1 As New Author Aut1 = AutNotice that it is not
Set Aut1 = Autwhich would change the value of the variable Aut1. Here, Author has a default value setter that is defined to set the reference.
A constructor style can be used as follows:
Dim Aut As New Author Aut.Create(MyDatabase,"Samuel Clemens"An alternative approach is to have a separate OSAX class object:
Dim Aut As Author Set Aut = AuthorClass.Create(MyDatabase,"Samuel Clemens")
Dim Aut As Author Dim Aut1 As New Author 'Set Aut variable to the returned object Set Aut = Writers.GetAuthor("Elbert Hubbard") 'Set C++ reference of Aut1 to the returned object Aut1 = Writers.GetAuthor("Nicolas Bourbaki")Why would you want to use one approach instead of another? If you are using the Microsoft Transaction Server (MTS), your objects may be under its control. You can initialize them by setting their C++ references to another OSAX instance, similar to the way you initialize other objects by loading them from a file.
API Reference
OSAX Methods
The general format of an OSAX method is:
OSAX_BEGIN_METHOD(IID) // method body [optional exception translators] OSAX_TRANSLATE_EXCEPTIONS OSAX_END_METHODThe argument IID should be the IID of the interface implemented by the method. These macros enable the ObjectStore fault handler, perform synchronization, establish appropriate transaction context, and translate C++ and TIX exceptions into ActiveX exceptions.
OSAX_HANDLE_EXCEPTION(H)If a C++ exception derived from the class exception is thrown, an ActiveX exception is formed from the error description and the method returns an HRESULT of H.
OSAX_HANDLE_EXCEPTION(E, e) exception handling codeIf a C++ exception derived from E is thrown, e is bound to a reference to the exception. You can handle the exception in an arbitrary way.
template<class T> class IOSAXContextInformationImplThe argument T should be the ATL class that implements your object. The class is used internally by OSAX to associate OSAX objects with appropriate transactions.
All OSAX instance class classes must use the following class as a base class:
template <class CInstanceClass, class CInstance> class CInstanceClassImplThe template arguments are the class of the instance class and the class of the instance. The class implements the IOSAXType interface.
template <class CInstanceClass, class CInstance, class C> class CInstanceImplAll classes that implement OSAX instances must include this base class. It defines the CInstance::CDataPtr class and many methods used internally by OSAX.
static CInstance::GetInterfacePointer(const CInstance::CDataPtr&,I** ppI)The GetInterfacePointer methods are used to obtain an interface pointer for an OSAX instance or instance container. They are defined by the OSAX_INTERFACE_CONVERSION macro.
static CInstance::GetClassInterfacePtr(I** ppI)The GetClassInterfacePtr method is used to obtain an interface pointer for the OSAX instance class associated with CInstance. The method is defined by OSAX_CLASS_INTERFACE.
HRESULT CInstance::SetDataImpl(IUnknown* pUnk)Use SetDataImpl to change which C++ object is associated with an instance container. The context associated with pUnk will become the context for the instance container. If SetDataImpl is called by an OSAX instance, E_INVALIDARG is returned.
static void CInstance::OnDeleteData(C* pC)This method is called on FinalRelease when CInstance::DeleteDataOnFinalRelease has been called to mark the object for deletion on FinalRelease. It deletes pC. You may define override this definition in the CInstance class to do something different.
HRESULT CInstance::DeleteDataOnFinalRelease(VARIANT_BOOL bDelete)Use this method to specify whether or not the C++ data should be deleted upon final release of the OSAX instance.
static CComPtr<IOSAXInstanceClass> CInstance::InstanceClass()Returns the InstanceClass (an internal OSAX object) for use with the OSAX operator new.
In the descriptions that follow, C is the class of the C++ type associated with CInstance.
CInstance::CDataPtr::CDataPtr()Creates a reference to a NULL pointer.
CInstance::CDataPtr::CDataPtr(C* pC)Obtains the OSAX instance for pC and wraps it in CDataPtr.
CInstance::CDataPtr::CDataPtr(IUnknown* pUnk)If pUnk is an OSAX instance, CDataPtr will refer to its C++ object.
If pUnk supports IATKRefString (an ATK object), then CDataPtr will reference the C++ pointer referenced by the ATK object.
CInstance::CDataPtr::CDataPtr(VARIANT& rvar, const CDataPtr& refDefault)If rvar is an optional argument to a method, and the argument was supplied by the caller, then it should be an interface pointer, and the CDataPtr(IUnknown* pUnk) procedure will be used.
If rvar was not supplied by the called, then the CDataPtr will be initialized to the C++ pointer referenced by refDefault.
C* CInstance::CDataPtr::operator -> () const CInstance::CDataPtr::operator C* () const C* CInstance::CDataPtr::Resolve() constAll these return the referenced C++ pointer. You may need to explicilty use Resolve in some cases where a conversion operator can not be applied.
CDataPtr& CInstance::CDataPtr::operator = (const CDataPtr& rDataPtr)Changes the referenced C++ pointer to that of rDataPtr. Do not assign to the data member of an OSAX instance. Instead, use the method CInstance::SetDataImpl.
bool CInstance::CDataPtr::operator !() constReturns true if CDataPtr references a NULL pointer.
template <class CInstance, class C, const GUID* G> class CConversionImplC is the C++ type referenced by the OSAX instance, and G is the CLSID for the instance class. CInstance is the class name you will use as the class name in the server you are using. It does not need be the same as the class name where the server is actually implemented.
You must add OSAX_INTERFACE_CONVERSION and OSAX_CLASS_INTERFACE macros for the relevant interfaces, and a constructor.
Examples can be found in osax.h. For example, IOSAXString is implemented in an OSAX object server and exposed to other object servers in osax.h as:
class COSAXString : public CConversionImpl<COSAXString,char,&CLSID_OSAXString> { public: COSAXString (IUnknown* pUnk) : ConversionImpl(pUnk){} OSAX_INTERFACE_CONVERSION(IOSAXString) OSAX_CLASS_INTERFACE(IOSAXStringConstructor) };
template <class IDispatchImplCollection, class CInstance, class TElement, class IElement> class IOSAXCollectionImplUse this as a base class instead of IDispatchImpl if you want to expose a class derived from os_Collection as a collection.
IDispatchImplCollection is an IDispatchImpl template instantiation for your collection interface. CInstance is your ATL class. TElement is the class that implements the OSAX instances for the elements of the collection. IElement is the interface to use for methods that accept elements as arguments.
template <class IDispatchImplCollection, class CInstance, class TKey, class IKey, class TElement, class IElement> class IOSAXDictionaryImplUse this as a base class instead of IDispatchImpl if you want to expose a class derived from os_dictionary as a collection.
IDispatchImplCollection is an IDispatchImpl template instantiation for your collection interface. CInstance is your ATL class. TKey is the ATL class that implements the keys. IKey is the interface for the keys. TElement is the ATL class that implements the elements. IElement is the interface for the elements.
OSAX_INTERFACE_CONVERSION(I)Add this macro to the class definition of your instance to define the method:
CInstance::GetInterfacePointer(const CDataPtr&,I** ppI) OSAX_CLASS_INTERFACE(I)Add this macro to the class definition of your instance to define the method:
CInstance::GetClassInterfacePtr(I** ppI) OSAX_TYPE_NAME(name)Add this macro to the class definition of your instance to specify the name of the C++ class. This name can be used to obtain an os_typespec.
OSAX_OBJECTSTORE_TYPESPECAdd this macro to the class definition of your instance to allow it to get a typespec from the C++ class's get_os_typespec method.
OSAX_NO_OBJECT_TABLEAdd this macro to the class definition of your instance to make it not use an object table. This means that there may be multiple OSAX instances for the same C++ object.
HRESULT OSAX::DeleteDataOnFinalRelease(IUnknown* pUnk, VARIANT_BOOL bFree)Use this method if you want the C++ object referenced by pUnk to be deleted upon final release of pUnk.
bool OSAX::Equal(IUnknown* pUnk1, IUnknown* pUnk2)Returns true if pUnk1 and pUnk2 refer to the same C++ object.
HRESULT OSAX::OnEndPage()Call this in servers used by ASP if you want them to abort the transaction when a page is ended.
HRESULT OSAX::GetOSAXObjectStore(IOSAXObjectStore**)Call this to obtain an IOSAXObjectStore interface pointer.
void* operator new(size_t nSize, IUnknown* pUnkWhere, IOSAXInstanceClass* pInstanceClass) void* operator new(size_t nSize, IUnknown* pUnkWhere, IOSAXInstanceClass* pInstanceClass, size_t nElts) void* operator new(size_t nSize, VARIANT& rvar, IOSAXInstanceClass* pInstanceClass) void* operator new(size_t nSize, VARIANT& rvar, IOSAXInstanceClass* pInstanceClass, size_t nElts)OSAX provides these overloadings of operator new to simplify the allocation of C++ objects. You do not need to these news. The pUnkWhere or rvar arguments specify where allocation should take place. If they are a segment or database, allocation will take place in the segment or database. If they are an OSAX instance, allocation will take place in the same segment as the referenced data. Otherwise, transient allocation is used.
Use the nElts versions for vector allocation. The nElts argument specifies the number of elements.
Updated: 03/18/98 13:55:43