Com Programming With Microsoft .Net

Overview

Microsoft .NET is here, but COM and COM+ will be a valuable part of every Microsoft Windows programmer’s toolbox for years. This in-depth programming guide explains how and where COM and COM+ fit into the new .NET world, demonstrates the new technologies that are available in .NET, and shows how to interoperate between COM/COM+ and .NET. It explains how to use Microsoft Visual Studio .NET to write traditional COM objects, how to use COM objects in .NET code, and how to use .NET objects as COM objects. It also ...

See more details below
Available through our Marketplace sellers.
Other sellers (Paperback)
  • All (14) from $1.99   
  • New (3) from $35.19   
  • Used (11) from $1.99   
Close
Sort by
Page 1 of 1
Showing All
Note: Marketplace items are not eligible for any BN.com coupons and promotions
$35.19
Seller since 2008

Feedback rating:

(169)

Condition:

New — never opened or used in original packaging.

Like New — packaging may have been opened. A "Like New" item is suitable to give as a gift.

Very Good — may have minor signs of wear on packaging but item works perfectly and has no damage.

Good — item is in good condition but packaging may have signs of shelf wear/aging or torn packaging. All specific defects should be noted in the Comments section associated with each item.

Acceptable — item is in working order but may show signs of wear such as scratches or torn packaging. All specific defects should be noted in the Comments section associated with each item.

Used — An item that has been opened and may show signs of wear. All specific defects should be noted in the Comments section associated with each item.

Refurbished — A used item that has been renewed or updated and verified to be in proper working condition. Not necessarily completed by the original manufacturer.

New
0735618755 BRAND NEW NEVER USED IN STOCK 125,000+ HAPPY CUSTOMERS SHIP EVERY DAY WITH FREE TRACKING NUMBER

Ships from: fallbrook, CA

Usually ships in 1-2 business days

  • Standard, 48 States
  • Standard (AK, HI)
$65.00
Seller since 2014

Feedback rating:

(164)

Condition: New
Brand new.

Ships from: acton, MA

Usually ships in 1-2 business days

  • Standard, 48 States
  • Standard (AK, HI)
$65.00
Seller since 2014

Feedback rating:

(164)

Condition: New
Brand new.

Ships from: acton, MA

Usually ships in 1-2 business days

  • Standard, 48 States
  • Standard (AK, HI)
Page 1 of 1
Showing All
Close
Sort by
Sending request ...

Overview

Microsoft .NET is here, but COM and COM+ will be a valuable part of every Microsoft Windows programmer’s toolbox for years. This in-depth programming guide explains how and where COM and COM+ fit into the new .NET world, demonstrates the new technologies that are available in .NET, and shows how to interoperate between COM/COM+ and .NET. It explains how to use Microsoft Visual Studio .NET to write traditional COM objects, how to use COM objects in .NET code, and how to use .NET objects as COM objects. It also discusses COM+ topics such as disconnected applications and subscriptions. The authors show you both theoretical and practical approaches—explaining how the mechanisms work and also providing practical advice with sample code to show how to handle interoperability.
Topics covered include:
The .NET view of COM and COM+

  • COM, COM+, and .NET
  • Using COM components in .NET code
  • Using .NET components in COM applications
  • Overview of COM+ coding for .NET

Writing COM code
  • Attributed programming
  • Active Template Library (ATL) and ATL Server

Writing COM+ code
  • A simple COM+ example
  • Working with disconnected applications
  • Creating subscriptions
  • Web application scenarios

Interoperability
  • Interacting with unmanaged code
  • Advanced interaction
  • Working with predefined interfaces

Includes sample code on the Web
Read More Show Less

Product Details

  • ISBN-13: 9780735618756
  • Publisher: Microsoft Press
  • Publication date: 2/26/2003
  • Pages: 542
  • Product dimensions: 7.54 (w) x 9.18 (h) x 1.21 (d)

Meet the Author

As a professional consultant, trainer, and writer, Julian Templeman has been involved in using, teaching, and writing about COM for five years. He's taught COM courses to hundreds of students throughout the United Kingdom and Europe, and as a result he knows how to explain complex COM issues to programmers. As a consultant, he's advised large and small companies on COM technology, and he has a firm grasp of the practicalities involved in the everyday use of COM in Windows-based software development.

Julian has also been sole or joint author of 10 programming books, so he has expertise in explaining programming topics in writing. Two of these books concerned COM: the first was the only detailed book on using COM with the Microsoft MFC library, and the second was the best-selling Beginning ATL COM Programming from Wrox Press. Julian's most recent book, published through Microsoft Press, is Microsoft Visual C++ .NET Step by Step.

On the .NET side, Julian has been using .NET since the first alpha version was handed out at the Denver Professional Developers' Conference in 1998. At the time of writing (April 2002), he has contributed to three .NET books on the market, and he has an in-depth knowledge of the .NET technologies at both overview and detailed levels. In addition, he has recently coauthored Microsoft Official Curriculum course 2558 on writing .NET code using Visual C++ .NET. Julian is also currently running .NET programming courses and doing a small (but increasing) amount of .NET consultancy work.

Read More Show Less

Read an Excerpt

Chapter 6.

Attributed Programming

  • What Are Attributes?
    • How Do Attributes Work?
  • Using Attributes in C++ Code
  • Walkthrough: Creating a Simple COM Component
    • Seeing the Inserted Code
    • Adding COM Objects
    • Adding Methods and Properties
    • Testing the Component
    • Creating the Server by Hand
  • Basic Attributed Programming
    • Creating Modules
    • Creating Interfaces
    • Creating Coclasses
    • Stand-Alone Attributes
  • Handling Aggregation
    • Review of COM Aggregation and Delegation
    • The aggregatable and aggregates Attributes
  • Handling Errors
  • Events
    • Adding Event Support
    • Handling Events
  • Compiler Options
    • Generating IDL
    • Suppressing IDL Generation
    • Summary

6   Attributed Programming

The second part of this book switches away from interoperability issues and focuses instead on creating and using COM components by exploiting the new features of Visual C++ .NET. The Active Template Library (ATL) was introduced in Visual C++ 4, and was designed to let C++ programmers create the smallest, fastest, most efficient COM components possible. The library has matured with successive releases of Visual C++ and now provides a very powerful tool for the C++ COM programmer.

Microsoft has introduced many new features into the latest version of ATL, version 7. Most important, though, Microsoft has introduced a new way to write ATL COM components, using attributed programming. This innovation makes it possible to write ATL COM components directly in C++, and for many components, developers will no longer have to interact with the Interface Definition Language (IDL) or work with the ATL source code. In fact, the compiler generates the IDL and ATL source code at compile time, so you don’t need to look at them at all unless you want to understand what is happening.

This chapter provides an introduction to attributed programming, while Chapter 7 looks at the additions to the latest version of ATL—in particular, the ATL Server classes, which are designed for writing server-side applications for use with Internet Information Server (IIS).


IMPORTANT:
Attributed programming is used to create COM components in C++ using the ATL library. That means this chapter has nothing to do with .NET and also assumes knowledge of C++ and ATL programming on the part of the reader. No information in this chapter applies to Visual C# or Visual Basic .NET.

What Are Attributes?

Attributes provide a way to extend C++ without breaking the structure of the language. They define an add-on mechanism that is used to attach extra data to C++ constructs but which doesn’t require adding new keywords to the language or altering the way C++ currently works.


IMPORTANT:
Attributes are used for several separate tasks in the .NET release of Microsoft development tools: providing metadata for managed types, creating COM objects, creating Web Services and ATL Server code, implementing performance counters, and implementing OLEDB consumers. Although the C++ syntax is the same wherever attributes are used, the tasks are quite different and attributes can work in different ways. For example, COM attributes deal with code generation, whereas metadata attributes provide data that can be used at compile time, run time, or both.

In Visual C++ 6, most COM development in C++ used the ATL library. While ATL produces very small, efficient COM classes, a large proportion of COM objects don’t make use of ATL functionality beyond using the wizards to create an ATL skeleton and add methods and properties. The use of ATL for these components is overkill, and the developers of these classes don’t need to modify—or even see—the ATL or IDL source code.

The COM-related attributes are designed to simplify the creation of COM components, and it’s possible to create many components without seeing any ATL or IDL code at all. Even if you are used to using ATL, attributed programming can increase the productivity of component developers. You can still use ATL if you need to, but attributes simplify the process of component creation for the many cases where you don’t need anything out of the ordinary.

To put it rather simplistically, attributes let you specify in C++ code the same things you previously had to code in IDL or ATL. COM developers don’t need to use a different library or learn IDL to create components, and they can do all their development in plain C++. Here’s a simple example. To mark a class as the implementation of a COM coclass, use the coclass attribute on the class definition:

[coclass]
class MyObject
{
  ...
};

If you also want to specify the CLSID for the resulting coclass rather than have one generated automatically for you, add the uuid attribute:

