Walkthrough: Creating a Simple COM Component

team lib

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 youve created the object, youll 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.

click to expand
Figure 6-2: The Attributed check box governs whether attributed code will be produced by the ATL Project Wizard.

Youll 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 youll find that it contains a stand-alone module attribute, similar to the one below:

 //ThemoduleattributecausesDllMain,DllRegisterServerand //DllUnregisterServertobeautomaticallyimplementedforyou [module(dll,uuid="{1D196988-3060-486E-A4AC-38F9685D3BF7}", name="SimpleObject", helpstring="SimpleObject1.0TypeLibrary", 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 youre 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 youre not using Visual Studio, simply add /Fx to the compiler command line.

Compile the project, and youll 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_line7"c:\writing\combook\simpleobject\simpleobject.cpp" extern"C"STDAPIDllRegisterServer(void) { return_AtlModule.DllRegisterServer(); } #injected_line7"c:\writing\combook\simpleobject\simpleobject.cpp" extern"C"STDAPIDllUnregisterServer(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, youll see the definition of a module class. Note that the following code has been reformatted to make it easier to read:

 //+++StartInjectedCodeForAttribute'module' #injected_line7"c:\writing\combook\simpleobject\simpleobject.cpp" classCSimpleObjectModule:publicCAtlDllModuleT<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. Youll 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.

click to expand
Figure 6-3: The ATL Simple Object Wizard lets you set the names of files, classes, and COM identifiers.

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

click to expand
Figure 6-4: The Options pane lets you specify COM properties for the coclass.

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

When the wizard completes, the .h and .cpp files for the implementation class will have been generated and added to the project. Youll also note that theres no IDL file in the project, as everything you would have specified in the IDL is now added using attributes. As youd 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, youll see an interface definition that looks like this:

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

