All the Different Kinds of ATL-Based Classes

[Previous] [Next]

In 1996—when ATL first appeared—there were no wizards to generate objects. You had to do it all by hand. When the next version of ATL came out in 1997, Microsoft Visual Studio provided a number of different wizards for generating COM classes (including controls). Visual C++ 6.0 produces even more classes and controls. Here's a survey of the differences between the various classes the ATL Object Wizard produces.

Simple Objects

The first kind of object on the list is ATL's simple object. A simple object has only the most basic COM support, including the IUnknown interface and a class object. From there, you invent new interfaces and add them to your class. You get only a simple object from the wizard, but you can choose from many options for your class. These options are available via the Attributes tab of the ATL Object Wizard properties dialog box.

The ATL Object Wizard Options

The ATL Object Wizard is an extremely convenient way to add a COM class to your ATL-based server. Just select New ATL Object from the Insert menu. Figure 9-2 illustrates the ATL Object Wizard.

Figure 9-2. The ATL Object Wizard.

The Attributes dialog allows you to select the threading model for your COM class, whether you want a dual (IDispatch-based) or a custom interface. The dialog also lets you decide how your class will support aggregation. In addition, the Object Wizard lets you easily include the ISupportErrorInfo interface and connection points in your object. Finally, you can choose to aggregate to the Free Threaded Marshaler (FTM).

Let's take a look at ATL's support for the various threading models.

Apartments and Threading

One key to figuring out COM is to understand that COM is centered on the notion of abstraction—hiding as much information as possible from the client. One piece of information that COM hides from the client is whether the COM class is thread-safe. The client should be able to use an object as it sees fit without having to worry about whether an object properly serializes access to itself. COM uses an apartment to provide this abstraction.

This information on apartments and threading is specific to Windows NT 4.0. The apartment story changes with Windows 2000.

An apartment defines an execution context that houses interface pointers. A thread (an execution context) enters an apartment by calling a function from the CoInitialize family: CoInitialize, CoInitializeEx, or OleInitialize. Then COM requires that all method calls to an interface pointer be executed within the apartment that initialized the pointer (for example, from the same thread that called CoCreateInstance). Modern COM currently defines two kinds of apartments—single-threaded apartments and multithreaded apartments. Single-threaded apartments can house only one thread, whereas multithreaded apartments can house several threads. A process can have only one multithreaded apartment, but it can have many single-threaded apartments. An apartment can house any number of COM objects.

A COM object created within a single-threaded apartment is guaranteed to have its method calls serialized through the remoting layer. A COM object created within a multithreaded apartment doesn't have this guarantee. A good analogy for instantiating a COM object within a multithreaded apartment is putting a piece of data into global scope where multiple threads can get to it. Putting a COM class within a single-threaded apartment is like having data within the scope of only one thread. The bottom line is that COM classes that want to live in the multithreaded apartment had better be thread-safe, whereas COM classes that are satisfied living in their own apartments need not worry about concurrent access to their data. Access to a COM object living in the same apartment as the client is extremely fast. Accessing a COM object in a different apartment than the client incurs the cost of remoting, which is much slower.

A COM object that lives within a different process space from its client has its method calls serialized automatically via the remoting layer. However, a COM class that lives in a DLL might want to provide its own internal protection (using critical sections, for example) rather than have the remoting layer protect it. A COM class advertises its thread safety to the world via a Registry setting. This named value lives under the CLSID key in HKEY_CLASSES_ROOT, as shown here:

 [HKCR\CLSID\{some GUID …}\InprocServer32] @="C:\SomeServer.DLL" ThreadingModel=<thread model> 

The ThreadingModel can be one of four major values: Single, Both, Free, or Apartment. The named value can also be blank. Here's a rundown of what each value indicates:

  • Single (or blank) indicates that the class executes in the main thread only.
  • Both indicates that the class is thread-safe and can execute in both apartments. Using this value tells COM to use the same kind of apartment as the client.
  • Free indicates that the class is thread-safe. Using this value tells COM to force the object inside the multithreaded apartment.
  • Apartment indicates that the object isn't thread-safe and must live in its own single-threaded apartment.

ATL provides support for all current threading models.