[coclass, uuid(12EA4458-7753-11D2-098A-00C04F37BBFF)]
class MyObject
{
  ...
};

When you compile code containing attributes such as coclass and guid, the compiler automatically generates the ATL and IDL code needed to implement the COM component.

You can apply attributes to nearly any C++ construct, such as interfaces, classes, data members, and member functions. As you read the chapter, you’ll find explanations of all the major attributes available to the COM programmer using Visual C++ .NET.

Using attributes replaces the large amount of IDL and registration code required by a COM component with a few concise annotations to class and interface definitions.


NOTE:
COM attributes don’t change the way COM objects are registered and the way they work, so type libraries and registry entries are still the same as before. Attributes simply provide a simpler and more abstract layer at the C++ code level.

How Do Attributes Work?

Attributes are implemented by attribute providers, which are implemented as separate dynamic-link libraries (DLLs). When the Visual C++ compiler recognizes an attribute, the code is parsed and syntactically verified as correct. The compiler then dynamically calls an attribute-provider DLL, which will insert code or make other modifications at compile time; the compiler processes the generated code as part of building the compiled output. The provider that gets called depends on the type of attribute—for example, ATL-related attributes are all implemented by the Atlprov.dll provider. The process is illustrated in Figure 6-1.

Figure 6-1 An attribute provider works with the compiler to generate code at compile time. (Image unavailable)

ATL attributes work by inserting ATL code into the project. Attributes don’t alter the contents of the source file, and you can see only the injected code in the debugger. If you’d like a copy of the generated code, you can use the /Fx compiler option to generate a file containing the original file with the injected code merged. If you are using Visual Studio .NET, you can find this option on the Output Files pane of the C++ section in the Project Properties dialog. Note that the generated ATL code exists only at compile time, but information about the code needs to be added to debug builds so that it’s available to the debugger.


NOTE:
As of .NET 1.1, you can’t write your own attribute providers. Two providers are used with C++: clxx.dll, which the compiler uses for basic type generation and marshaling; and atlprov.dll for ATL. In fact, it appears unlikely you’ll ever be able to write custom providers because they interact with the compiler at a very low level and to write them would require knowing a lot of details about how the compiler works that are unlikely to be made public knowledge.

Using Attributes in C++ Code

In case you haven’t used attributes in C++ code in Visual Studio .NET, this section briefly explains attribute syntax and usage. The basic syntax will be familiar to anyone who has done IDL coding.

Attributes can be attached to elements in C++ code by placing square brackets immediately before the element that the attributes apply to:

[coclass, uuid(12EA4458-7753-11D2-098A-00C04F37BBFF)]
class MyObject
{
  ...
};

In the example, the coclass and uuid attributes are being applied to the class that follows. If you need to specify more than one attribute, use a comma-separated list. Attributes can take parameters—in the example, coclass doesn’t take any parameters, while uuid takes one, a GUID.

Attribute parameters can be mandatory or optional. Mandatory parameters (also called positional parameters) occur first in the argument list. Optional (or named) parameters occur after any positional ones, and they use a name=value syntax to denote which optional parameters are being specified. This means it doesn’t matter in which order named parameters are specified. Here’s an example:

[ module(dll, uuid = "{1D196988-3060-486E-A4AC-38F9685D3BF7}", 
         name = "SimpleObject", 
         helpstring = "SimpleObject 1.0 Type Library",
         resource_name = "IDR_SIMPLEOBJECT") ];

The module attribute has one positional parameter (dll) and four named parameters. The named parameters can be specified in any order.


NOTE:
Although it has no bearing on how you use attributes in your C++ code, what you’re doing when you specify an attribute is coding a constructor call. Attributes are implemented by classes, and when you attach an attribute to a code element, you’re specifying how the attribute object should be created by giving the parameters for the constructor. The attribute object can then be invoked by the compiler or run time to do its job.

As well as using COM attributes, you can also write your own custom attributes for use with C++ classes and members. These have nothing to do with COM and are not used by the compiler. Instead, they’re used to provide extra run-time information about types, which can be retrieved using the .NET Framework reflection mechanism. For example, you could create a custom attribute that holds information about the revision history of a class. Writing custom attributes is outside the scope of this book; if you want to know more, read Programming with Managed Extensions for Microsoft Visual C++ .NET by Richard Grimes, published by Microsoft Press.

Walkthrough: Creating a Simple COM Component

This section provides a complete walkthrough that shows how to create a simple COM server housed in a DLL by using attributed code and the wizards provided by Visual Studio .NET. After you’ve created the object, you’ll see how to test the component from a console application, and then how to write the same code without using Visual Studio .NET.

Create an ATL project as normal. Select the Application Settings, and note how the Attributed and DLL options are selected by default. Note also how selecting the Attributed option disables the Proxy/Stub Merging, MFC, and Component Registrar Support options. Deselecting the Attributed option causes a nonattributed ATL project to be created and enables the disabled options.

Figure 6-2 The Attributed check box governs whether attributed code will be produced by the ATL Project Wizard. (Image unavailable)

You’ll find the project contains two .cpp files: the normal stdafx.cpp used to handle precompiled header generation, and the source code file for the project, SimpleObject.cpp. Open this file, and you’ll find that it contains a stand-alone module attribute, similar to the one below:

// The module attribute causes DllMain, DllRegisterServer and 
// DllUnregisterServer to be automatically implemented for you
[ module(dll, uuid = "{1D196988-3060-486E-A4AC-38F9685D3BF7}", 
         name = "SimpleObject", 
         helpstring = "SimpleObject 1.0 Type Library",
         resource_name = "IDR_SIMPLEOBJECT") ];

The stand-alone module attribute is used to generate the standard skeleton code required by an ATL application, including (as the comment says) the registration functions.

Seeing the Inserted Code

The /Fx (merge inserted code) compiler option can be used to provide a listing that shows the code that has been inserted by the attributes. If you’re using Visual Studio .NET, right-click the solution name in Solution Explorer and click Properties to bring up the property pages for the solution. Then expand the C/C++ entry in the list in the left-hand pane, select Output Files from the expanded list, and set the Expand Attributed Source option to Yes. If you’re not using Visual Studio, simply add /Fx to the compiler command line.

Compile the project, and you’ll find that a file with a .mrg.cpp extension is produced for each source file, which contains the original source with generated code merged in. Note the comment at the top of the .mrg files: the code in these files might not be exactly the same as that which was generated for the compiler.

If you look in the SimpleObject.mrg.cpp file, you can see the generated definitions for the registration functions, which will look similar to this:

#injected_line 7 "c:\\writing\\com book\\simpleobject\\simpleobject.cpp"
extern "C" STDAPI DllRegisterServer(void) 
{
    return _AtlModule.DllRegisterServer();
}
#injected_line 7 "c:\\writing\\com book\\simpleobject\\simpleobject.cpp"
extern "C" STDAPI DllUnregisterServer(void) 
{
    return _AtlModule.DllUnregisterServer();
}

The #injected directives show which line in the original file caused this code to be injected.

Further down the .mrg file, you’ll see the definition of a module class. Note that the following code has been reformatted to make it easier to read:

//+++ Start Injected Code For Attribute ‘module’
#injected_line 7 "c:\\writing\\com book\\simpleobject\\simpleobject.cpp"

class CSimpleObjectModule : public CAtlDllModuleT<CSimpleObjectModule>
{
public:
    DECLARE_LIBID(__uuidof(SimpleObject))
};

This code defines an ATL module class that is derived from CAtlDllModuleT<>. In previous versions of ATL, modules were represented by CComModule; in version 7, this functionality has been split between several new classes. You’ll find details of these new classes in the "Creating Modules" section later in the chapter.

Adding COM Objects

You add a class to represent a COM coclass by right-clicking the Solution name in Solution Explorer, selecting Add and then Add Class from the context menu. Select the ATL Simple Object icon from the right-hand pane, and click Open. The ATL Simple Object Wizard appears as shown in Figure 6-3. This wizard looks similar to the ATL Object Wizard in Visual Studio 6.

Figure 6-3 The ATL Simple Object Wizard lets you set the names of files, classes, and COM identifiers.(Image unavailable)

The Options pane shown in Figure 6-4 lets you set the options for your new coclass before it’s implemented, and once again it’s very similar to the one you are familiar with in Visual Studio 6.

Figure 6-4 The Options pane lets you specify COM properties for the coclass(Image unavailable)

Note that one bug from previous versions has been fixed: the free-threaded marshaler option is grayed out when the threading model won’t support it.

When the wizard completes, the .h and .cpp files for the implementation class will have been generated and added to the project. You’ll also note that there’s no IDL file in the project, as everything you would have specified in the IDL is now added using attributes. As you’d expect from an ATL project, the header file Simplest.h contains the definition of the C++ class that implements the coclass, but the code is very different. First, you’ll see an interface definition that looks like this:

