previous page
next page

Hosting a Control

If you want to host a control, you can do so with ATL's control-hosting support. For example, the Ax in CAxDialogImpl stands for ActiveX control and indicates that the dialog box is capable of hosting controls. To host a control in a dialog box, right-click the dialog box resource and choose Insert ActiveX Control.[5] This produces a dialog box that lists the controls installed on your system, as shown in Figure 1.17.

[5] For commonly used ActiveX controls, it's usually easier to add them to your Visual Studio toolbox than to go through this dialog box every time. Right-click the toolbox, select Choose Items, wait approximately 37 minutes for the dialog box to appear, select the COM Components tab, and select the controls you want on your toolbox.

Figure 1.17. Insert ActiveX Control dialog box


After you insert the control, you can click on it and set its properties in the Properties window, as shown in Figure 1.18.

Figure 1.18. Control Properties dialog box


By clicking the Control Events toolbar button, you also can choose to handle a control's events, as shown in Figure 1.19.

Figure 1.19. Choosing which control events to handle


When the dialog box is shown, the control is created and initialized based on the properties set at development time. Figure 1.20 shows an example of a dialog box hosting a control.

Figure 1.20. A dialog box hosting a COM control


ATL provides support for hosting ATL controls not only in dialog boxes, but also in other windows, in controls that have a UI declared as a dialog box resource (called composite controls), and in controls that have a UI declared as an HTML resource (called HTML controls). For more information about control containment, see Chapter 12, "Control Containment."

Being a C++ COM Client

COM and C++ go hand in handat least, theoretically. A COM interface maps directly to a C++ abstract class. All you need to do to use a COM object is run its IDL file through the MIDL compiler, and you've got a header file with all the information you need.

This worked well until the VB team asked if it could play with this COM stuff, too.

VB developers generally neither know nor want to know C++. And IDL is a language that's very much in the C++ tradition, with lots of support for C/C++-specific things in it (such as arrays and pointers) VB needed a way to store type information about COM objects that VB developers could use and understand easily.

Thus was born the type library (a.k.a. typelib). A typelib stores information about a COM object: The classid, the interfaces that the object supports, the methods on those interfaces, and so onjust about everything you'd find in an IDL file (with some unfortunate exceptions, mostly having to do with C-style arrays). The COM system includes a set of COM objects that lets you programmatically walk through the contents of a typelib. Best of all, the typelib can be embedded into a DLL or EXE file directly, so you never have to worry about the type information getting lost.

The typelib was so successful for VB developers that many COM components these days aren't shipped with an IDL file; the type library includes everything needed to use the components. Only one thing is missing: How do we use typelibs in C++?

The C++ language doesn't understand typelibs. It wants header files. This was such a serious problem that, back in Visual Studio 6, Microsoft extended the compiler so that it could use type libraries in much the same way that you use header files. This extension was the #import statement.

#import is used much like #include is. The general form is shown here:

#import "pisvr.dll" <options>

The #import statement generates either one or two C++ header files, depending on the options you use. These header files have the extensions .tlh (for "typelib header") and .tli (for "typelib inline") and are generated into your project output directory (by default, Debug for a debug build, Release for a release build).

The options on the #import line give you a great deal of control over the contents of the generated files. Check the Visual Studio documentation for the full list; we talk about some of the more commonly used options here.

The no_namespace option tells the compiler that we don't want the contents of the generated files to be placed into a C++ namespace. By default, the contents of the generated files are placed in a C++ namespace named after the type library.

named_guids instructs the compiler that we want to have named symbols for the GUIDs in the type library. By default, this would not compile because the name CLSID_PISvr would not be defined:

::CoCreateInstance( CLSID_PISvr, ... );

Instead, you have to do this:

::CoCreateInstance( __uuidof( PISvr ), ... );

You also need to use __uuidof( ) to get the IID for interfaces.

The raw_interfaces_only option requires the most explanation. By default, when the #import statement generates the header file, it doesn't just spit out class definitions for interfaces. It actually generates wrapper classes that attempt to make a COM interface easier to use. For example, given the interface:

interface ICalcPi : IDispatch {
  [propget, id(1), helpstring("property Digits")]
  HRESULT Digits([out, retval] LONG* pVal);
  [propput, id(1), helpstring("property Digits")]
  HRESULT Digits([in] LONG newVal);
  [id(2), helpstring("method CalcPi")]
  HRESULT CalcPi([out,retval] BSTR* pbstrPi);
};

Normal use of this interface would be something like this:

HRESULT DoStuff( long nDigits, ICalcPi *pCalc ) {
    HRESULT hr = pCalc->put_Digits( nDigits );
    if( FAILED( hr ) ) return hr;

    BSTR bstrResult;
    hr = pCalc->CalcPi( &bstrResult );
    if( FAILED( hr ) ) return hr;

    std::cout << "PI to " << nDigits << " digits is "
        << CW2A( bstrResult );

    ::SysFreeString( bstrResult );
    return S_OK;
}

When using the #import statement, on the other hand, using this interface looks like this:

void DoStuff( long nDigits, ICalcPiPtr spCalc ) {
  spCalc->Digits = nDigits;
  _bstr_t bstrResults = spCalc->CalcPi();
  std::cout << "PI to " << spCalc->Digits << " digits is "
    << ( char * )bstrResults;
}

The ICalcPiPtr type is a smart pointer expressed as a typedef for the _com_ptr_t class. This class is not part of ATL; it's part of the Direct-To-COM extensions to the compiler and is defined in the system header file comdef.h (along with all the other types used by the wrapper classes). The smart pointer automatically manages the reference counting, and the_bstr_t type manages the memory for a BSTR (which we discuss in Chapter 2, "Strings and Text").

The most remarkable thing about the wrapper classes is that the hrESULT testing is gone. Instead, the wrapper class translates any failed HRESULTs into a C++ exception (the _com_error class, to be precise). This lets the generated code use the method's [retval] variable as the actual return value, which eliminates a lot of temporary variables and output parameters.

The wrapper classes can immensely simplify writing COM clients, but they have their downsides. The biggest is that they require the use of C++ exceptions. Some projects aren't willing to pay the performance penalties that exception handling brings, and throwing exceptions means that developers have to pay very careful attention to exception safety.

Another downside to the wrappers for ATL developers is that ATL also has wrapper classes for COM interfaces (see Chapter 3, "ATL Smart Types") and BSTRs (see Chapter 2). The ATL wrappers are arguably better than the ones defined in comdef.h; for example, you can accidentally call the Release() method on an ICalcPiPtr, but if you use the ATL wrapper, that would be a compile error.

By default, you get the wrappers when you use #import. If you decide that you don't want them, or if for some reason they don't compile (which has been known to happen to at least one of your humble authors on very complex and strange typelibs), you can turn off the wrapper classes and just get straight interface definitions by using the raw_interfaces_only option.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)