Choosing threading models from the Object Wizard inserts different code into your class depending on which model you select. For example, if you select the Apartment option, the Object Wizard derives your class from CComObjectRootEx and includes CComSingleThreadModel as the template parameter, like this:

 class ATL_NO_VTABLE CApartmentOb :      public CComObjectRootEx<CComSingleThreadModel>,     public CComCoClass<CApartmentOb,          &CLSID_ApartmentOb>,     public IDispatchImpl<IApartmentOb,          &IID_CApartmentOb,          &LIBID_DLLSVRLib> {  }; 

The CComSingleThreadModel template parameter mixes in the more efficient standard increment and decrement operations for IUnknown (because access to the class is automatically serialized). In addition, the Object Wizard causes the class to insert the correct threading-model value in the Registry. Choosing the Single option in the ATL Object Wizard dialog box also causes the class to use the CComSingleThreadModel and leaves the threading model value blank in the Registry.

Selecting Both, or the free-threading model, causes the class to use the CComMultiThreadModel template parameter, which employs the thread-safe Win32 increment and decrement operations InterlockedIncrement and InterlockedDecrement. For example, a free-threaded class definition looks like this:

 class ATL_NO_VTABLE CFreeOb :      public CComObjectRootEx<CComMultiThreadModel>,     public CComCoClass<CFreeOb,          &CLSID_FreeOb>,     public IDispatchImpl<IFreeOb,          &IID_IFreeOb,          &LIBID_DLLSVRLib> {  } 

Choosing Both for your threading model causes the Registrar to insert Both as the named value for the threading model in the Registry; choosing Free uses the named value of Free for the threading model in the Registry.

When you're writing the object that will live safely within the multithreaded apartment, please be aware of one caveat: Writing a simple object to live in the multithreaded apartment is a seemingly straightforward matter—simply put locks around the methods that access your object's internal data. However, getting an object to work correctly in the multithreaded apartment is another matter. Writing thread-safe COM code involves potential deadlocks and race conditions that aren't immediately obvious.

For example, most Win32 synchronization mechanisms have thread affinity, which means that the synchronization objects become attached to a thread. This causes difficulties because you can potentially have many threads calling into an object. (You're writing the object to live within the multithreaded apartment.) Imagine writing an object that obtains a lock. That's fine—obtaining a lock within a method call prevents other threads from corrupting the data within the object. Now imagine this method making an outbound call to another object that lives on another thread, and then having the other object make an inbound call into the object holding the lock. The object holding the synchronization object will end up deadlocking. For more information about the intricacies of COM and thread affinity, be sure to read Effective COM by Don Box, Keith Brown, Tim Ewald, and Chris Sells (Addison-Wesley, 1999)—particularly Item 34, "Beware Physical Locks in the MTA."

Dual Interfaces vs. Custom Interfaces

In addition to using the threading model you select for your object, the ATL Object Wizard gives you a chance to choose between a regular custom interface and a dual interface for your object's main incoming interface. A dual interface is an interface whose first functions are the IDispatch functions—GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, and Invoke—followed by custom interface functions. A regular custom interface is one in which the first functions are simply the IUnknown functions followed immediately by the interface functions. When you choose a dual interface, the Object Wizard inserts the correct code to make your main incoming interface a dual interface. The code generated by the Object Wizard derives the class from IDispatchImpl and puts an entry in the interface map for IDispatch, like this:

 class ATL_NO_VTABLE CSimpleObjectDefault :      public CComObjectRootEx<CComSingleThreadModel>,     public CComCoClass<CSimpleObjectDefault,         &CLSID_SimpleObjectDefault>,     public IDispatchImpl<ISimpleObjectDefault,          &IID_ISimpleObjectDefault,         &LIBID_EVERYOBJECTKNOWNTOMANSVRLib> {  BEGIN_COM_MAP(CSimpleObjectDefault)     COM_INTERFACE_ENTRY(ISimpleObjectDefault)     COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP()   }; 

In addition, the Object Wizard defines your main incoming interface as a dual interface in the IDL, like this:

 [     object,     uuid(DB0C84DF-B372-11D2-803A-B8B4F0000000),     dual,     helpstring("ISimpleObjectDefault Interface"),     pointer_default(unique) ] interface ISimpleObjectDefault : IDispatch { }; 

In contrast, classes generated with the Custom Interface radio button checked simply inherit from the interface described in the IDL and have a single entry in the interface map for that interface.

The last three options are connection points, ISupportErrorInfo, and the Free Threaded Marshaler.

Connection Points and ISupportErrorInfo

Adding connections to your COM class is easy. Marking the Support Connection Points check box causes the class to derive from IConnectionPointImpl. This option also adds a blank connection map to your class. Adding connection points to your class (for example, an event set) is simply a matter of defining the callback interface in the IDL file and then using the ClassView to create a proxy. The code generated by the Implement Connection Point Wizard available through the ClassView adds the proxy class to the COM class and adds the connection points to the connection point map.

ATL also includes support for ISupportErrorInfo. The ISupportErrorInfo interface ensures that error information is correctly propagated up the call chain. Automation objects that use the error-handling interfaces must implement ISupportErrorInfo. Clicking Support ISupportErrorInfo in the Object Wizard dialog box causes the ATL-based class to derive from ISupportErrorInfoImpl.

The Free Threaded Marshaler

Checking the Free Threaded Marshaler option aggregates the COM Free Threaded Marshaler (FTM) to your class. The class does this by calling CoCreateFreeThreadedMarshaler in the class's FinalConstruct function, as shown here:

 class ATL_NO_VTABLE CATLFTMObj :      public CComObjectRootEx<CComMultiThreadModel>,     public CComCoClass<CATLFTMObj, &CLSID_ATLFTMObj>,     public IDispatchImpl<IATLFTMObj, &IID_IATLFTMObj,          &LIBID_FTMSVRLib> { public:     CATLFTMObj()     {         m_pUnkMarshaler = NULL;     } DECLARE_REGISTRY_RESOURCEID(IDR_ATLFTMOBJ) DECLARE_GET_CONTROLLING_UNKNOWN() DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CATLFTMObj)     COM_INTERFACE_ENTRY(IATLFTMObj)     COM_INTERFACE_ENTRY(IDispatch)     COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal,         m_pUnkMarshaler.p) END_COM_MAP() HRESULT FinalConstruct() {     return CoCreateFreeThreadedMarshaler(         GetControllingUnknown(), &m_pUnkMarshaler.p); } void FinalRelease() {     m_pUnkMarshaler.Release(); } CComPtr<IUnknown> m_pUnkMarshaler; // IATLFTMObj public: }; 

