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