ActiveX is a strategic technology base for Internet programming and distributed computing. While ActiveX is the successor for OLE (Object Linking and Embedding), OLE still forms the foundation of ActiveX programming. The basis for ActiveX is to provide an object-oriented solution for solving problems encountered in developing operating systems and applications. ActiveX provides the specifications necessary to create component software that ultimately benefits the computing industry.
At the core of ActiveX is an extremely powerful and extensible architecture called the Component Object Model (COM). COM provides a simple yet elegant solution for solving complex software problems such as accessing objects and services outside of application boundaries and version control. COM solves these problems through the use of binary components that are running in the system rather than by developing source code components within an application.
If you are using the Visual C++ compiler from Microsoft, chances are very high that you are also using the Microsoft Foundation Classes (MFC) as the building blocks for your applications and components. MFC is a powerful set of base classes that provide a framework of abstractions into the Windows SDK for developing Win32 applications and components.
Classes within the MFC framework are not directly derived from COM interfaces. However, the architects of MFC have provided direct support for adding COM to any MFC-based component or application. The roots for supporting COM within MFC lie in wrappers called Interface maps. Interface maps are similar to message maps (which are used for distributing Windows messages to MFC classes) in both concept and execution.
COM Objects give software developers the ability to leverage object-oriented software techniques for solving application and operating system development issues. The COM specification is not geared toward a specific language, although C++ is a natural choice when developing COM Objects. Four basic components compose a COM Object:
The class ID (CLSID) is an identifier for the COM class. This key is registered in the Windows Registry and contains a pointer (path) to where the DLL (Dynamic Link Library) or EXE containing the class can be located. The CLSID can be found in the Windows Registry under the path HKEY_CLASSES_ROOT\CLSID.
The Interface ID (IID) is an identifier for the interface to the
class. The IID is used by applications to query and invoke the methods into
the class. The IID is also contained in the Windows Registry and can be
found in the path HKEY_CLASSES_ROOT\Interface. Figure 12.1 illustrates the relationship
among class, interfaces, and IID.
FIG. 12.1
Relationship of COM Classes and Interfaces.
What COM provides to software developers is an object-oriented solution for building and maintaining software solutions. Programmers using non-object-oriented languages such as Visual Basic can develop and use COM components to build software solutions.
COM also provides a unique solution to the version control problems present in many of today's software solutions. Since COM Objects are binary components, developers do not have to worry about new or updated versions of a component being placed on a computer where their application is running. The reason for this is that COM deals with interfaces. If an interface is enhanced, new methods can be added to the interface, or additional interfaces can be obtained without breaking an existing application. COM's solution to version control provides a great method for upgrading applications while preserving legacy systems.
When creating your COM Objects, a few tools must be installed on your computer. Most of these tools are automatically installed as part of the Visual C++ development environment.
The Microsoft MIDL compiler is now a standard component of the Microsoft Visual
C++ environment. The MIDL compiler compiles COM interface definitions into C code,
which is then compiled into the project by the Visual C++ compiler. Figure 12.2 illustrates
the purpose of the MIDL compiler.
FIG. 12.2
Inputs and outputs of the MIDL compiler.
The MIDL compiler also provides support for marshaling interfaces across process boundaries. Starting with Visual C++ 4.0, the MIDL compiler was shipped as a standard component of Visual C++. The MIDL compiler is also available with the Win32 SDK from Microsoft.
GUIDGEN is a tool used to generate Global Unique Identifiers (GUID),
which can be used for Interface IDs, Class IDs, or any other 128-bit UUID,
such as an RPC interface. GUIDGEN is installed when the OLE development
option is selected during the Visual C++ installation. When GUIDGEN is run,
you must select the proper format for the UUID and then press the New GUID
button to copy the UUID to the Windows Clipboard. After running the GUIDGEN
application, the resulting GUID is pasted from the Clipboard into the code
that needs a GUID.
NOTE: The tool GUIDGEN is also installed by default if the option Typical is selected during the Visual C++ 5.0 installation.
RegEdit or the registration editor is a standard component of both the
Windows 95 and Windows NT operating systems. The registration editor is used for
browsing and altering operating system and application settings. The registration
editor can also be used for installing and registering your COM Objects.
CAUTION:
RegEdit is a powerful tool and must be used with extreme caution by experienced users. If used improperly, systems can be damaged, resulting in a loss of data or a malfunctioning computer.
In Windows 95, this program is called regedit.exe. In Windows NT, this program is
called regedt32.exe.
The registration server is an application that can be used to register the settings of a COM Object in the Windows registry without the need to create a separate registration file. The application is called regsvr32.exe and is automatically installed if the OLE development option is selected during Visual C++ installation or if the ActiveX SDK is installed.
In order to maximize development productivity, the tools needed for COM programming
should be integrated into the Visual C++ environment. Each of the tools needed can
be added to the IDEs (Integrated Development Environment) Tools menu. The
following sections illustrate how to incorporate the tools into the IDE.
FIG. 12.3 Adding GUIDGEN
Adding the Registry Editor
Adding the Registration Server A COM interface is a group of functions used to manipulate the data of
the class that is implementing the interface. Interfaces only define the functions
that belong to the group. An interface does not implement the function or contain
data. The function implementation and data belong to the class that implements the
interface. ActiveX is based entirely on a set of COM interfaces. These COM interfaces are
a standard part of the operating system. In other words, Windows 95 and Windows NT
contain all of the code that implement the ActiveX COM interfaces. When building new components based on COM, these components define custom
interfaces. A custom interface is an interface that is not already supported by the
operating system. A custom interface contains a set of functions that are specific
to the new component being built. For example, a spell-checker component may contain
a custom interface that contains a set of functions used by a program that uses the
spell-checker component. Once an interface is defined, multiple components may be
built that support and implement the interface. Going back to the spell-checker component, a defined spell-checker interface may
be implemented by multiple companies. Having multiple companies provide a component
with the same interface gives application developers the flexibility to have all
of the components exist on a system, yet provide the user with the ability to load
and use a specific company's spelling checker. When creating a custom interface, the interface definitions need to be shared
among multiple applications, such as the server that implements the interface and
the client that uses the interface. For this reason, it makes sense to define the
interfaces in a project separate from the server or client projects. Multiple interfaces
can be defined within a single project. In this chapter, we develop three projects to implement and use COM Objects. Table
12.1 shows the project names and the purpose of each project. The IFISH project contains two COM interface definitions, IFish and IBass.
The project IFISH is implemented as a DLL. The DLL does not contain any MFC code
or written C\C++ code. The code contained within IFISH is produced by the MIDL compiler.
The MIDL compiler takes the interface definition files (IDL) as input and produces
C code for the interface as output. The C code that is produced is needed to implement
parameter marshaling. Parameter marshaling is needed if the COM interface
is implemented in an executable (EXE). The marshaling allows the parameters to be
passed across process boundaries. Even if the COM interface implementation is in a DLL (in-process server), the
MIDL compiler should still be used. There are no penalties for implementing parameter
marshaling. NOTE: The IFISH project is built as a DLL. This is not the DLL that is
implementing the COM interface. IFISH contains only the interface definitions. Projects
that contain the interface definitions should be implemented as DLLs.
FIG. 12.4 When creating the interface definition, you must determine whether marshaling
code is needed to provide support for passing parameters between two processes. The
safest method is to always assume that marshaling is needed. Providing marshaling
support also allows the freedom to create either an in-process server (DLL) or an
out-of-process server (EXE) to implement the interface. Use of the Microsoft RPC MIDL compiler provides parameter marshaling support.
Parameter marshaling is automatically provided by defining the COM interface with
the Interface Definition Language (IDL). Once the interface is defined using
IDL, the RPC MIDL compiler automatically generates the code necessary for marshaling
support. Two interface definition files are used in the IFISH project, IFISH.IDL and IBASS.IDL
(see Listing 12.1).
[ All interface definition files have the extension IDL. The first portion of the
IDL file contains an object definition section. The most important part of this section
is the UUID of the object. The UUID is a unique 128-bit number
that is created through the tool GUIDGEN. The UUID in IFISH.IDL
distinctly identifies the IFish COM interface definition. This number is
used by applications that will use the IFish interface. A unique UUID number can be generated by performing the following steps:
FIG. 12.5
Following the object definition section is the actual interface definition. As
shown in Listing 12.1, the object definition resembles a C++ class definition. The
keyword interface specifies the start of an interface definition. The name of the
interface and any inherited interfaces follows. In this case, the interface name
is IFish, and this interface inherits the IUnknown interface. Since the IUnknown interface is a standard interface within the operating
system, you don't need to redefine the functions within the interface. You only need
to import the IUnknown interface definition. You do this through the statement
import "unknwn.idl";. The functions implemented by the interface need to be added in order to complete
the interface definition. When using IDL, all portions of a function must be defined:
the return value and all parameters, including direction (in, out,
or both) and size of the parameters. Specifying all portions of a function allows
the MIDL compiler to generate the correct marshaling code for the interface. NOTE: All return values must be of type HRESULT, which
is standard OLE return value. If the return value is not an HRESULT,
the MIDL compiler will not provide marshaling information to marshal across process
boundaries. The return value HRESULT is needed for network support. In case
a network error occurs, a valid error code can be returned without having to generate
an exception.
[ After the respective IDL files have been created, they must be compiled in order
for the interface code to be generated. Since the MIDL compiler was added to the
project in the section "Adding the MIDL Compiler to the IDE," this task
is an easy one. To compile the IDL files, perform the following steps:
You may be surprised to see the code that is generated by the MIDL compiler from
the simple interface definitions IFish and IBass. Unlike a C++
compiler, the output from MIDL is not binary code. Instead, MIDL generates
C code, which is then compiled by the C compiler as part of the interface project. When the file IFISH.IDL was compiled, the files shown in Table 12.2 were generated.
NOTE: When the IFISH project was created, no source files were included
in the project. The entire project consisted of only a MAK file. Since this is an
interface-only DLL, the entire contents of the project will consist of the files
created via the MIDL compiler.
FIG. 12.6 One of the tedious tasks that, unfortunately, is not performed by either the Application
Wizard or the MIDL compiler is the creation of a library definition file (DEF). This
library definition file, a standard part of DLLs, defines which functions are exported
or made accessible by the DLL. The filename is IFISH.DEF. NOTE: Since a standard Win32 DLL was created, there were no functions to
be exported because there were no source files when the file was created. This is
not the case when an MFC DLL is created. In that case, MFC source code is
produced, and a DEF file for the project is also created with default functions exported
through the DEF file.
LIBRARY IFISH The DLL entry points must be defined because of the parameter-marshaling
code generated by the MIDL compiler. The MIDL compiler generates code that uses the
IMarshall interface. The IMarshall interface requires the DLL entry
point's DllGetClassObject and DllCanUnloadNow. The IMarshall
interface is a COM interface that implements parameter marshaling for all COM Objects. These two entry points are explained in greater detail in the section "Accessing
In-Process COM Objects."
Parameter marshaling is implemented through RPC (Remote Procedure Calls) libraries.
When creating interface libraries that use RPC for parameter marshaling, you must
link a number of RPC libraries into the interface project. You can select from two
methods for linking the RPC libraries into the interface project:
Four RPC libraries must be included in the interface project, rpcndr.lib, rpcdce4.lib,
rpcns4.lib, and rpcrt4.lib. Creating a file with compiler pragmas is much easier
than trying to remember these library filenames and including them for each project
that defines a COM interface. In the IFISH project is a file called RPCHELP.C (see Listing 12.4). This file
contains the necessary compiler pragmas for RPC support.
#pragma comment(lib, "rpcndr.lib") The file RPCHELP.C must be added to the IFISH project in order for the project
to link properly. The file can be added to the project by performing the following
steps:
The interface definitions for IFISH are now complete, and the project is ready
to be built. Building the project generates a DLL called IFISH.DLL.
Only one task remains before the IFish and IBass interfaces
can be used. The interfaces must be registered in the Windows registry. The Windows
registry is the holding ground for all class and interface IDs. For the IFISH project, a registration file named IFISH.REG must be manually created.
The contents of IFISH.REG are shown in code Listing 12.5.
HKEY_CLASSES_ROOT\Interface\{011BB310-5AB0-11d0-907E-00A0C91FDE82}
To add the interface keys to the Windows registry, do the following:
Now that the IFish interface definitions are defined, the object that
implements the interfaces must be created. Remember that the IFISH interface
DLL contains only the interface definitions and RPC proxy code for parameter
marshaling. There is no code for implementing the interface within IFISH.DLL. The
COM Object developed in this section in the project BASS.DLL will contain an MFC
class called CBass that will implement both the IFish and IBass
interfaces. COM Objects can be implemented as either a DLL or an EXE. COM Objects that reside
within a DLL are called in-process servers. COM Objects that reside in an EXE are
called out-of-process servers. The application that will use the COM Objects does
not see a difference between the two types of servers. However, internally, there
are a few differences between in-process servers and out-of-process servers.
CAUTION: Nothing special is needed to create a basic application for containing
your COM Objects. The application is where the COM interface definitions are implemented.
With this in mind, you can create a basic COM Object application. In this section,
you will create a COM Object using an MFC DLL (in-process server) as the containing
application. During creation of the application, the differences between creating
an in-process and out-of-process server will be pointed out. NOTE: The differences between an in-process and out-of-process server are
trivial at this point because the interface DLL already contains the proxy code used
for parameter marshaling. The determination to have an in-process server versus an
out-of-process server depends on the use and needs of the COM Object.
FIG. 12.7 FIG. 12.8 FIG. 12.9 TIP: Your COM Objects will be easier to distribute if you use the static-linked
version of MFC. If your COM Objects are part of a project that contains other MFC
components and applications, you will get better performance by using the DLL version
of MFC.
In-process servers contain two functions that serve as entry points for clients
accessing COM Objects. These functions are DllGetClassObject and DllCanUnloadNow.
These functions are not needed for EXE or out-of-process servers. In order for the COM support functions to be accessed, the functions must be exported
from the DLL. An exported function can be called from any Windows application. The
COM support functions are defined in MFC but are implemented in the server DLL. These
support functions are also exported through the definition file (.DEF) of the DLL
that uses the functions--in this case, BASS DLL. The AppWizard has already created
a DEF file entitled BASS.DEF (see Listing 12.6).
; BASS.def : Declares the module parameters for the
DLL. NOTE: The BASS definition file and the three support functions needed for
accessing COM Objects were automatically inserted by the MFC Application Wizard as
a result of selecting the OLE Automation option for the project. Automation and COM
Objects are accessed in in-process servers through the same support functions. If
OLE Automation was not selected, these functions will have to be implemented
manually.
The MFC AppWizard inserts all of the code needed for accessing COM Objects in
an MFC Application DLL. The code for DllGetClassObjects is shown in Listing
12.7.
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,
LPVOID* ppv) STDAPI DllCanUnloadNow(void) An EXE server does not need to implement the function DllCanUnloadNow
because an EXE can keep track of how many users it has. When the usage count reaches
0, the EXE can unload itself from memory.
STDAPI DllCanUnloadNow(void) BOOL DllRegisterServer(void) The ActiveX development kit has a tool called regsvr32.exe. This program can be
run with two parameters. The first parameter is the DLL to register. The second parameter
is TRUE (for registering a server) or FALSE for unregistering a
server. To register the BASS COM server, run regsvr32 with the following
parameters: regsvr32 BASS.DLL TRUE
// by exporting DllRegisterServer, you can use regsvr.exe
Now that the application that will contain the COM Class has been created, the
COM Class needs to be added to the project. The Visual C++ ClassWizard will be used
to implement this class. The class name being created is CBass. To create
the CBass class, perform the following steps:
FIG. 12.11
The class CCmdTarget is chosen as the base class because MFC provides
a standard implementation of the IUnknown interface in this class. As you
have seen, the IUnknown interface provides the three basic methods that
must be supported by all COM Objects. Deriving from the CCmdTarget base class also allows the object to be
created through the MFC class COleObjectFactory. The class COleObjectFactory
is called through the DLL entry point, DllGetClassObject. If the COM class
was not derived from a class with CCmdTarget as its base, special object
creation code must be written in the function DllGetClassObject to create
an instance of the object. NOTE: Even though CCmdTarget was selected as the base class for
the CBass class, any of the other classes derived from CCmdTarget
can be used. If a class not derived from CCmdTarget is used, you
must manually provide support for the IUnknown interface.
Supporting the IUnknown Interface NOTE: The file commacros.h is not a part of MFC and must be added to the
project manually.
#ifndef _COMMACROS_H Adding a Class ID NOTE: To create a unique ID using GUIDGEN, refer to the steps
outlined in the section " Creating the Interface Definition."
#ifndef _CLSID_Bass The macro DEFINE_GUID assigns the name CLSID_Bass to the class
ID that was created via GUIDGEN. This macro is placed in a header file that is used
by all clients that need to invoke an instance of CLSID_Bass. This file
is not used by the server that implements the COM Object. NOTE: Even though the IUnknown interface functions are not
included within the interface map for the class, they must be supported
by the class. All derived methods of the interface must be supported, although
not explicitly defined.
NOTE: Upon inspection of the macro BEGIN_INTERFACE_PART, you can
see that the methods for the IUnknown interface are automatically added
to the class. This eliminates the need to manually add them to the class.
#ifndef __AFXWIN_H__ Implementing MFC Interface Maps
//{AFA853E0-5B50-11d0-ABE6-D07900C10000} runtime_class::interface_class::method. // implement the Iunknown interface /////////////////////////////////////////////////////////////////////////////
Building the COM Object
FIG. 12.13
Use of COM Objects within an application is an easy task. A test application called
ComTest has been developed to aid in the testing and use of the IFish and
IBass interfaces. Only a handful of functions are necessary to access and utilize COM interfaces
within an application. These functions can be broken into two categories:
When building MFC applications that will utilize COM interfaces, two functions
must be called to properly initialize the MFC framework. These functions are AfxOleInit()
and CoFreeUnusedLibraries(). These functions must be called during the
application initialization and removal. Listing 12.14 shows how the OLE libraries
are initialized for use during application creation and removed during exit. NOTE: If the COMTEST app was created with OLE support, the AppWizard would
have automatically inserted the function AfxOleInit() within the InitInstance()
method of CComTestApp. Likewise, OLE termination code would be automatically
called on the program's exit.
CComTestApp::CComTestApp() Using COM interfaces within an application is similar to using any ordinary C++
class, the exception being that instances of a class are created through the function
CoCreateInstance() rather than the new operator. After an
interface pointer is returned, it can be used as though it is a C++ class, to call
any of the functions in the interface. Listing 12.15 provides an example of how to
access COM interfaces and call their functions.
void CComTestView::OnEditCreatebassinterfaces() This chapter discussed creating COM Objects based on the MFC application framework.
While MFC does not directly utilize COM, provisions have been made so that MFC supports
COM and reduces the amount of work necessary in creating COM Objects. Other techniques
can be used for creating COM Objects. Chapter 13 examines
the creation of COM Objects through the ActiveX Template Library (ATL). ATL provides
a lightweight COM framework for building COM Objects.
© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a
Simon and Schuster Company.
Adding the MIDL Compiler to the IDE
Add your tools settings for the MIDL compiler in the Customize dialog.
Defining COM Interfaces Using IDL
Project Name
Purpose
IFISH (IFISH.DLL)
Implements a COM interface definition.
BASS (BASS.DLL)
Contains an MFC class that implements the COM interfaces in IFISH.
COMTEST (COMTEST.EXE)
Sample Test application that uses the BASS COM Object.
The project IFISH defines two COM interfaces, the IFish interface and the
IBass interface. The IFish interface is a base class for
all of the different species of fish. The IBass interface is an interface
specific to a particular type of fish. Both of these interface definitions will be
implemented within the IFISH project.
Creating the IFISH Project
Perform the following steps in order to create the IFISH project:
Select the project attributes for IFISH in the New dialog.Creating the Interface Definition
Listing 12.1
object,
uuid(011BB310-5AB0-11d0-907E-00A0C91FDE82),
pointer_default(unique)
]
interface IFish : IUnknown
{
import "unknwn.idl";
HRESULT IsFreshwater([out] BOOL *pBool);
HRESULT GetFishName([out, string, size_is(255)] char *p); }
Use the Create GUID dialog to generate unique identifiers for COM interfaces.
The interface for IBASS is shown in Listing 12.2. Note that IBass
is an aggregate interface, not an inherited interface. Aggregate interfaces
will be explained in the section "Implementing the Interface."
Listing 12.2
object,
uuid(F60D7C40-5B4E-11d0-ABE6-D07900C10000),
pointer_default(unique)
]
interface IBass : IUnknown
{
import "unknwn.idl";
HRESULT GetLocation([out, string, size_is(255)] char *p);
HRESULT SetLocation([in, string] char *p);
HRESULT EatsOtherFish([out] BOOL *pBool); } Compiling the Interface Definition Files
File
Purpose
IFISH.H
Support header file for the IFish interface.
IFISH_I.C
Interface definition file that is added to both the server and client projects.
IFISH_P.C
Proxy code that implements the marshaling code for the interface.
DLLDATA.C
Reference file used for loading the correct interface from the DLL. Shared by all
IDL files compiled within this project.
The files that were created by the MIDL compiler must now be added to the IFISH project.
The following files must be added to the IFISH makefile in order for the IFish
and IBass interface definitions to be accessible and used in COM Object
implementations.
You can add these files to the project by performing the following steps:
ifish.h
ibass_i.c
ifish_i.c
ibass_p.c
ifish_p.c
Dlldata.c
ibass.h
Rpchelp.c
The Insert Files into Project dialog is used for adding MIDL files into a project.Creating a Definition File
The contents of the IFISH.DEF file were created manually and can be viewed in Listing
12.3.
Listing 12.3
DESCRIPTION `IFISH Interface Marshaling'
EXPORTS
DllGetClassObject
DllCanUnloadNow Adding the RPC Libraries to the Interface Project
Listing 12.4
#pragma comment(lib, "rpcns4.lib")
#pragma comment(lib, "rpcrt4.lib")
Registering the Interfaces
Listing 12.5
HKEY_CLASSES_ROOT\Interface\{011BB310-5AB0-11d0-907E-00A0C91FDE82} \ProxyStubClsid32
HKEY_CLASSES_ROOT\CLSID\{011BB310-5AB0-11d0-907E-00A0C91FDE82} = IFish_PSFactory
HKEY_CLASSES_ROOT\CLSID\{011BB310-5AB0-11d0-907E-00A0C91FDE82}\InprocServer32 = d:\dev\ifish\debug\ifish.dll
HKEY_CLASSES_ROOT\Interface\{F60D7C40-5B4E-11d0-ABE6-D07900C10000}
HKEY_CLASSES_ROOT\Interface\{F60D7C40-5B4E-11d0-ABE6-D07900C10000} \ProxyStubClsid32
HKEY_CLASSES_ROOT\CLSID\{F60D7C40-5B4E-11d0-ABE6-D07900C10000} = IBass_PSFactory
HKEY_CLASSES_ROOT\CLSID\{F60D7C40-5B4E-11d0-ABE6-D07900C10000}\InprocServer32 = d:\dev\ifish\debug\ifish.dll
Implementing the Interface
If DCOM is in your development plans, you have one detriment to in-process servers:
DCOM or the Distributed Component Object Model allows components to reside on other
computers in your network environment. This feature allows the components to utilize
the processing power of other workstations. If you are planning for DCOM, you must
implement your COM Objects as out-of-process servers because DLLs cannot be used
across machine boundaries.
Using the Visual C++ AppWizard to Create the COM Object
To create the basic application, perform the following steps:
Name the COM Object in the New Project Workspace dialog.
NOTE: When creating an out-of-process server, select the MFC AppWizard (exe)
item in the New Project tab.
Choose your project build options.
Recap the project selections in the New Project Information dialog.
At this point, you have a basic shell application that can be used for your COM Objects.
Accessing In-Process COM Objects
Listing 12.6
LIBRARY "BASS"
DESCRIPTION `FISH Windows Dynamic Link Library'
EXPORTS
; Explicit exports can go here
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllRegisterServer PRIVATE
Each of the COM support functions are explained in the following three sections.
HRESULT DllGetClassObject (REFCLSID rclsid, REFIID riid, LPVOID *ppv)
Listing 12.7
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return AfxDllGetClassObject(rclsid, riid, ppv);
} Listing 12.8
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return AfxDllCanUnloadNow();
}
Listing 12.9
STDAPI DllRegisterServer(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
COleObjectFactory::UpdateRegistryAll();
return S_OK;
}
Creating a Class that Implements COM Interfaces
Set the class options with the New Class dialog.
Figure 12.12 illustrates the CBass class and the interfaces that will be
encapsulated within this class.
FIG. 12.12
Class hierarchy and supported interfaces of the CBass class.
Listing 12.10
#define _COMMACROS_H
#ifndef IMPLEMENT_IUNKNOWN
#define IMPLEMENT_IUNKNOWN_ADDREF(ObjectClass, InterfaceClass)\
STDMETHODIMP_(ULONG)ObjectClass::X##InterfaceClass::AddRef(void)\
{ \
METHOD_PROLOGUE(ObjectClass, InterfaceClass); \
return pThis->ExternalAddRef(); \
}
#define IMPLEMENT_IUNKNOWN_RELEASE(ObjectClass, InterfaceClass)\
STDMETHODIMP_(ULONG)ObjectClass::X##InterfaceClass::Release(void)\
{ \
METHOD_PROLOGUE(ObjectClass, InterfaceClass); \
return pThis->ExternalRelease(); \
}
#define IMPLEMENT_IUNKNOWN_QUERYINTERFACE(ObjectClass, InterfaceClass)\
STDMETHODIMP ObjectClass::X##InterfaceClass::QueryInterface(REFIID riid, LPVOID *pVoid)\
{ \
METHOD_PROLOGUE(ObjectClass, InterfaceClass); \
return (HRESULT)pThis->ExternalQueryInterface(&riid ,ppVoid); \
}
#define IMPLEMENT_IUNKNOWN(ObjectClass, InterfaceClass)\
IMPLEMENT_IUNKNOWN_ADDREF(ObjectClass, InterfaceClass)\
IMPLEMENT_IUNKNOWN_RELEASE(ObjectClass, InterfaceClass)\
IMPLEMENT_IUNKNOWN_QUERYINTERFACE(ObjectClass, InterfaceClass)
#endif #endif
After the CLSID is created, it must be placed in a header file that acts
as a define for the class implementation. For the CBass object, the file
bassid.h is created. The CLSID is then pasted into the file and added to
the macro DEFINE_GUID (see Listing 12.11).
Listing 12.11
#define _CLSID_Bass
//{AFA853E0-5B50-11d0-ABE6-D07900C10000}
DEFINE_GUID(CLSID_Bass,0xAFA853E0,0x5B50,0x11d0,
0xAB,0xE6,0xD0,0x79,0x00,0xC1,0x00,0x00); #endif
Using Interface Maps to Support COM Interfaces
Adding Interface maps to a class is an easy procedure. To add interface maps to a
class, perform the following steps:
Data Member
Interface Method
BOOL m_bFreshwater
IFish::IsFreshwater
CString m_zFishName
IFish::GetFishName
CString m_zLocation
IBass::GetLocation IBass::SetLocation
BOOL m_bEatsOtherFish
IBass::EatsOtherFish
The header file for the CBass class (BASS.H) has been modified to include
all of the changes listed in Table 12.3. The resultant file is shown in Listing 12.12.
Listing 12.12
#error include `stdafx.h' before including this file for PCH
#endif
#include "resource.h" // main symbols
#include "..\ifish\ifish.h"
#include "..\ifish\ibass.h" ////////////////////////////////////////////////////
/////////////////////////// CBass command target
class CBass : public CCmdTarget
{
DECLARE_DYNCREATE(CBass)
CBass(); // protected constructor used by dynamic creation
// Attributes
public:
CString m_zFishName;
CString m_zLocation;
BOOL m_bEatsOtherFish;
BOOL m_bFreshwater;
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CBass)
//}}AFX_VIRTUAL
// Implementation
protected:
virtual ~CBass();
// Generated message map functions
//{{AFX_MSG(CBass)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
DECLARE_OLECREATE(CBass)
DECLARE_INTERFACE_MAP()
BEGIN_INTERFACE_PART(Fish, IFish)
STDMETHOD(GetFishName)(char *pStr);
STDMETHOD(IsFreshWater)(BOOL *pBool);
END_INTERFACE_PART(Fish)
BEGIN_INTERFACE_PART(Bass, IBass)
STDMETHOD(GetLocation)(char *pStr);
STDMETHOD(SetLocation)(char *pStr);
STDMETHOD(EatsOtherFish)(BOOL *pBool);
END_INTERFACE_PART(Bass) };
IMPLEMENT_OLECREATE(CBass, "Bass",
0xAFA853E0,0x5B50,0x11d0,0xAB,0xE6,0xD0,0x79,0x00,0xC1,0x00,0x00)
The macro BEGIN_INTERFACE_MAP takes two parameters: the runtime class (Cbass)
and the derived class (CCmdTarget). This macro provides a series of methods
used for retrieving entries into the interface map structure.
The macro INTERFACE_PART is needed for each COM interface that is to be
supported by the class. This macro links the runtime class with the UUID
for the interface and the friendly interface name.
The macro END_INTERFACE_MAP ends the interface map implementation.
BEGIN_INTERFACE_MAP(CBass, CCmdTarget)
INTERFACE_PART(CBass, IID_IFish, Fish)
INTERFACE_PART(CBass, IID_IBass, Bass)
END_INTERFACE_MAP();
Listing 12.13 illustrates the implementation file, bass.cpp.
The macro METHOD_PROLOGUE is used to establish a local variable named pThis,
which is a pointer to the interface function table. The arguments to the METHOD_PROLOGUE
are the runtime class and the interface name. This macro must precede every
interface implementation.
IMPLEMENT_IUNKNOWN(CBass, Fish)
IMPLEMENT_IUNKNOWN(CBass, Bass)
Listing 12.13
// CBass
IMPLEMENT_DYNCREATE(CBass, CCmdTarget)
CBass::CBass()
{
m_zFishName = "Large Mouth Bass";
m_zLocation = "Under Lily Pads";
m_bEatsOtherFish = TRUE;
m_bFreshwater = TRUE;
}
CBass::~CBass()
{
} BEGIN_MESSAGE_MAP(CBass, CCmdTarget)
//{{AFX_MSG_MAP(CBass)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//{AFA853E0-5B50-11d0-ABE6-D07900C10000}
IMPLEMENT_OLECREATE(CBass, "Bass",
0xAFA853E0,0x5B50,0x11d0,0xAB,0xE6,0xD0,0x79,0x00,0xC1,0x00,0x00)
BEGIN_INTERFACE_MAP(CBass, CCmdTarget)
INTERFACE_PART(CBass, IID_IFish, Fish)
INTERFACE_PART(CBass, IID_IBass, Bass)
END_INTERFACE_MAP();
/////////////////////////////////////////////////////////////////////////////
// CBass message handlers
// CBass:Fish implementation of IFish
// implement the Iunknown interface
IMPLEMENT_IUNKNOWN(CBass, Fish)
STDMETHODIMP CBass::XFish::GetFishName( char *pStr)
{
METHOD_PROLOGUE(CBass, Fish);
TRACE("CBass::XFish::GetFishName\n");
if (pStr)
strcpy((char *)pStr, (LPCTSTR)pThis->m_zFishName);
return (HRESULT)NOERROR;
}
STDMETHODIMP CBass::XFish::IsFreshwater( BOOL *pBool )
{
METHOD_PROLOGUE(CBass, Fish);
TRACE("CBass::XFish::IsFreswWater\n");
if (pBool)
{
*pBool = pThis->m_bFreshwater;
return S_OK;
}
return (HRESULT)NOERROR;
}
// CBass:Fish implementation of IFish
// implement the Iunknown interface
IMPLEMENT_IUNKNOWN(CBass, Bass)
STDMETHODIMP CBass::XBass::GetLocation( char *pStr)
{
METHOD_PROLOGUE(CBass, Bass);
TRACE("CBass::XBass::GetLocation\n");
if (pStr)
strcpy((char *)pStr, (LPCTSTR)pThis->m_zLocation);
return (HRESULT)NOERROR;
}
STDMETHODIMP CBass::XBass::SetLocation( char *pStr)
{
METHOD_PROLOGUE(CBass, Bass);
TRACE("CBass::XBass::SetLocation\n");
if (pStr)
pThis->m_zLocation = pStr;
return (HRESULT)NOERROR;
}
STDMETHODIMP CBass::XBass::EatsOtherFish( BOOL *pBool )
{
METHOD_PROLOGUE(CBass, Bass);
TRACE("CBass::XBass::EatsOtherFish\n");
if (pBool)
{
*pBool = pThis->m_bEatsOtherFish;
return S_OK;
}
// return E_BADPOINTER;
return (HRESULT)NOERROR;
}
Enabling automatic use of precompiled headers for building a COM Object.
Using the Interface
OLE Initialization and Shutdown Functions
Listing 12.14
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
AfxOleInit();
}
int CComTestApp::ExitInstance()
{
::CoFreeUnusedLibraries();
return CWinApp::ExitInstance();
}
COM Object Access Functions
Listing 12.15
{
char lo_Location[255];
char lo_FishName[255];
IFish *pIfish;
IBass *pIBass;
::CoCreateInstance( CLSID_Bass, NULL,
CLSCTX_INPROC_SERVER,
IID_IFish,
(LPVOID *)&pIfish);
if ( pIfish )
{
TRACE0("Success ... Got an interface to Ifish\n");
pIfish->GetFishName(lo_FishName);
TRACE1("The Fish Name is %s\n", lo_FishName);
if (pIfish->QueryInterface(IID_IBass, (LPVOID *)&pIBass)== S_OK)
{
pIBass->GetLocation(lo_Location);
TRACE1(" The Fish is a bass and it is located %s\n", lo_Location);
pIBass->Release();
}
pIfish->Release();
} } From Here...