// ISimplest
[
   object,
   uuid("FE19D164-DB7D-4A17-8D99-DFD57FB69E02"),
   dual, helpstring("ISimplest Interface"),
   pointer_default(unique)
]
__interface ISimplest : IDispatch
{
};

The COM interface is defined directly in C++, using the __interface keyword. It is qualified with the same attributes that you’d expect to see on the definition of an interface in a COM IDL file.


NOTE:
The __interface keyword is used to define both the COM interface and managed .NET language interfaces. The keyword is the same, but the usages are completely different.

Here is the definition of the implementation class:

// CSimplest

[
   coclass,
   threading("apartment"),
   vi_progid("SimpleObject.Simplest"),
   progid("SimpleObject.Simplest.1"),
   version(1.0),
   uuid("5BDCE1E1-D79D-41E1-9500-E4ED3E64887A"),
   helpstring("Simplest Class")
]
class ATL_NO_VTABLE CSimplest : 
   public ISimplest
{
public:
   CSimplest()
   {
   }

   DECLARE_PROTECT_FINAL_CONSTRUCT()

   HRESULT FinalConstruct()
   {
      return S_OK;
   }
   
   void FinalRelease() 
   {
   }

public:

};

This is recognizably an ATL class. It contains familiar items such as the ATL_NO_VTABLE and DECLARE_PROTECT_FINAL_CONSTRUCT macros, but it doesn’t expose any ATL implementation details—such as the ATL base classes—which are provided when the code is injected by the attribute provider at compile time. You’ll see what this code looks like shortly.

Once again, you can see information that would have been provided in the IDL file is supplied inline using attributes. This is very useful—for instance, if you want to change, say, the threading model of a component, you need to change only the threading attribute on the class definition. With ATL 3, you would have had to change it in the header file and the registration .rgs file.

If you generate a .mrg file and open it, you can see how the class has been generated. One thing you might notice is that even though the component has a dual interface and the interface definition in the Simplest.h file derives from IDispatch, the generated class doesn’t inherit from IDispatchImpl:

class ATL_NO_VTABLE CSimplest : 
    public ISimplest