The COM interface is defined directly in C++, using the __interface keyword. It is qualified with the same attributes that youd 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("SimplestClass") ] classATL_NO_VTABLECSimplest: publicISimplest { public: CSimplest() { } DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULTFinalConstruct() { returnS_OK; } voidFinalRelease() { } 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 doesnt expose any ATL implementation detailssuch as the ATL base classeswhich are provided when the code is injected by the attribute provider at compile time. Youll 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 usefulfor 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 doesnt inherit from IDispatchImpl :

 classATL_NO_VTABLECSimplest: publicISimplest , /*+++AddedBaseclass*/publicCComCoClass<CSimplest,&__uuidof(CSimplest)>, /*+++AddedBaseclass*/publicCComObjectRootEx<CComSingleThreadModel>, /*+++AddedBaseclass*/publicIProvideClassInfoImpl<&__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 havent changed, so its 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 youve done a build. You can open this file, and its content should be familiar. Dont 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 dont 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 isnt visible, select Class View on the View menu or press CTRL+SHIFT+C to display it. Expand the component nodewhich 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.

click to expand
Figure 6-5: Adding a method to an ATL class using the Add Method Wizard

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. Youll also find that the parameter attribute check boxes are sensitive to the parameter type that is, if you dont 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 dont press Finish before youve added the final one otherwise , it wont be added.

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

click to expand
Figure 6-6: Setting IDL attributes using the Add Method Wizard

As youd 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. Heres a sample IDL file that was produced for a simple component:

 import"c:\ProgramFiles\MicrosoftVisualStudio.NET2003\ Vc7\PlatformSDK\include\prsht.idl"; import"c:\ProgramFiles\MicrosoftVisualStudio.NET2003\ Vc7\PlatformSDK\include\mshtml.idl"; import"c:\programfiles\microsoftvisualstudio.net2003\ vc7\platformsdk\include\dimm.idl"; import"c:\ProgramFiles\MicrosoftVisualStudio.NET2003\ Vc7\PlatformSDK\include\mshtmhst.idl"; import"c:\programfiles\microsoftvisualstudio.net2003\ vc7\platformsdk\include\docobj.idl"; import"c:\ProgramFiles\MicrosoftVisualStudio.NET2003\ Vc7\PlatformSDK\include\exdisp.idl"; import"c:\ProgramFiles\MicrosoftVisualStudio.NET2003\ Vc7\PlatformSDK\include\objsafe.idl"; [ object, uuid(FE19D164-DB7D-4A17-8D99-DFD57FB69E02), dual, helpstring("ISimplestInterface"), pointer_default(unique) ] #line14"c:\writing\cm\combook\attributes\simpleobject\simplest.h" interfaceISimplest:IDispatch{ #line16"c:\writing\cm\combook\attributes\simpleobject\simplest.h" [propget,id(2),helpstring("propertyX")] HRESULTX([out,retval]short*pVal); [propput,id(2),helpstring("propertyX")] HRESULTX([in]shortnewVal); [id(3),helpstring("methodSquare")] HRESULTSquare([in]SHORTn, [out,retval]long*pResult); }; [version(1.0),uuid(1D196988-3060-486E-A4AC-38F9685D3BF7), helpstring("SimpleObject1.0TypeLibrary")] librarySimpleObject { importlib("stdole2.tlb"); importlib("olepro32.dll"); [ version(1.0), uuid(5BDCE1E1-D79D-41E1-9500-E4ED3E64887A), helpstring("SimplestClass") ] #line34"c:\writing\cm\combook\attributes\simpleobject\simplest.h" coclassCSimplest{ interfaceISimplest; }; } 

You can see thatapart from some import declarations and formattingit is very similar to the ATL IDL files youre 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 books companion content. This content is available from the books Web site at http://www.microsoft.com/mspress/books/6426.asp .

Listing 6-1: TestObject.cpp
start example
 #include<iostream> #include"atlbase.h" //Createwrappersforthecomponent #import"..\_SimpleObject.tlb"no_namespace usingnamespacestd; voidmain() { CoInitialize(NULL); { //Createaninstanceoftheobject CComPtr<ISimplest>pI; pI.CoCreateInstance(__uuidof(CSimplest)); //CalltheSquaremethod longres=0; res=pI->Square(3); cout<<"Resultis"<<res<<endl; } CoUninitialize(); } 
end example
 

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. Heres 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. Youll find this file in the Chapter06\HandCrafted folder in the books companion content.

Listing 6-2: Defs.h
start example
 #pragmaonce #defineSTRICT #ifndef_WIN32_WINNT #define_WIN32_WINNT0x0400 #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> usingnamespaceATL; 
end example
 

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 #define s 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 Ive called MyServer.cpp as shown in Listing 6-3. This file is also located in the Chapter06\HandCrafted folder in the books companion content.

Listing 6-3: MyServer.cpp
start example
 #include"Defs.h" //ThemoduleattributeisspecifiedinordertoimplementDllMain, //DllRegisterServerandDllUnregisterServer [module(dll,name="MyServer",helpstring="MyServer1.0TypeLibrary")]; [emitidl]; 
end example
 

The file simply contains two ATL attributes. The first attribute, module , youve already seen in the Visual Studio .NET example. It causes the generation of the ATL DLL container code, and in this case Im specifying a minimum number of parameters. I havent 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/LDMyServer.cpp regsvr32MyServer.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, youll find that IDL and type library files have been generated. Since you didnt specify a name for these files, the type library and IDL files both use the default vc70 root name; youll 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:Replacethisuuidwithonegeneratedusinguuidgen [ object, uuid("103FF9D9-8BC9-4ea8-8CD4-C1E627D04358"), dual,helpstring("ISimplestInterface"), pointer_default(unique) ] __interfaceISimplest:IDispatch { HRESULTSquare([in]shortval,[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++ codefor 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("SimpleClass") ] classATL_NO_VTABLECObject1:publicISimplest { public: CObject1(){} //Thesinglemethod HRESULTSquare(shortval,long*pResult){ *pResult=val*val; returnS_OK; } DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULTFinalConstruct() { returnS_OK; } voidFinalRelease() { } }; 

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 dont 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.

 
team lib


COM Programming with Microsoft .NET
COM Programming with Microsoft .NET
ISBN: 0735618755
EAN: 2147483647
Year: 2006
Pages: 140

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net