The FTM allows thread-safe objects to bypass the standard marshaling that occurs whenever cross-apartment interface methods are invoked. That way, threads living in one apartment can access interface methods in another apartment as though they were in the same apartment, thus tremendously speeding up cross-apartment calls. The FTM does this by implementing IMarshal for you.

The FTM works like this: When the client asks the object for an interface, the remoting layer turns around and calls QueryInterface, asking for IMarshal. If the object implements IMarshal—which it does because the Object Wizard also adds an entry into the class's interface to handle QueryInterface requests for IMarshal—and the marshaling request is in process, the FTM copies the actual interface pointer into the marshaling packet. That way, the client receives a real live pointer to the object. The client talks to the object directly, without having to go through proxies and stubs. Of course if you choose this option, all the data in your object had better be thread-safe. Finally, when using the FTM, be sure that your object isn't using any apartment-relative resources—such as interface pointers marshaled for a particular apartment—since that will completely break COM's assumptions about apartments and threading.

You should generally avoid the FTM unless you're 100 percent sure you understand the issues involved.

That wraps up the options you have when creating basic COM classes. Let's take a look at the other kinds of COM classes you can create using the ATL wizards, starting with Developer Studio Add-ins.

Developer Studio Add-ins

A Developer Studio Add-in is a COM class implementing an interface named IDSAddIn. Developer Studio Add-ins are useful for extending the Visual Studio environment as well as automating routine tasks in the Developer Studio environment. For example, you might build a Developer Studio Add-in to help automate your build process.

As many applications do these days, Visual Studio maintains an object model. That is, the Developer Studio environment and its components become objects you can control programmatically. For example, you can activate and size windows and even manage projects programmatically. In addition, you can use an Add-in to supply new commands, menu items, and toolbar buttons to the Visual Studio.

The ATL Object Wizard will generate a Developer Studio Add-in shell for you. The main difference between a regular COM class and a Developer Studio Add-in is that the Add-in implements IDSAddIn, shown here:

 interface IDSAddIn : IUnknown {     HRESULT OnConnection(IApplication* pApp,          VARIANT_BOOL bFirstTime,          long dwCookie,          VARIANT_BOOL* OnConnection);     HRESULT OnDisconnection(VARIANT_BOOL bLastTime); }; 