,
    /*+++ Added Baseclass */ public CComCoClass<CSimplest, &__uuidof(CSimplest)>,
    /*+++ Added Baseclass */ public CComObjectRootEx<CComSingleThreadModel>,
    /*+++ Added Baseclass */ public IProvideClassInfoImpl<&__uuidof(CSimplest)>
{
public:
      ...

The coclass attribute generates its own implementation of IDispatch, which you can see if you look further down the listing. If you decide that you want to use the ATL 3 behavior, you can manually derive from IDispatchImpl, and the attribute provider will not generate the custom implementation.


NOTE:
This is the general principle: attribute providers will not generate code if they see that an implementation already exists. Because of this principle, you can provide custom behavior in a straightforward manner by overriding the attribute provider.

What About IDL?

If all the attributes you used to specify in IDL are now written in the C++ code, is there still an IDL file? The answer is a definite yes. The underlying mechanisms of building, registering, and using COM objects haven’t changed, so it’s still necessary to provide a type library. An IDL file is still needed, then, so that MIDL can compile it into a type library. The file is generated by the compiler from the attribute information in the source code, and you can see it in the project directory once you’ve done a build. You can open this file, and its content should be familiar. Don’t make changes to it, though, because it will be regenerated by the compiler.

The name of the file will be the project name with a prepended underscore—for example, _SimpleObject.idl. If you have a reason to change this, bring up the project properties by right-clicking the solution name in Solution Explorer, choosing Properties, and then looking for the Embedded IDL entry under the Linker section. You can change the Merged IDL Base File Name, but make sure that you don’t use the project name, as this can cause generated filenames to clash with others already in the project.

Adding Methods and Properties

You add methods and properties to the coclass by using Class View. If this isn’t visible, select Class View on the View menu or press CTRL+SHIFT+C to display it. Expand the component node—which in this case is called SimpleObject—and find the ISimplest interface symbol. Right-click the interface symbol, click Add and then Add Method to bring up the Add Method Wizard shown in Figure 6-5.

Figure 6-5 Adding a method to an ATL class using the Add Method Wizard (Image unavailable)

The main difference between the ATL 7 and the ATL 3 wizard is the ability to set parameter attributes rather than having to type them in. You’ll also find that the parameter attribute check boxes are sensitive to the parameter type—that is, if you don’t specify a pointer type, the dialog will disable the out and retval check boxes.


NOTE:
Make sure you use the Add button to add each parameter, and don’t press Finish before you’ve added the final one—otherwise, it won’t be added.

The second page of the wizard shown in Figure 6-6 lets you set the IDL attributes for the method.

Figure 6-6 Setting IDL attributes using the Add Method Wizard (Image unavailable)

As you’d expect, this adds a COM method definition to the interface defined in Simplest.h and an implementation method to the CSimplest class. Because the interface has been defined as dual, the method has a dispID, which is automatically set to the next available value.

Properties can be added in a similar way, by selecting the Add Property menu item to bring up the Add Property Wizard.

Building the project creates the component DLL and its type library, and it also writes an IDL file using the attributes in the code. Here’s a sample IDL file that was produced for a simple component:

import "c:\Program Files\Microsoft Visual Studio .NET 2003\
Vc7\PlatformSDK\include\prsht.idl";
import "c:\Program Files\Microsoft Visual Studio .NET 2003\
Vc7\PlatformSDK\include\mshtml.idl";
import "c:\program files\microsoft visual studio .net 2003\
vc7\platformsdk\include\dimm.idl";
import "c:\Program Files\Microsoft Visual Studio .NET 2003\
Vc7\PlatformSDK\include\mshtmhst.idl";
import "c:\program files\microsoft visual studio .net 2003\
vc7\platformsdk\include\docobj.idl";
import "c:\Program Files\Microsoft Visual Studio .NET 2003\
Vc7\PlatformSDK\include\exdisp.idl";
import "c:\Program Files\Microsoft Visual Studio .NET 2003\
Vc7\PlatformSDK\include\objsafe.idl";

[
   object,
   uuid(FE19D164-DB7D-4A17-8D99-DFD57FB69E02),
   dual,
   helpstring("ISimplest Interface"),
   pointer_default(unique)

#line 14 "c:\\writing\\cm\\com book\\attributes\\simpleobject\\simplest.h"
interface ISimplest : IDispatch {
#line 16 "c:\\writing\\cm\\com book\\attributes\\simpleobject\\simplest.h"
   [propget,id(2),helpstring("property X")] 
          HRESULT  X([out,retval] short  *pVal);
   [propput,id(2),helpstring("property X")] 
          HRESULT  X([in] short newVal);
   [id(3),helpstring("method Square")] 
          HRESULT  Square([in] SHORT n, 
                          [out,retval] long* pResult);
};

[ version(1.0), uuid(1D196988-3060-486E-A4AC-38F9685D3BF7), 
    helpstring("SimpleObject 1.0 Type Library") ]
library SimpleObject
{
   importlib("stdole2.tlb");
   importlib("olepro32.dll");

   [
      version(1.0),
      uuid(5BDCE1E1-D79D-41E1-9500-E4ED3E64887A),
      helpstring("Simplest Class")
   ] 
#line 34 "c:\\writing\\cm\\com book\\attributes\\simpleobject\\simplest.h"
   coclass CSimplest {
      interface ISimplest;
   };
}

You can see that—apart from some import declarations and formatting—it is very similar to the ATL IDL files you’re used to working with.

Testing the Component

You can easily write a console application to test the component. Listing 6-1 contains an example. You can find this sample in the Chapter06\TestObject folder of the book’s companion content. This content is available from the book’s Web site.

#include <iostream>
#include "atlbase.h"

// Create wrappers for the component
#import "..\_SimpleObject.tlb" no_namespace

using namespace std;

void main()
{
   CoInitialize(NULL);
   {
      // Create an instance of the object
      CComPtr<ISimplest> pI;
      pI.CoCreateInstance(__uuidof(CSimplest));

      // Call the Square method
      long res = 0;
      res = pI->Square(3);
      cout << "Result is " << res << endl;
   }
   CoUninitialize();
}

Listing 6-1 TestObject.cpp

One thing to note here: You might wonder why the code between CoInitialize and CoUninitialize is placed in a block. It is important that all COM interface pointers be released by the time CoUninitialize is called, and placing the code in a block ensures that any CComPtr smart pointers have had their destructors called (and thus have released interface pointers they hold) before the call to CoUninitialize.

Creating the Server by Hand

You can also create COM servers without using Visual Studio .NET to help create and process the attributed code. Here’s how you could create the same simple server using Notepad or another text editor in place of Visual Studio .NET.

Creating the Header File

First, create a header file to contain the #define and #include directives needed by the project as shown in Listing 6-2. You’ll find this file in the Chapter06\HandCrafted folder in the book’s companion content.

#pragma once
#define STRICT

#ifndef _WIN32_WINNT
   #define _WIN32_WINNT 0x0400
#endif

#define _ATL_ATTRIBUTES
#define _ATL_APARTMENT_THREADED
#define _ATL_NO_AUTOMATIC_NAMESPACE

#include <atlbase.h>
#include <atlcom.h>
#include <atlwin.h>
#include <atltypes.h>
#include <atlctl.h>
#include <atlhost.h>
using namespace ATL;

Listing 6-2 Defs.h

This file sets up the environment for the attributed ATL source code. Note the definition of the _ATL_ATTRIBUTES symbol, which results in the inclusion of the ATL attribute provider. You need to have this defined whenever you want to use attributes with ATL code. The other two #defines are used to create an apartment-threaded component and to prevent the use of a namespace.

Creating the ATL DLL

Next, create a source file for the component, which in this case I’ve called MyServer.cpp as shown in Listing 6-3. This file is also located in the Chapter06\HandCrafted folder in the book’s companion content.

#include "Defs.h"

// The module attribute is specified in order to implement DllMain,
// DllRegisterServer and DllUnregisterServer
[ module(dll, name = "MyServer", helpstring = "MyServer 1.0 Type Library") ];
[ emitidl ];

Listing 6-3 MyServer.cpp

The file simply contains two ATL attributes. The first attribute, module, you’ve already seen in the Visual Studio .NET example. It causes the generation of the ATL DLL container code, and in this case I’m specifying a minimum number of parameters. I haven’t specified a uuid attribute, which means that one will be generated automatically by the compiler.

The emitidl attribute causes IDL attribute information to be echoed in the IDL file created by the compiler. Save the file, compile it, and run Regsvr32.EXE to register the component:

cl /LD MyServer.cpp
regsvr32 MyServer.dll

The /LD flag tells the compiler and linker to build a DLL rather than an EXE. When you look at the directory in which you compiled the source, you’ll find that IDL and type library files have been generated. Since you didn’t specify a name for these files, the type library and IDL files both use the default vc70 root name; you’ll see how to specify a different file name later in the chapter.

Adding Interface and Coclass Definitions

Just as in the Visual Studio .NET example, you add attributes directly to the C++ source code to control the creation of a COM component. To add an interface to the server project, open the source file and add the following definition:

// ISimplest
// Note: Replace this uuid with one generated using uuidgen
[
   object,  
   uuid("103FF9D9-8BC9-4ea8-8CD4-C1E627D04358"),
   dual, helpstring("ISimplest Interface"),
   pointer_default(unique)
]
__interface ISimplest : IDispatch
{
   HRESULT Square([in] short val, [out, retval]long* pResult);
};

The __interface keyword is used to define a COM interface. It is different from the interface keyword used in ATL 3, which was simply a typedef for struct. Using __interface imposes COM interface rules on the C++ code—for example, an interface can derive only from another interface (and not from a class or struct) and can contain only pure virtual functions (so no data members, constructors, or destructors). The type of interface to be generated is defined by an attribute. By default, the interface will be custom, but dual can be used to specify a dual interface and dispinterface to specify an ODL-style dispatch interface.

The members of the interface are added using standard IDL-like syntax, with attributes used to define the direction in which parameters are passed.

The attributed C++ class definition is similar to the IDL interface definition, and all the attributes will be familiar to the ATL programmer. Add the definition of the class to the source code after the interface:

// CObject1
[
   coclass,
   threading("apartment"),
   vi_progid("MyServer.Simple"),
   progid("MyServer.Simple.1"),
   version(1.0),
   uuid("15615078-523C-43A0-BE6F-651E78A89213"),
   helpstring("Simple Class")
]
class ATL_NO_VTABLE CObject1 : public ISimplest
{
public:
   CObject1() { }

   // The single method
   HRESULT Square(short val, long* pResult){
      *pResult = val * val;
      return S_OK;
   }

   DECLARE_PROTECT_FINAL_CONSTRUCT()
   HRESULT FinalConstruct()
   {
      return S_OK;
   }
   
   void FinalRelease() 
   {
   }
};

The class has the following attributes set:

  • coclass, which defines this as the implementation of a COM coclass. This must be specified for all coclass implementations.
  • threading, which specifies the threading model for the coclass.
  • vi_progid, which specifies the version-independent progID for the class.
  • progid, which specifies the progID, including version information.
  • version, which defines the version of the coclass.
  • uuid, which can be used to specify an attribute if you don’t want to use the default generated by the compiler.
  • helpstring, which is used to define a helpstring for the coclass.

After compiling and registering the component, you can use the same simple console application to test the component.

Basic Attributed Programming

Now that you’ve seen how to create a COM server by using attributes, this section will examine the use of attributes in more detail and provide a reference to the most commonly used attributes.

Creating Modules

When creating COM code with ATL, you have the choice of using a DLL, an EXE, or a Windows Service as the container for the coclasses. Each of these needs to provide a framework for hosting the coclasses it contains, and this framework is provided for you by the ATL Project Wizard. In previous versions of ATL, this would result in the creation of explicit code. For example, for a COM DLL project, you could see—and edit—the definitions and implementation of the DllMain, DllGetClassObject, DllRegisterServer, DllUnregisterServer, and DllCanUnloadNow functions, and you could also see and edit the basic IDL library definition.

When using attributes, the stand-alone module attribute performs these functions. It generates the skeleton for the appropriate server type, and it defines the IDL library block that will contain the coclass definitions.

The module attribute can take a large number of parameters, as shown in Table 6-1.

Table 6-1 Parameters for the module Attribute

Parameter Description
type Defines the type of module that will be created. The value can be dll, exe, or service, with the default being dll.
name The name of the library block.
uuid The GUID for the library block. If this parameter is omitted, a GUID will automatically be generated.
version The version number for the library block. The default value is 1.0.
lcid Used to specify the 32-bit Windows National Language Support locale identifier for the library. This attribute identifies the locale for a type library and lets you use international characters inside the library block.
control A Boolean value specifying whether all the coclasses in the library are controls.
helpstring The help string for this library block.
helpstringdll Specifies the DLL to be used for document-string lookups.
helpfile The name of the help file for the type library.
helpcontext The help context ID for this type library.
helpstringcontext Specifies the context ID for a help topic.
hidden Prevents the entire library from being displayed in a user-oriented browser, such as the Visual Basic Object Browser.
restricted Members of the library cannot be used arbitrarily. This parameter is typically used to prevent access from scripting languages.
custom Can be used to specify one or more custom attributes, each of which is defined by a GUID and a value.
resource_name The resource ID of the .rgs file used to register the APPID of the DLL, executable, or service.

Using the module Attribute

You cannot use the module attribute with a completely empty parameter list. You must provide a minimum of a name parameter, in which case defaults will be assumed for all the other parameters:

[module(name="MyObject")];

This bit of code will define a library block in the IDL using the name provided, and it will generate a GUID to represent the library ID and use a default version of 1.0. Here’s the library block in the IDL that was generated from the module statement:

[ version(1.0), uuid(1a7833b4-e38a-32a6-a32e-d4c61bfb3305) ]
library MyObject
{
    importlib("stdole2.tlb");
    importlib("olepro32.dll");
}

As well as creating the library block in the IDL file, the inclusion of a module attribute also creates a global object to manage the coclasses in the DLL or EXE. In previous versions of ATL, this object was called _Module and was of type CComModule. In Visual C++ .NET, the functionality provided by CComModule has been spread over a number of classes, as listed in Table 6-2.

Table 6-2 ATL Module Classes

Class Description
CAtlBaseModule CAtlBaseModule exposes data needed by most ATL applications, including the resource instance and the HINSTANCE of the module. This class inherits from the _ATL_BASE_MODULE structure, which contains module-specific data.
CAtlComModule CAtlComModule implements a COM server module, providing basic registration and deregistration functionality for objects in the module’s object map and for type libraries.
CAtlWinModule CAtlWinModule provides support for ATL classes that have GUIs.
CAtlDebugInterfacesModule CAtlDebugInterfacesModule provides support for debugging interfaces. This class is used in any ATL project that has defined the _ATL_DEBUG_QI symbol.
CAtlModule CAtlModule provides basic functionality for all module types, including the Lock and Unlock methods used for threadsafe operation.
CAtlModuleT CAtlModuleT is a template class that derives from CAtlModule. It acts as a base class for the following three module classes and contains basic registration functionality.
CAtlDllModuleT CAtlDllModuleT is a template class that derives from CAtlModuleT. It provides support for DLL servers, including implementations of the DllMain function, plus the four functions always exported by DLL servers (DllGetClassObject, DllRegisterServer, DllUnregisterServer, and DllCanUnloadNow).
CAtlExeModuleT CAtlExeModuleT is a template class that derives from CAtlModuleT. It provides support for EXE servers, including parsing the command line, registering and revoking class objects, and managing interaction with the message loop.
CAtlServiceModuleT CAtlServiceModuleT is a template class that derives from CAtlModuleT. It provides support for ATL servers implemented as Windows Services, with functionality that includes parsing the command line and installing, registering, and uninstalling the service.

The generated code will contain a module object called _AtlModule, which will be of one of the three CAtlXxxModuleT types: CAtlDllModuleT if the module type was given as dll (or if no type was specified), CAtlExeModuleT if the module type was given as exe, or CAtlServiceModuleT if the module type was given as service.


TIP:
You can see the generated code if you use the /Fx compiler switch, and examine the .mrg file that is created.

In addition, CAtlExeModuleT implements a timeout mechanism, which can be used to improve server performance in cases where large numbers of objects are being created and destroyed. The COM rules state that when the last object supported by a server has been destroyed, the server can exit. If the Boolean m_bDelayShutdown member of CAtlExeModuleT is set to true, the server will not shut down until the period specified by m_dwTimeOut has expired. The default timeout is 5 seconds.

The custom Attribute

This attribute provides a general mechanism for adding metadata to type libraries and can be applied to modules, interfaces, and coclasses. It takes two parameters:

  • A GUID that identifies the metadata
  • A value, which can be anything that will fit into a VARIANT

The GUID used to identify the metadata has no meaning except to clients that are looking for it. Use of this attribute on C++ classes will result in an equivalent IDL custom attribute being added to the IDL and compiled into the type library. Clients must use the type library interfaces (ITypeLib and ITypeInfo) to retrieve details of custom attributes.

As an example of the use of this attribute, consider the following fragment of Visual C# code, which is generated when a .NET component is being exported for use as a COM object:

[
   odl, uuid(FEDF4CC0-E0ED-3DC4-ABE7-E5B4BBC78D84),
   hidden, dual, nonextensible, oleautomation,
   custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, ExportVb.Class1) 
]
interface _Class1 : IDispatch {
  // ...
};

For exported .NET components, the custom attribute is used to specify the namespace to which the .NET component belongs. When the COM Callable Wrapper (CCW) is created to expose a .NET instance as a COM object, the CCW can find the namespace information from the type library.

Creating Interfaces

In previous versions of ATL, interfaces were defined directly in IDL and implemented in the ATL source class. Wizard support in Visual Studio would add implementation code to the ATL class files, but there were several disadvantages with the way this process worked. First there was often a lot of manual work to be done, especially when adding extra interfaces to a class. Second, doing anything out of the ordinary required a detailed knowledge of IDL. This was unsatisfactory because IDL is a complex and subtle language, requiring extra knowledge and skills on the part of the C++ programmer.

Using attributes, the programmer now codes an interface definition directly in C++, using attributes to provide all the information that would have been given in the IDL file. In many cases, there is a one-to-one mapping between .NET COM attributes and IDL constructs, so if you already know how to construct and edit IDL, you’ll be able to quickly transfer to using attributes.

As the previous walkthrough explained, COM interfaces are defined using the new __interface keyword. Interfaces defined in this way have the following properties:

  • They can inherit from zero or more base interfaces.
  • They cannot inherit from a base class.
  • They can contain only public, pure virtual methods.
  • They cannot contain constructors or destructors.
  • They cannot define overloaded functions or operator overloads.
  • They cannot contain static methods.
  • They cannot contain data members, although pure virtual properties are allowed.
  • Methods use the __stdcall calling convention.

Note that the __interface keyword is not only used for COM interface definitions, it can also be used for defining both managed and unmanaged C++ interfaces. To define a COM interface, you need to apply the object attribute to the interface definition, as you also have to do in IDL interface definitions.

// A non-COM interface
__interface INonCOM
{
   // Interface definition
};

// A COM interface
[ object ]
__interface ICOM : IUnknown
{
   // Interface definition
};

Once the compiler sees the object attribute, it will apply the rules that COM interfaces and interface methods have to follow. For example, since all COM interfaces are based on IUnknown, COM interfaces defined using __interface must inherit from IUnknown or another COM base interface.

Attributes are used to provide information to the compiler to enable it to generate an IDL file for compilation by MIDL. The attributes that can be used with interfaces are shown in Table 6-3, and you will see that they parallel many attributes used in an IDL interface definition.

Table 6-3 Attributes Used with Interfaces

Attribute Description
async_uuid Generates both synchronous and asynchronous versions of the interface. A GUID must be provided to be used as the interface ID for the asynchronous interface.
custom Inserts one or more IDL custom attributes, each of which is identified by a GUID and a value.
dispinterface Defines an interface as being a pure dispatch interface, only usable by late-bound clients. Dispinterfaces must have IDispatch at the top of their inheritance hierarchy.
dual Defines an interface as being a dual interface, usable by early-bound or late-bound clients. Dual interfaces must have IDispatch at the top of their inheritance hierarchy.
export Indicates that an interface should be included in the IDL. This attribute is not normally needed, as COM interface definitions are automatically included in the generated IDL.
helpcontext The help context ID for this interface.
helpfile The help file for this interface.
helpstring The help string to display for the interface.
helpstringcontext The help context ID for this interface.
hidden Adds the hidden IDL attribute to the interface, indicating that it should not be shown in the Visual Basic Object Browser.
library_block By default, only interfaces that are used in coclass definitions are compiled into the type library. An interface tagged with the library_block attribute will be placed in the library block in the IDL, and therefore compiled into the type library regardless of whether it is referenced by a coclass.
local The local attribute can be used on an interface to cause the MIDL compiler only to create header files.
nonextensible When applied to an Automation interface, this attribute indicates that the interface cannot be extended at run time.
object Defines the interface as being a COM interface, as opposed to being a managed or unmanaged non-COM interface. All COM interface definitions will include this attribute.
odl Marks the interface as an ODL interface. This attribute is supported only for backward compatibility and is not required.
oleautomation Marks an interface as being compatible with OLE Automation. This means the parameters and return types for methods in the interface must be Automation-compatible.
pointer_default Specifies the default pointer attribute for pointers used in this interface. This attribute can take one of three parameters: ptr, ref, or unique. These correspond to the equivalent IDL pointer attributes.
restricted Denotes that an interface cannot be called arbitrarily. This attribute is used to restrict the interfaces that can be used from scripting languages.
uuid Specifies an interface ID for the interface. If this attribute is not supplied, a GUID will automatically be generated.

The sample code in the earlier section "Adding Interface and Coclass Definitions" showed how these attributes can be applied to an interface.

Defining Interface Methods

An interface is a collection of pure virtual function definitions, but you don’t use C++ virtual function syntax to declare them. You declare them as simple function prototypes, and they will be assumed to be public and pure virtual, as shown in the following example:

// A COM interface
[object]
__interface ICOM : IUnknown
{
   // Method is assumed to be public and pure virtual
   HRESULT AMethod(int n);
};

COM interface methods are required to return HRESULTs, and the compiler will issue an error if you try to use any other return type in an interface that has the object attribute.

Table 6-4 lists the most common attributes that can be used on methods, parameters and return types.

Table 6-4 Attributes That Can Be Applied to Interface Methods

Attribute Description
bindable Indicates that a property supports data binding.
helpstring Provides a help string for a method.
hidden Indicates that an interface method should not appear in object browser utilities.
id Provides a dispatch ID (dispID) for a method.
in Indicates that a method parameter should be marshaled from the caller to the callee.
local Denotes a local method for which no marshalling code is generated.
nonbrowsable Indicates that an interface method should not appear in object browser utilities.
out Indicates that a method parameter should be marshaled from the callee back to the caller.
propget Used to label property accessor methods.
propput Used to label property setting methods.
propputref Used to label a property setting method that uses a reference rather than a value.
ptr Designates a pointer as a full pointer, which can be null, and can change its value.
retval Indicates that a method parameter can be used as a function return value. There can be only one [retval] parameter, and it must be the last parameter in the method’s parameter list.
string Indicates that an array of, or pointer to, types char, wchar_t, or byte should be treated as a C-style null-terminated string.
synchronize Causes the method to be synchronized, via calls to Lock and Unlock placed at the beginning and end of the method code.

Interface methods defined using attributes look similar to their IDL counterparts and use the same rules—for example, out parameters must be pointers, and the retval parameter must occur at the end of the parameter list. The following code sample illustrates this:

// Example attributed interface method
[id(3), helpstring("method Square")] HRESULT Square(
                      [in] SHORT val, [out,retval] LONG* pResult);

Dispatch Interfaces

Interfaces can be marked with the dual or dispinterface attributes to show that they can be called by late-bound clients using Automation. All methods in dispatch interfaces must define dispatch IDs (dispIDs) for their methods. These IDs are positive integers.

You use the id attribute to assign dispIDs to methods within an interface definition:

[id(2)] HRESULT MethodOne();
[id(3)] HRESULT MethodTwo();

The compiler will check for duplicate dispIDs, and if any are found, MIDL compilation will fail.

Handling Arrays

You might notice that the list of interface method attributes does not contain any IDL attributes used when passing arrays by pointer (size_is, length_is, and so on). Although these are not listed as COM attributes, you can still use them when defining methods in COM interfaces, and they will be passed through into the generated IDL. For example, here’s the definition of a method that computes the sum of an array of values:

HRESULT Sum([in] short nVals, [in, size_is(nVals)] short* pArray,
     [out,retval] long* pSum);

Creating Coclasses

The coclass attribute is used to mark a class that implements a COM coclass. The attribute can be applied to classes and structs, and it takes no arguments. In its simplest form, it can be used alone, like this:

[coclass]
class MyComClass : public ISomeInterface
{
   // ...
};

When the coclass attribute is applied to a class in an ATL project, the following changes will be made to the class:

  • ATL base classes are added.
  • If the class inherits from any dual interfaces that are not defined using attributes, these dual interfaces are replaced with the corresponding IDispatchImpl class. If a base dual interface is defined using attributes, the interface is not modified in the base class list.
  • A COM map is added, list ing all interfaces implemented by the target class, all interfaces specified using com_interface_entry attributes, and those introduced via aggregates attributes.
  • An OBJECT_ENTRY_AUTO entry is placed in the COM map, which has the effect of entering the class into the COM map, updating the registry, and creating an instance of the object.

The following base classes will be added in the generated code:

  • CComCoClass, to implement the class factory and aggregation behavior.
  • CComObjectRootEx, to provide behavior specific to the threading model. If no threading attribute is provided, apartment-model threading is assumed.
  • IProvideClassInfo2Impl, to provide a default implementation of the IProvideClassInfo and IProvideClassInfo2 interfaces. If the noncreatable attribute is specified for the class, IProvideClassInfo2Impl will not be added as a base class.
These classes provide, among other features, registry entry handling (autoregistration), a class factory, and an IUnknown implementation.

The use of the coclass attribute also adds a number of member functions to the target class:

  • UpdateRegistry, to register the class factory for the class.
  • GetObjectCLSID, to return the component’s CLSID.
  • GetObjectFriendlyName, to return a string of the form "target_class_name Object". This function can be explicitly overridden to return another name.
  • GetProgID, to return a string containing the component’s progID.
  • GetVersionIndependentProgID, to return a string containing the component’s version-independent progID.

Table 6-5 lists the attributes that can be used on coclasses.

Table 6-5 Attributes That Can Be Applied to coclasses

Attribute Description
aggregatable This attribute denotes that a coclass can be aggregated. See the upcoming "Handling Aggregation" section for more details.
aggregates This attribute specifies the COM coclasses that a coclass will aggregate. See the upcoming "Handling Aggregation" section for more details.
coclass Specifies that a C++ class implements a COM coclass.
com_interface_entry Adds an entry to the COM_MAP for the class.
control Specifies that a coclass implements an ActiveX control.
custom Adds a custom entry to the type library.
default Indicates which interfaces should be used as the default interfaces for the coclass.
event_source Denotes an event source. See the upcoming "Events" section for details on how this attribute is used.
event_receiver Denotes that this class receives events. See the upcoming "Events" section for details on how this attribute is used.
helpcontext Provides a context ID within a help file that can be accessed for more help about the class.
helpfile Provides the name of the help file associated with this class.
helpstring Provides a help string that can be displayed in object browsers and other end-user tools.
helpstringcontext Specifies the ID of a help topic in an .hlp or a .chm help file.
hidden Indicates that this item should not be displayed in object browsers and other end-user tools.
implements By default, only COM interfaces that are base classes are added to the IDL coclass definition. This attribute forces other interfaces to be added to the IDL definition, even if they are not bases.
implements_category Adds an implemented category to the CATEGORY map for the class. Clients can query this map at run time to determine the categories that are implemented by a class.
licensed Indicates that this class uses the ActiveX licensing model and must be created using the IClassFactory2 interface.
noncreatable Specifies that this class cannot be created by COM. This attribute is normally used on COM types that are created by other methods, such as the C++ new operator.
progid Adds a progID for the class. The parameter to this class is a string containing the progID in the usual typelib.coclass.version format.
registration_script Supplies the path to a registration script (.rgs) file that will be used to register the class. If this attribute is not supplied, a default registration script will be generated. To disable automatic registration, use this attribute with the special path none.
requires_category Adds a required category to the CATEGORY map for the class. Clients can query this map at run time to determine the categories that need to be implemented by users of a class.
restricted Indicates that this coclass cannot be used arbitrarily. This attribute is used to restrict the use of classes by scripting clients.
source Defines the outgoing (source) interfaces supported by a class. See the upcoming "Events" section for details on how this attribute is used.
support_error_info Adds support for the ISupportErrorInfo interface to the class. See the upcoming "Handling Errors" section for more details.
threading Specifies the threading model for a class. The parameter for this attribute can be one of the following values: apartment, neutral, single, free, or both.
uuid Specifies a CLSID for the class, as a string. If this attribute is not used, a CLSID will automatically be generated for the class.
version Specifies a version for the class, which will be used to provide the type library block version.
vi_progid Specifies a progID without a version.

Default Interfaces

COM coclasses support default incoming and outgoing interfaces so that client code—especially Visual Basic 6—can simply create an object without specifying which interface on the object is to be used. These are indicated by applying the default attribute to the C++ class, as shown in the following code fragment:

[
  coclass,
  default(ITwo)
]
class MyComClass : public IOne, public ITwo
{
  // ...
};

If your class implements source interfaces, you can provide a second parameter that specifies the default source interface. If you don’t use the default attribute, the first base interface will be taken as the default outgoing interface, and the first source interface will be taken as the default source.

Stand-Alone Attributes

A number of COM-related attributes are not applied to classes, interfaces, or methods, but are used as stand-alone attributes. Table 6-6 lists all the stand-alone attributes.

Table 6-6 Stand-Alone Attributes

Attribute Description
cpp_quote This attribute is used to pass quoted strings through MIDL compilation into the generated header files. Quotes are stripped from the string before it is inserted into the header file.
custom Inserts a custom IDL attribute into the IDL file.
emitidl Determines how IDL generation proceeds. See the upcoming "The emitidl Attribute" section for further details.
idl_quote Some IDL constructs are not implemented via attributes. The idl_quote attribute lets you pass unsupported IDL constructs directly into the generated IDL file.
import Places a #import statement in the IDL that causes the inclusion of another .idl, .odl, or header file.
importidl Merges the content of another .idl file. Any IDL inside the library block of the inserted file is merged into the library block of the file in which the importidl attribute occurs. IDL outside the library block will be placed outside the library block of the file in which the importidl attribute occurs.
importlib Imports types from another type library so that they can be referenced in IDL.
include Causes a #include statement to be placed in the IDL after the import "docobj.idl" statement.
includelib Causes an IDL or .h file to be included in the generated IDL after the importlib statement. This attribute is repeatable.
module Declares a module. See the "Creating Modules" section earlier in the chapter for more details.
no_injected_text Can be used to prevent the compiler from injecting text. This is placed in the merged code generated by the /Fx compiler option, and ensures that attributes will not be processed a second time if the merge file is compiled.
pragma This attribute is used to pass a string to the generated header files without any processing by the MIDL compiler. The pragma pack attribute can be used to control how the MIDL compiler will pack structures.

Because these attributes are not part of any other C++ construct, they form statements in their own right and will therefore always end with a semicolon when used in code. For example, look at this code:

[ module(dll, uuid = "{1D196988-3060-486E-A4AC-38F9685D3BF7}", 
         name = "SimpleObject", 
         helpstring = "SimpleObject 1.0 Type Library",
         resource_name = "IDR_SIMPLEOBJECT") ];

The emitidl Attribute

The emitidl attribute controls how IDL attributes will be processed. The format of this attribute is shown in the following code fragment:

[emitidl(value, defaultimports=boolean];

The value parameter can take one of four values:

  • true, meaning that IDL attributes encountered in the code will be processed and added to the generated IDL file. This is the default if this value is omitted.
  • false, meaning that IDL attributes encountered in the code will not be processed.
  • restricted, meaning that IDL attributes can be present in a file that doesn’t include a module attribute. No IDL file will be generated.
  • forced, meaning that the file must contain a module attribute if it also contains IDL attributes.

The defaultimports named parameter is optional and takes a Boolean value. If the value is false, the standard docobj.idl file (which contains all the Microsoft-defined COM and OLE IDL) will not be implicitly included in the generated IDL. If you want to include any standard IDL files—such as ocidl.idl—you’ll have to include them manually.

If the value of defaultimports is true, docobj.idl will be included. If an .idl file with the same name as an .h file that you #include into your source code is found in the same directory as the .h file, the generated .idl file will contain an import statement for that .idl file. Here is an example showing how the emitidl attribute can be used:

[emitidl(true, defaultimports=true];

Handling Aggregation

This section explains how the aggregatable and aggregates attributes are used to control the aggregation behavior of COM components.

Review of COM Aggregation and Delegation

Before discussing keywords, I’ll provide a quick review of COM aggregation and delegation, because the distinctions between the two are often not understood very well by COM programmers. Delegation means that one COM object creates another and delegates method calls on one or more interfaces to the created object, as shown in Figure 6-7.

Figure 6-7 COM delegation does not expose the delegated object to the client. (Image unavailable)

Client code sees only the outer object, and the inner object is private. Lifetime and interface management is simple because the inner object is used only by its creator. One of the main advantages of delegation lies in being able to selectively expose functionality from inner objects: you can filter the data being passed to calls or even decide not to expose some interfaces or methods at all. The main disadvantage of delegation is that the outer object has to be written to perform the delegation. Delegation has no special support from ATL because the code needed to work with a delegated object will be different in each case.

If delegation is essentially a code reuse mechanism, aggregation is a COM identity trick used to create one logical COM object out of two or more physical objects. When one COM object aggregates another, it provides the identity—the GUID—for the aggregate, but this time the inner object exposes its interfaces directly to the client. This is shown in Figure 6-8.

Figure 6-8 COM aggregation exposes aggregated interfaces to the client. (Image unavailable)

The outer object creates the inner object, and the client—under the impression that it is talking to a single object—talks directly to both of them. This necessitates cooperation between the inner and outer objects to maintain reference counts and handle QueryInterface calls. This support is provided by ATL.

The aggregatable and aggregates Attributes

The aggregatable attribute is used to indicate whether a coclass wants to be aggregated. It can take one of three values:

  • allowed, meaning the coclass can be instantiated as aggregated or stand-alone.
  • never, meaning the coclass cannot be instantiated as an aggregated object. If an attempt is made to do so, the class factory will return CLASS_E_NOAGGREGATION.
  • always, meaning the coclass must be instantiated as an aggregated object. If an attempt is made to create a stand-alone object, the class factory will return E_FAIL.

Here’s an example of the attribute in use:

[coclass, aggregatable(always)]
class MyClass
{
   // ...
};

These values correspond to the yes, no, and always choices presented by the ATL Object Wizard. The default attribute parameter is allowed, so you won’t see this attribute in the generated code if you choose the yes option in the wizard.

The aggregates attribute is used to specify one or more objects that are going to be aggregated by a class. For example

// This class aggregates instances of the CObject1 
// and CObject2 classes
[coclass, 
  aggregates(__uuidof(CObject1)),
  aggregates(__uuidof(CObject2))
]
class MyClass
{
};

By default, the aggregates attribute will add a COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND entry to the object’s interface map, which will expose all the interfaces in the aggregated object. If you don’t want to do this, you can use the com_interface_entry attribute to specify individual interfaces on aggregated objects, like this:

[coclass, 
  com_interface_entry(
       "COM_INTERFACE_ENTRY_AGGREGATE(__uuidof(IAbc), pUnk)")
]
class MyClass
{
   //...
};

In this example, the COM_INTERFACE_ENTRY_AGGREGATE macro puts an entry in the interface map so that all calls to interface IAbc are forwarded to the object whose IUnknown pointer is passed as the second parameter. This second parameter will typically be a data member of the class to which the attribute is being applied.

Handling Errors

It is a well-known fact that COM interface methods return HRESULTs to indicate status. If you want to return more information than is encapsulated in the standard set of COM HRESULTs—the ones you can find in <winerror.h>—you should implement the ISupportErrorInfo interface, and then use the AtlReportError function or the CComCoClass<>::Error methods to report errors.

The support_error_info attribute is used to implement rich error handling in ATL classes. It can be included in a project by checking the appropriate box in the ATL Object Wizard, and you can also add it by hand to manually written code. The attribute takes as its argument the name of the interface for which error support is to be generated. You can add more than one support_error_info attribute if you want to provide rich error information for more than one interface. This will result in the implementation of InterfaceSupportsErrorInfo, which contains details of all the interfaces you’ve named in support_error_info attributes. The following example will add rich error support for both the IOne and ITwo interfaces:

[coclass, 
  support_error_info("IFoo"),
  support_error_info("IFoo2")
]
class MyClass
{
   //...
};

Events

COM provides connection points as a standard mechanism for connecting event sources to event sinks. Although they provide a useful standard that can be used between event sources and sinks that have been separately developed, they are expensive (in terms of interface calls) and are therefore normally used only for in-process components.

Attributes can be used to implement connection points by using the event_source attribute with the com parameter. For example:

[coclass, event_source(com)]
class MyClass : public ISomeInterface
{
};

Note that event_source isn’t used only in COM code. This attribute can be used to generate three different kinds of events, depending on the first parameter:

  • com is used to generate COM connection points.
  • native is used to generate events using C++ callbacks.
  • managed is used to generate .NET events and can be used only with managed classes.

Because the syntax is similar for all three types, Microsoft has rather grandly termed this the Unified Event Model. As we’re dealing with implementing COM objects in this chapter, I’ll be considering only the first type.

Adding Event Support

It is easy to add event support if you are creating an ATL object with Visual Studio .NET. Simply check the Connection Points box on the Options page of the ATL Object Wizard. The following paragraphs show you what gets added to an ATL project to support connection points.

The first item that has been added to the generated code is an event_source attribute on the implementation class, showing that the class supports COM connection points:

[
    coclass,
    threading("apartment"),
    event_source("com"),
    
vi_progid("SimpleJ.SimpleK"),
    progid("SimpleJ.SimpleK.1"),
    version(1.0),
    uuid("C57EA18F-2869-4CD8-BB36-7743B63F718E"),
    helpstring("SimpleK Class")
]
class ATL_NO_VTABLE CSimpleK : 
    public ISimpleK
{ ...

A coclass that implements connection points also has to support a source interface, so you’ll see that the wizard has added a dispinterface to the file:

// _ISimpleKEvents
[
    dispinterface,
    uuid("5D2B0BD7-5CD8-46E0-9855-2F6B898D0B0C"),
    helpstring("_ISimpleKEvents Interface")
]
__interface _ISimpleKEvents
{
};

The dispinterface event is associated with the coclass using the __event keyword:

class ATL_NO_VTABLE CSimpleK : 
    public ISimpleK
{
public:
    CSimpleK()
    {
    }

    __event __interface _ISimpleKEvents;
   ...
};

This declaration associates _ISimpleEvents as a source interface for the class and will also cause the provider to generate a method that you call to fire the event. You can now add the definitions of the event methods to dispinterface:

__interface _ISimpleKEvents
{
   // Return two values when asked
   [id(1), helpstring("method Values")] 
            void Values([in] short x, [in] short y);
};

When you compile the project with the /Fx option set and look at the .mrg file, you’ll see the compiler has generated a Values method as part of the coclass implementation. To fire this method, you simply call it, and because it has the same name and arguments as the dispinterface method, it doesn’t matter that the method is hidden from you.

You can also use the __raise keyword when firing events:

__raise Values(10,10);

This keyword "emphasizes the site of an event," to quote MSDN. It is optional and has two effects: it makes it easy to see where events are being raised; and it causes a runtime error if it is used with a function that isn’t an event. The second effect helps guard against accidentally calling the wrong function.

Manually Writing COM Event Code

The following code shows how to define a COM class that fires events if you are not using Visual Studio .NET. Listing 6-4 contains the header file, EvtSrc.h, which defines the GUID for a class called CEventSource. You’ll find this file in the Chapter06\Events\Src folder in the book’s companion content.

#pragma once

[ dual, uuid("d4efa6dc-bb8f-44f0-88cf-2ae663c76312") ]
__interface IEvents {
   [id(1)] HRESULT Values([in] short nVal1, [in] short nVal2);
};

[ dual, uuid("a3b7fea2-7396-4727-9691-7dc55acca27a") ]
__interface ISource {
   [id(1)] HRESULT Fire();
};

class DECLSPEC_UUID("530DF3AD-6936-3214-A83B-27B63C7997C4") CEventSource;

Listing 6-4 EvtSrc.h

CEventSource is a COM class that supports one interface, ISource, and one event interface, IEvents. The ISource interface has one member, Fire, which is used to raise the event defined in the IEvents source interface. The EvtSrc.cpp file implements this COM class in Listing 6-5. This file is also located in the Chapter06\Events\Src folder in book’s companion content.

#define _ATL_ATTRIBUTES 1
#include <atlbase.h>
#include <atlcom.h>
#include "EvtSrc.h"

[ module(DLL, name="EventSource", uuid="6E46B59E-89C3-4c15-A6D8-B8A1CEC98830") ];

[coclass, event_source(com), uuid("530DF3AD-6936-3214-A83B-27B63C7997C4")]
class CEventSource : public ISource {
public:
   __event __interface IEvents;

Listing 6-5 EvtSrc.cpp

   // This method fires the event
   HRESULT Fire() {
      __raise Values(10,12);
      return S_OK;
   }
};

The first thing to note is that to define events in this way, you need to include the ATL header files and define the _ATL_ATTRIBUTES preprocessor symbol. The CEventSource class defines an event member and uses the optional __raise keyword to fire the event when the Fire method is called.

You can build this code into a DLL and register the COM coclass by using the following command lines:

cl /LD EvtSrc.cpp
regsvr32 EvtSrc.dll

In the next section, you’ll see how to handle the events raised by this component.

Handling Events

A class that is going to handle connection point events should be created with the event_receiver attribute. Like event_source, this attribute takes a com parameter to show that it wants to use connection points rather than native or managed events. The class also needs to implement the methods in the sink interface, which in practice means defining handler methods that have the same signature. Note that the class does not have to derive from the interface directly; the handler methods will be dynamically hooked up to the source object at run time.

Listing 6-6 shows how to handle the event fired by the CEventSource class that was defined in the previous section. You can find this file in the Chapter06\Events\Client folder in the book’s companion content.

#define _ATL_ATTRIBUTES 1
#include <atlbase.h>
#include <atlcom.h>

#include <iostream>
using namespace std;

#include "..\Src\EvtSrc.h"

// Define a module
[ module(name="EventTest") ];

Listing 6-6 EvtClient.cpp

// Define an event handler class
[ event_receiver(com) ]
class EventHandlerClass {
public:
   HRESULT Handler1(short nVal1, short nVal2) {
      cout << "Handler1 called with values " << nVal1 << " and " 
           << nVal2 << endl;
      return S_OK;
   }

   HRESULT Handler2(short nVal1, short nVal2) {
      cout << "Handler2 called with values " << nVal1 << " and " 
           << nVal2 << endl;
      return S_OK;
   }

   // Hook up two handlers to the same event source
   void HookEvent(ISource* pSource) {
      __hook(&IEvents::Values, pSource, 
                   &EventHandlerClass::Handler1);
      __hook(&IEvents::Values, pSource, 
                    &EventHandlerClass::Handler2);
   }

   // Unhook the event handlers
   void UnhookEvent(ISource* pSource) {
      __unhook(&IEvents::Values, pSource, 
                   &EventHandlerClass::Handler1);
      __unhook(&IEvents::Values, pSource, 
                   &EventHandlerClass::Handler2);
   }
};

int main() {
   // Create COM object
   CoInitialize(NULL);
   ISource* pSource = 0;
   HRESULT hr = CoCreateInstance(__uuidof(CEventSource), NULL, 
            CLSCTX_ALL, __uuidof(ISource), (void **) &pSource);
   if (FAILED(hr)) {
      cout << "Error creating CEventSource object: " << hex 
           << hr << dec << endl;
      return -1;
   }

   // Create the handler object, and set up  the event notification
   EventHandlerClass theHandler;
   theHandler.HookEvent(pSource);

   // Fire the event
   pSource->Fire();

   // Unadvise
   theHandler.UnhookEvent(pSource);

   CoUninitialize();

   return 0;
}

The code defines a class that is going to be used to handle COM events and which is therefore tagged with the event_receiver(com) attribute.

Two new keywords are used to handle events: __hook and __unhook. The __hook keyword generates an advise call to the object, telling it to start sending events, while __unhook undoes the advise. To use __hook, you need to pass three things:

  • The address of the event. When handling events from a COM object, this must be the address of an event function on an interface, not on the COM class itself.
  • A pointer to the source object.
  • The address of the handler function.

Once the call to __hook has returned, the handler function will be called each time a Values event is fired. To stop being sent events, __unhook is called with the same parameters. In this example, two handler functions are hooked to the same event to show it’s possible to have more than one handler function for an event.

Compiler Options

There are four linker options that can be used to control the output of IDL and type library files for an attributed COM project. They are listed in Table 6-7 and explained in the following subsections.

Table 6-7 Linker Options Used with Attributed Code

Option Description
/idlout Specifies the name of an IDL file in which the compiler will save the IDL generated for the project
/ignoreidl Specifies that IDL attributes present in the source should not be used to create an IDL file
/midl Used to specify the name of a file containing MIDL command-line options
/tlbout Specifies the name of the type library created by the compiler

Generating IDL

The /idlout and /tlbout options can be used to specify the base names for IDL and type library files. If you don’t use either of these options, the linker will use vc70 as the base name for the generated files, creating vc70.idl, vc70.tlb, and so on.

If you just specify /idlout and /tlbout, all the MIDL-generated files (*.tlb, *.idl, *_i.c, *_p.c, and *.h) will take their names from the filename you supply. You can specify both options to generate a type library with a different name to the IDL file. In this case, the IDL file name is used to name all the generated files except the type library.

You use the options on the command line like this:

cl /LD MyServer.cpp /link /tlbout:foo.tlb

Since /tlbout is a linker option, you need to place it after the /link option.

Suppressing IDL Generation

By default, IDL attributes in source code will be processed and used to create an IDL file, which is then processed by MIDL in the usual way. If you want to suppress the generation of an IDL file for some reason, use the /ignoreidl linker option.

Summary

This chapter has described attributed programming, an important upgrade to ATL that makes it much easier to write COM components in C++ using the ATL library. Many ATL developers will find they can now write COM components without having to interact with IDL or the ATL source code.

This chapter has shown you how to apply COM attributes to C++ code, and it has discussed all the common COM-related attributes. You should now be in a position to use attributes to produce COM components more simply than before, while still retaining control over the way in which they are implemented in ATL and represented in a type library.

The next chapter discusses the other major addition to ATL: the set of classes introduced for writing plugins for web servers, which are collectively known as ATL Server. You’ll learn which classes form part of the ATL Server library and how to use them to create server applications.

Read More Show Less

Table of Contents

Acknowledgments

Introduction

Part I: The .NET View of COM and COM+

Chapter 1: COM and .NET

Chapter 2: COM+ and .NET

Chapter 3: Using COM Components in .NET Code

Chapter 4: Using .NET Components in COM Applications

Chapter 5: An Overview of COM+ Coding for .NET

Part II: Writing COM Code

Chapter 6: Attributed Programming

Chapter 7: ATL and ATL Server

Part III: Writing COM+ Code

Chapter 8: A Simple COM+ Example

Chapter 9: Working with Disconnected Applications

Chapter 10: Creating Subscriptions

Chapter 11: Web Application Scenarios

Part IV: Interoperability

Chapter 12: Interacting with Unmanaged Code

Chapter 13: Advanced Interaction

Chapter 14: Working with Predefined Interfaces

Appendix : About the Author

Appendix : Air Compressor

Read More Show Less

Customer Reviews

Be the first to write a review
( 0 )
Rating Distribution

5 Star

(0)

4 Star

(0)

3 Star

(0)

2 Star

(0)

1 Star

(0)

Your Rating:

Your Name: Create a Pen Name or

Barnes & Noble.com Review Rules

Our reader reviews allow you to share your comments on titles you liked, or didn't, with others. By submitting an online review, you are representing to Barnes & Noble.com that all information contained in your review is original and accurate in all respects, and that the submission of such content by you and the posting of such content by Barnes & Noble.com does not and will not violate the rights of any third party. Please follow the rules below to help ensure that your review can be posted.

Reviews by Our Customers Under the Age of 13

We highly value and respect everyone's opinion concerning the titles we offer. However, we cannot allow persons under the age of 13 to have accounts at BN.com or to post customer reviews. Please see our Terms of Use for more details.

What to exclude from your review:

Please do not write about reviews, commentary, or information posted on the product page. If you see any errors in the information on the product page, please send us an email.

Reviews should not contain any of the following:

  • - HTML tags, profanity, obscenities, vulgarities, or comments that defame anyone
  • - Time-sensitive information such as tour dates, signings, lectures, etc.
  • - Single-word reviews. Other people will read your review to discover why you liked or didn't like the title. Be descriptive.
  • - Comments focusing on the author or that may ruin the ending for others
  • - Phone numbers, addresses, URLs
  • - Pricing and availability information or alternative ordering information
  • - Advertisements or commercial solicitation

Reminder:

  • - By submitting a review, you grant to Barnes & Noble.com and its sublicensees the royalty-free, perpetual, irrevocable right and license to use the review in accordance with the Barnes & Noble.com Terms of Use.
  • - Barnes & Noble.com reserves the right not to post any review -- particularly those that do not follow the terms and conditions of these Rules. Barnes & Noble.com also reserves the right to remove any review at any time without notice.
  • - See Terms of Use for other conditions and disclaimers.
Search for Products You'd Like to Recommend

Recommend other products that relate to your review. Just search for them below and share!

Create a Pen Name

Your Pen Name is your unique identity on BN.com. It will appear on the reviews you write and other website activities. Your Pen Name cannot be edited, changed or deleted once submitted.

 
Your Pen Name can be any combination of alphanumeric characters (plus - and _), and must be at least two characters long.

Continue Anonymously

    If you find inappropriate content, please report it to Barnes & Noble
    Why is this product inappropriate?
    Comments (optional)