There's not much to IDSAddIn. When Developer Studio creates an instance of your Add-in, Developer Studio calls QueryInterface to get IDSAddIn. Developer Studio calls OnConnection and passes in a pointer to an interface named IApplication. This is the top-level pointer to Visual Studio, and it establishes the communication between Visual Studio and the Add-in. When Visual Studio calls your Add-in's OnDisconnection method, Visual Studio is informing your Add-in that it's disconnecting from your Add-in.

If you want to, you can add a toolbar button to the Add-in. When you add a toolbar button, the Object Wizard asks you to describe the command on the DevStudio Add-ins And Macro Files property page. In addition to generating the code for connecting the Add-in to the environment, the Object Wizard adds the code required by the Add-in to add a command to the Developer Studio menu and toolbar.

Internet Explorer Object

The next kind of object to check out is ATL's Internet Explorer object. The main characteristic distinguishing an Internet Explorer object is that it implements the interface IObjectWithSite, shown here:

 IObjectWithSite : public IUnknown {     virtual HRESULT SetSite(IUnknown *pUnkSite);     virtual HRESULT GetSite(REFIID riid,          void **ppvSite); }; 

The IObjectWithSite interface represents a well-known way to establish communication between a control and its site in the container. Whenever a control needs to exchange interfaces with a container, this is the way to set up the communication. Notice that GetSite and SetSite both manage IUnknown pointers as parameters. This implies that objects implementing IObjectWithSite are not tied to a certain kind of site (as opposed to interface pairs such as IOleControl and IOleControlSite, which exchange specific sites). IObjectWithSite is a generic way for controls and containers to establish communication with each other.

MMC Snap-in

The Microsoft Management Console (MMC) is an Explorer-style console framework for developing server and network management applications. MMC collects small in-process COM objects (called snap-ins) and runs them together under one roof. MMC is already in use today. For example, the Microsoft Transaction Server Explorer runs as an MMC snap-in.

Generating an MMC snap-in component using the Object Wizard yields a standard COM class implementing IComponent and IPersistStreamInit. An MMC snap-in object implements IComponent so that MMC can talk to your object. Here's a rundown of the IComponent interface:

  • Initialize This function provides an entry point to your object for MMC to use.
  • Notify MMC calls this function to notify the snap-in of actions taken by a user (mouse moves, mouse clicks, activation, and so on).
  • Destroy MMC calls this function to ask the snap-in to release all references to the console.
  • QueryDataObject This function returns a pointer to IDataObject that MMC can use to obtain context information for a requested item.
  • GetResultViewType This function returns a string representing the type of result pane view MMC will use.
  • GetDisplayInfo This function retrieves a RESULTDATAITEM structure representing information about an item in the result pane to be displayed.
  • CompareObjects This function compares two IDataObject pointers (acquired by MMC through a call to QueryDataObject).

In addition to implementing IComponent, an MMC snap-in object implements IPersistStreamInit. IPersistStreamInit derives from IPersistStream and adds a function named InitNew that clients can use to ask the object to initialize itself. MMC uses a snap-in's IPersistStreamInit interface to ask the object to load and save its properties (usually configuration information).

MTS Component

The final kind of COM class to examine is the MTS component. MTS components are COM classes that know how to interact with Microsoft Transaction Server. Asking the Object Wizard to generate a default MTS component creates a generic COM class that's no different from the Simple Object the Object Wizard creates. When generating an MTS component using the Object Wizard, you can ask the Object Wizard to slip in an implementation of IObjectControl for you. In addition, you can ask the Object Wizard to generate a poolable object for you.

When writing components to run under MTS, you always have the ability to retrieve an interface to your object's context wrapper by calling GetObjectContext (which retrieves a copy of IObjectContext for you). If your object implements IObjectControl, your object's context wrapper will notify your object upon activation and deactivation. If you check the Object Wizard's Can Be Pooled check box, the Object Wizard implements IObjectControl::CanBePooled so that it returns TRUE. When an object says it can be pooled, MTS will recycle an instance of an object rather than create a new one.

Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127 © 2008-2017.
If you may any questions please contact us: