DCOM Microsoft Distributed Component Object Model
DCOM Microsoft Distributed Component Object Model
Preface
Appendix A
Appendix B
Appendix C
Quick References: ODL Language Features in
MIDL
Index [an error occurred while processing this directive]
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Table of Contents
-----------
Preface
Microsoft’s Distributed Component Object Model (DCOM) provides the
software infrastructure you need to build your next-generation distributed
application. However, you will need to know more than just “how to build
COM objects” to successfully develop that next-generation distributed
application. While learning to build COM objects is important, for corporate
applications developers it is only the beginning. After building the necessary
COM objects, the corporate applications developers must then assemble them
into a complete application, which is no trivial matter. A poorly written
component-based application will perform as equally unsatisfactorily as a
poorly written monolithic application. DCOM: Microsoft® Distributed
Component Object Model not only teaches you the fundamentals of COM and
building COM objects, but also goes the extra mile to show you how to build
componentized applications.
This book teaches you how to do the following:
• Create both in-process and out-of-process COM servers
• Create new COM objects from existing COM objects by using
containment and aggregation, COM’s implementation inheritance
mechanisms
• Create COM objects that support custom interfaces as well as COM
objects that support Automation through the use of dual interfaces
• Develop multiple COM objects into a single cohesive object hierarchy
• Develop client/server applications using an object hierarchy
• Develop web-based applications using an object hierarchy
• Use DCOM to improve both the client/server and web-based
application architectures.
Who This Book Is For
If you are responsible for the architecture, development, or deployment of a
corporate enterprise application, then this book is for you! This book will
provide you with a firm understanding of COM through a combination of
clear, concise explanations and related samples. In addition to providing you
with a firm understanding of COM, DCOM: Microsoft® Distributed
Component Object Model will also teach you how to create both client/server
and web-based applications using COM. Finally, this book will show you how
DCOM can be used to improve both the client/server and web-based
application architectures.
This book assumes that you are at least familiar with C++ and HTML.
Familiarity with Microsoft Visual Basic and Visual Basic Scripting Edition
(VBScript) should enhance your understanding of Chapters 8, 9, and 10,
although intimate knowledge of these two products is not required.
Support for Internet Information Server (IIS) and Active Server Pages (ASP)
are also provided as part of Windows NT 4.0 Workstation and Server.
Windows 95 supports Peer Web Services, which can also be freely
downloaded from the Microsoft web site.
The sample DCOM source code provided throughout this book and on the
accompanying CD-ROM were developed using Microsoft Visual C++ 5.0 and
tested on Windows NT 4.0 Workstation and Server as well as Windows 95.
The web-based applications developed in Chapters 9 and 10 were tested using
Microsoft Internet Explorer version 3.0.2.
How to Reach Me
If you have any questions, comments, or suggestions, you can reach me
through my CompuServe account at [email protected].
Table of Contents
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Table of Contents
-----------
Acknowledgments
I would like to thank several people at IDG Books: John Osborn, who helped
me formulate my original ideas into this final book, and Matt Lusher, who had
the thankless task of keeping me on schedule throughout the development
process. I would also like to thank Luann Rouff, Anne Friedman, and Susan
Parini and the rest of the IDG Books staff who helped make this book possible.
I would like to thank Mary Kirtland, Charlie Kindel, Markus Horstman, and
everyone else who helped review the various technical aspects of this book.
Special thanks to Brian Staples for taking time out to not only review this
book, but to also provide valuable insight during this book’s early
developmental stages. I would also like to thank Ted Hase, Morris Beton, and
the rest of DRG for supporting me throughout this entire process.
To my family: Mr. and Mrs. Frank E. Redmond Jr., Alicia,
Angela, and Darlene, and my loving wife, Jill, for all of their
enthusiasm, encouragement, and support. Without you this book
simply would not have been possible. Thanks.
Table of Contents
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Part 1
Introduction to COM
Chapter 1
A COM Overview
IN THIS CHAPTER
• COM clients
• COM objects
• Interfaces
• COM servers
• COM’s APIs
• COM’s implementation locator service
• COM’s transparent LPC and RPC mechanism
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
COM Objects
//{7AF31102-7A1B-11DO-BADC-0080C7B24880}
const CLSID CLSID_MyObject = {0x7af31101,0x7a1b,0x11d0,
{0xba,0xdc,0x00,0x80,0xc7,0xb2,0x48,0x8}};
Typically, the CLSID information is included as part of a header file, which is
redistributed — along with the object itself — for consumption by other
developers. However, the header file is not redistributed with any resultant
applications created using the object. The header file is only required by other
developers who want to use the object as part of their development efforts. While
CoCreateGuid guarantees the uniqueness of each CLSID, it is the developer’s
responsibility to make sure that each human-readable name is unique within the
scope of its definition. And like traditional object-based development, any naming
conflicts will be caught at compile time, when you will be forced to resolve them.
Interfaces
//{23237f09-e569-11d0-94ab-00a024a85a21}
const IID IID_MyInterface = {0x23237f09,0xe569,0x11d0,
{0x94,0xab,0x00,0xa0,0x24,0xa8,0x5a,0x21}};
Interfaces are essential to COM programming because they are the only way to
interact with a COM object. Instead of obtaining a pointer to an entire COM
object, a COM client must obtain a pointer to a particular interface, which is then
used to access the functions defined as part of that particular interface. The only
way to access the functions of a particular interface is through a pointer to that
interface. So if you have a pointer to the ICopyInfo interface, you will only be
able to access the CopyName, CopyAge, CopySex, and CopyAll member
functions. In order to access SwapName, SwapAge, SwapSex, or SwapAll,
you must first obtain a pointer to the ISwapInfo interface. The fact that
interfaces are the only way to interact with COM objects should help explain why
each interface must be uniquely identifiable. The process of moving from one
interface to another is known as interface navigation.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
INTERFACE NAVIGATION
To support interface navigation, every interface must implement a special function named
QueryInterface. QueryInterface takes two parameters, one to specify the desired
interface’s IID, and the other to receive the actual interface pointer. If the COM object
implements the interface identified by the IID, the QueryInterface call will succeed and
return a pointer to the interface in the second parameter; otherwise, the QueryInterface
call will fail, and a NULL value will be returned in the second parameter. The following
snippet shows how a client would obtain a pointer to the ISwapInfo interface, assuming
that it already had an IReverseInfo interface pointer. We’ll uncover the process of how
the client obtained the original interface in the section on COM’s implementation locator
service.
HRESULT hr;
ISwapInfo *pISwapInfo; //Declare a pointer to an ISwapInfo
//interface
LIFETIME MANAGEMENT
We have already seen how QueryInterface is used for interface navigation; now we will
look at how AddRef and Release are used to manage the lifetime of a COM object.
Typically, the client of an object is responsible for managing the lifetime of that object. The
client creates the object whenever it needs to, uses the object, and destroys it once it is done.
However, COM objects may have multiple clients that are each unaware of the others. To
prevent one client from destroying a COM object and leaving the others with invalid interface
pointer references, both the client and the COM object share the responsibility of lifetime
management. A COM object’s lifetime is managed through a process called reference
counting. Every COM object maintains an internal counter variable:
ULONG CSomeObject::AddRef(void)
{
return ++m_cRef;
}
Whenever a client is finished using an interface, it is the client’s responsibility to call
Release on that interface:
ULONG CSomeObject::Release(void)
{
m_cRef-;
if (0 == m-cRef)
{
delete this;
.
. //other object destruction code
.
return 0;
}
return m_cRef;
}
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
OBJECT VERSIONING AND EVOLUTION
While COM strictly prohibits modifying an object’s interfaces, COM does
provide a simple yet effective strategy for introducing new features in a COM
object without modifying existing interfaces and without breaking existing
client applications. The solution is to simply add new interfaces. For example,
suppose that you’ve created a ReferenceMaterials COM object, and
that version 1 has one interface, IDictionary, which has only one function,
CheckWord. Clients can call CheckWord with a single argument containing
the spelling of a particular word that they would like to validate. CheckWord
returns TRUE or FALSE depending on whether the word is spelled correctly or
not. However, in version 2, the ReferenceMaterials COM object adds
an additional interface, IDictionary2, which has two functions,
CheckWord, and GetDefinition. Since the original IDictionary
interface hasn’t changed, older clients are completely unaware that the
ReferenceMaterials COM object has changed at all. However, newer
clients can obtain a pointer to the new IDictionary2 interface and use the new
GetDefinition function (see Figure 1-2). By simply adding new
interfaces, COM objects are able to add new features and functionality while
simultaneously maintaining backward compatibility with existing client
applications.
COM Servers
The class for each COM object is implemented in a binary code module (DLL
or EXE) called a COM server. COM servers implemented as DLLs are loaded
directly into the client process’s address space, and are commonly referred to
as in-process servers. The nature of a Win32 DLL is such that a copy of it is
mapped directly into each client application’s own private address space. This
means that each client application owns any resources allocated by the
in-process server. Since in-process servers don’t own their resources, they
cannot maintain global resources that are accessible by multiple clients (see
Figure 1-3).
Figure 1-3 Because DLLs do not maintain their own address space, their
clients each receive a separate and independent copy of all global resources.
While it may at times seem a bit disadvantageous for in-process servers not to
own their resources, in-process servers do have a major advantage … speed.
Because the in-process server is already mapped onto the client’s address
space, there is no need for the operating system to perform a context switch in
order to access the code contained in the DLL. As a result, there is very little
overhead associated with invoking the interface functions of a COM object
implemented in an in-process server.
COM servers can also be created as stand-alone EXEs, in which case they
maintain an address space apart from that of the client. COM servers created as
EXEs are commonly referred to as out-of-process servers. Since EXEs
maintain their own address space, out-of-process servers are also capable of
owning their resources, which may be shared among their clients (see Figure
1-4).
Figure 1-4 Because EXEs maintain their own address space, out-of-process
servers are also capable of owning their resources, which may be shared
among their clients.
An out-of-process server running on the same machine as its client(s) is
referred to as a local server and is said to serve the client(s) local objects.
However, any COM server, in-process or out-of-process, that is running on a
machine other than its client(s) is referred to as a remote server and is said to
serve the client(s) remote objects. In the case where a remote server is an
in-process server, COM automatically creates a separate surrogate process and
loads the in-process server into its address space (see Figure 1-5).
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
COM’s APIs
The COM Application Programming Interfaces (APIs) are used to access the
services offered by the COM Library. The COM APIs are similar to most other
Win32 APIs in the sense that they are ordinary function calls, not methods of
an interface. For easy identification, COM API functions typically begin with
the prefix “Co,” such as CoCreateInstance. When you look at the
definitions of the COM APIs, you may notice that many of them return a
strange HRESULT data type. An HRESULT is not a handle to a result, as its
name might imply. An HRESULT is used to return status information
regarding the success or failure of an operation. By dividing the 32-bit
HRESULT value into an internal structure containing four fields, it is possible
to return information regarding not only the success or failure of an operation,
but also detailed information regarding the source of the failure and the reason
for the failure. The internal structure of an HRESULT is described in Table
1-1.
Table 1-1 The Internal Structure of an HRESULT
S 31 Severity field.
0: Success. The function completed
successfully.
1: Error. The function failed.
R 29–30 Reserved for future use.
Facility 16–28 A number indicating the source of the failure.
This value must be universally unique, and
therefore is issued by Microsoft. (See Table
2-2 for a description of the currently defined
facility codes.)
A number describing the reason the error
Error Code 0–15
occurred.
Constants defining HRESULT return values typically use the following naming
convention:
<Facility>_<Sev>_<Reason>
where <Facility> is the facility name, <Sev> is either S or E, indicating
success or error, and <Reason> is a short description of the reason the error
occurred. In cases where the <Facility> value is FACILITY_NULL, the
naming convention is shortened to
<Sev>_<Reason>
as in E_UNEXPECTED or E_NOMEMORY. Table 1-3 shows some of the more
commonly used constants defining HRESULT return values.
Table 1-3 Commonly Used HRESULT Return Value Constants
Constant Meaning
Because an HRESULT returns not only success or failure, but other detailed
information as well, COM defines several macros that allow you to probe the
internal structure of an HRESULT value. Table 1-4 describes several of the
more commonly used macros.
Table 1-4 HRESULT Macros
Syntax Description
User-defined error codes should have a code value between 0x0200 and
0xFFFF, as values 0x0000 and 0x01FF are used by the COM-defined
FACILITY_ITF codes.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
COM’s Implementation Locator Service
Figure 1-6 In order to provide a system view of COM objects, COM maintains a
system-wide database called the system registry, which is essentially a lookup table
mapping CLSIDs to COM server filenames.
All interactions with the system registry are done through the Win32 APIs listed in
Table 1-5.
Table 1-5 Win32 APIs for Manipulating the System Registry
RegCloseKey RegConnectRegistry
RegCreateKey RegCreateKeyEx
RegDeleteKey RegDeleteValue
RegEnumKey RegEnumKeyEx
RegEnumValue RegFlushKey
RegGetKeySecurity RegLoadKey
RegNotifyChangeKeyValue RegOpenKey
RegOpenKeyEx RegQueryInfoKey
RegQueryMultipleValues RegQueryValue
RegQueryValueEx RegReplaceKey
RegRestoreKey RegSaveKey
RegSetKeySecurity RegSetValue
RegSetValueEx RegUnLoadKey
Top-level keys in the hierarchy are called root keys. COM-specific information is
maintained under the root key HKEY_CLASSES_ROOT. Under
HKEY_CLASSES_ROOT is another key called “CLSID,” under which each COM
server is responsible for creating its own key composed of a string representation of
the CLSID enclosed in curly braces, along with an optional string description as the
associated value:
HKEY_CLASSES_ROOT
CLSID
{12345678-ABCD-1234-5678-9ABCDEF00000} = Description
Under each COM object’s CLSID key, you will find one or more additional
subkeys that define the types of servers present for serving COM objects with that
CLSID. In-process servers must add the “InprocServer32” key and set its value
equal to the string representation of the DLL server’s pathname. Local servers must
add the “LocalServer32” key and set its value equal to the string representation of
the EXE server’s pathname. In order to provide a wide range of flexibility, a COM
object may be available from both in-process and out-of-process servers, in which
case both the “InprocServer32” and “LocalServer32” keys would be defined under
the object’s “CLSID key.”
HKEY_CLASSES_ROOT
CLSID
{12345678-ABCD-1234-5678-9ABCDEF00000} = Description
InprocServer32 = C:\SomeServer.dll
LocalServer32 = C:\SomeServer.exe
In the case where a COM object is available for use in different execution contexts,
it is the client’s responsibility to specify the desired context(s). When multiple
contexts are specified, the COM library will attempt to load in-process servers first,
followed by local servers and, finally, remote servers.
By maintaining a system view, any client on the system is capable of instantiating a
registered COM object. The COM API provides the CoCreateInstance
function, which client applications can use to create an instance of a particular
class. When a client calls CoCreateInstance to instantiate a COM object, as in
hr = CoCreateInstance(CLSID_SomeObject, NULL,
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
IID_ISomeInterface, &pSomeInterface);
it is invoking COM’s implementation locator service. COM’s implementation
locator service is implemented in the form of a Service Control Manager (SCM),
pronounced like scum. The SCM is ultimately responsible for the following:
• Locating the appropriate server for a COM object identified by a
client-supplied CLSID
• Launching the COM server
The SCM uses the system registry to locate and launch the appropriate COM
server.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
COM’s Transparent LPC and RPC Mechanism
Summary
In this chapter, we explored:
• COM’s binary standard, which describes how objects are laid out in
memory and how this allows COM to maintain language independence.
• The significance of interfaces in general, and the significance of the
IUnknown interface specifically, for interface navigation and object
lifetime management.
• How adding additional interfaces allows a COM object to introduce
new functionality while simultaneously supporting existing client
applications.
• The advantages and disadvantages of implementing a COM object in
an in-process server as opposed to an out-of-process server.
• How the implementation locator service of the COM Library makes
use of the system registry to provide a system-wide view of registered
COM objects.
• How the VTBL design of COM interfaces combines with proxies,
stubs, Local Procedure Calls (LPCs), and Remote Procedure Calls
(RPCs) to provide a singular programming model and location
transparency.
In the next chapter, you learn the responsibilities required of both the COM
client and the COM server by building an in-process COM server as well as a
client to manipulate it.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Chapter 2
Building In-Process Servers
IN THIS CHAPTER
• Allocate GUIDs for use as CLSIDs, IIDs, and LIBIDs
• Define interfaces
• Implement interface functions
• Implement an object class factory
• Register an in-process server with the system registry
• Load and unload an in-process server
• Initialize and uninitialize the COM Library
• Obtain initial and subsequent interface pointers
• Manipulate a COM object
NOW THAT YOU’VE had a fifty-thousand-foot view of COM, it’s time to dive
in for a closer look. The first target area is in-process COM servers. To better
illustrate the process and requirements of building an in-process COM server,
we build the UserInfo server.
Age short
Name LPSTR
Sex unsigned char
Allocating GUIDs
Every COM object must have a unique CLSID, and every interface must have
a unique IID. As we discovered in the last chapter, CLSIDs and IIDs are both
GUIDs. You also know that COM supplies the CoCreateGuid API function
to facilitate the creation of GUIDs. However, Microsoft Visual C++ includes
two applications, GUIDGEN.EXE and UUIDGEN.EXE, that both rely on
CoCreateGuid internally. These two applications help to further expedite
the creation of GUIDs. GUIDGEN is a Windows-based application that
generates GUIDs in a couple of different formats, and allows you to copy them
to the Windows clipboard so that you can paste them directly into your code.
The UUIDGEN application is a command-line application that allows you to
create a series of consecutive GUIDs with a single call; it also allows you to
save them to a text file. Creating multiple consecutive GUIDs can be really
helpful if you ever need to locate information regarding a specific COM server
in the registry. While you could use GUIDGEN several times to generate
enough GUIDs for the UserInfo server, I used UUIDGEN to generate three
consecutive GUIDs and have them written out to an ASCII text file named
guids.txt:
acceeb00-86c7-11d0-94ab-0080c74c7e95
acceeb01-86c7-11d0-94ab-0080c74c7e95
acceeb02-86c7-11d0-94ab-0080c74c7e95
The newly allocated GUIDs will be used for the CLSID in the definition of the
UserInfo COM object and also as an IID for UserInfo’s IUserInfo
interface.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Defining Each Object’s Interfaces
Objects and their interfaces are defined using the Interface Definition Language (IDL). While a
complete IDL reference is beyond the scope of this book, you can find it on Microsoft’s Web site
at www.microsoft.com. IDL, with its C-style syntax, is a simple and easy-to-use language for
defining a number of COM elements such as objects, interfaces, and type libraries. A type library
is essentially a language-neutral description of COM elements. Type libraries are typically used
during automation to perform parameter type checking. Automation is the process of manipulating
an application’s COM objects from outside the application using special automation interfaces.
(You can find more on automation and type libraries in Chapter 5.) While IDL can be used to
define several different types of elements, the syntax to describe objects and interfaces is
essentially the same: [attributes] elementname typename {memberdescriptions};
The attributes section is used to define the element’s characteristics. Elementname is a keyword
that indicates the type of element being defined (coclass, interface, library, etc.). The typename
assigns a name to the element. The memberdescriptions section contains definitions for one or
more additional elements contained within the element being defined. Listing 2-1 shows the IDL
definition of the IUserInfo interface.
Listing 2-1. The IDL definition of the IUserInfo interface
import "unknwn.idl" ;
//IID_IUserInfo
//These are the attributes of the IUserInfo interface
[
object,
uuid(acceeb02-86c7-11d0-94ab-0080c74c7e95),
helpstring("IUserInfo Interface.")
]
//Declaration of the IUserInfo interface
interface IUserInfo : IUnknown
{
//List of function definitions for each method supported
//by the interface
//
//[attributes] returntype [calling convention]
//funcname(params);
//
[propget, helpstring("Sets or returns the age of the user.")]
HRESULT Age([out, retval] short *nRetAge);
[propput, helpstring("Sets or returns the age of the user.")]
HRESULT Age([in] short nAge);
[propget, helpstring("Sets or returns the name of the user.")]
HRESULT Name([out, retval] LPSTR *lpszRetName);
[propput, helpstring("Sets or returns the name of the user.")]
HRESULT Name([in] LPSTR lpszName);
[propget, helpstring("Sets or returns the sex of the user.")]
HRESULT Sex([out, retval] unsigned char *byRetSex);
[propput, helpstring("Sets or returns the sex of the user.")]
HRESULT Sex([in] unsigned char bySex);
}
Notice the three attributes, object, uuid, and helpstring, used in the definition of the
IUserInfo interface. The object attribute is used to indicate that the interface is a custom
COM interface. The uuid attribute is used to assign a GUID to the interface. The helpstring
attribute is used to provide helpful information about the IUserInfo interface. Inside the
interface definition are definitions of each of the functions supported by the interface. Since
IUserInfo inherits from IUnknown, we don’t need to define the IUnknown functions in the
memberdescriptions of IUserInfo, but we do have to include IUnknown’s definition. To
include the definition of an existing element, IDL provides the import statement. By importing
unknwn.idl in the first line of IDL code in Listing 2-1, we have included the definition of the
IUnknown interface.
As you look at the IUserInfo function definitions, notice that there are two functions for each
property: a propget function, used to retrieve the property value; and a propput function,
used to alter the property value. Had any of the UserInfo properties been read-only, the
propput function would have been missing. Likewise, had any of the UserInfo properties
been write-only, the propget function would have been missing. Because access to some COM
objects may require marshaling, you must provide as much information as possible about the
parameters required by each interface function. To help provide this information, IDL defines the
in and out attributes, which are used to describe the purpose of each individual function
parameter. The in attribute is used to signal parameters responsible for bringing information into
a particular function, while the out attribute is used to signal parameters responsible for returning
information to the caller. These attributes can be combined to indicate a parameter that is
responsible for transferring information into and out of a function.
Each interface function is required to return an HRESULT so that the client can receive status
information regarding the success or failure of an operation. Therefore, to facilitate traditional
function-specific return values, a function must declare a pointer to memory that will receive the
return value, and have the function return information via the pointer. This pointer must include
both the out and retval attributes, and should always be the last parameter in the list.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
While these attributes identify the purpose of a parameter, each parameter still needs a
data type. Like all languages, IDL has a set of intrinsic data types it supports, which can
be seen in Table 2-2. In addition, IDL provides the typedef, enum, union, and
struct keywords for the definition of user-defined data types.
Table 2-2 Intrinsic Data Types Supported by IDL
Once the IUserInfo interface is defined, we can use it in the definition of the
UserInfo object. The UserInfo object is defined as an element of the UserInfo
library element. The library element represents the type library as a whole, and is
identified by a library identifier (LIBID). LIBIDs, like CLSIDs and IIDs, are also GUIDs.
Following is the IDL definition of the UserInfo library and UserInfo object:
//LIBID_UserInfo
//These are the attributes of the type library
[
uuid(acceeb00-86c7-11d0-94ab-0080c74c7e95),
helpstring("UserInfo Type Library."),
version(1.0)
]
//Definition of the UserInfo type library
library UserInfo
{
//CLSID_UserInfo
//Attributes of the UserInfo object
[
uuid(acceeb01-86c7-11d0-94ab-0080c74c7e95),
helpstring("UserInfo Object.")
]
//Definition of the UserInfo object
coclass UserInfo
{
//List all of the interfaces supported by the object
[default] interface IUserInfo;
}
}
Because a COM object may support many different interfaces, IDL supplies the
default attribute to signal macro languages that IUserInfo is the interface to use
for programmatic control. Had the UserInfo COM object supported additional
interfaces, they would have all been listed as part of the coclass UserInfo definition.
The coclass defines the various interfaces supported by a particular COM object, which
ultimately defines the object itself. Once we have defined the type library and all of the
objects and interfaces, we can compile the file using the Microsoft Interface Definition
Language (MIDL) compiler. The MIDL compiler takes UserInfo.idl and generates five
files: UserInfo.tlb, UserInfo_i.c, UserInfo_i.h, UserInfo_p.c, and dlldata.c.
UserInfo.tlb is the actual compiled type library, which is essentially a
language-independent header file. We’ll get to UserInfo_i.c and UserInfo_i.h in just a
minute. The UserInfo_p.c and dlldata.c files contain code that can be used to generate a
proxy/stub pair for marshaling and unmarshaling UserInfo function calls. Since
in-process servers are located in the process space of their client and thus don’t require
marshaling, we will ignore these two files. Now, back to UserInfo_i.c and UserInfo_i.h.
UserInfo_i.c contains the definitions of the human-readable names that are used to refer
to the IUserInfo interface, the type library, and the UserInfo object class.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The UserInfo_i.h file contains the C and C++ definitions for the IUserInfo interface; we
will, however, focus our attention on the C++ definition. The IUserInfo interface is
declared as a C++ abstract class, which means that at least one of its member functions is a
pure virtual function. Typically, whenever a C++ object is used to define an interface, all of
the interface functions will be represented as pure virtual functions. By declaring each
interface function as a pure virtual function, we force each object supporting the interface to
provide an implementation for each interface function. This is important, as we don’t want
clients attempting to invoke interface methods that aren’t actually implemented! Using C++
abstract base classes to define COM interfaces also allows COM objects to separate functional
definition from implementation. For example, one developer implementing IUserInfo may
choose to store the values of each property in a database, while another developer
implementing IUserInfo may choose to store the values of each property in a table in
memory. A developer creating an IUserInfo client is shielded from these implementation
specifics and only knows that calling a particular function with the appropriate parameters
will result in a predictable outcome. By shielding clients from implementation specifics, a
single application can be developed to work with any number of COM objects in a
plug-and-play fashion.
While all of the interfaces have been defined, they are defined as C++ abstract base classes,
which means that the C++ classes that define them cannot provide an implementation for
them. Implementation is provided in the C++ classes that subsequently inherit from the
abstract base class. In our case, we will create the CUserInfo C++ class, which will inherit
from the IUserInfo abstract base class. We will then provide the implementation of
IUserInfo via the CUserInfo C++ class. Had the UserInfo COM object supported
multiple interfaces, we would’ve just defined CUserInfo such that it multiply inherited
from the abstract base class of each supported interface, supplying the implementations of
each interface as part of the same CUserInfo C++ class. You will see an example of
multiple-interface inheritance in the next chapter. In the class declaration for CUserInfo
that follows, notice that the first three functions are QueryInterface, AddRef, and
Release, member functions of IUnknown from which IUserInfo itself is derived.
Remember that every interface must inherit from IUnknown, which means that
QueryInterface, AddRef, and Release must be the first three functions of every
interface that you create (see Listing 2-2).
Listing 2-2. The C++ class declaration for the CUserInfo object
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The next COM-specific function that we investigate is the AddRef function.
Whenever the number of outstanding references to an object is increased,
AddRef is called to increase the object’s internal reference counter:
STDMETHODIMP_(ULONG)CUserInfo::AddRef(void)
{
return ++m_cRef;
}//AddRef
The Release function is the opposite of the AddRef function. Whenever the
number of outstanding references to an object is decreased, Release is called
to decrease the object’s internal reference counter. When there are no
outstanding references, the object deletes itself. Once the object has deleted
itself, the global object counter g_cObjects is decremented to reflect the fact
that there is now one less object in existence. After all of this, the
ServerCanUnloadNow function is consulted to determine if it is all right for
the entire COM server to unload itself from memory, a feat that would be
accomplished by the UnloadModule function. But, because DLLs aren’t
responsible for unloading themselves, the UnloadModule function simply
returns. However, if the UserInfo COM object were being implemented in an
EXE server, UnloadModule would actually unload the EXE server from
memory. (You learn more about EXE servers in the next chapter.) Suffice it to
say that I have provided the UnloadModule function as a way to shield the
object from the differences between unloading a DLL server and unloading an
EXE server, should you decide on your own to implement the UserInfo COM
object in an EXE server. (See the sidebar “Encapsulating Server Packaging
Specifics.”)
STDMETHODIMP_(ULONG)CUserInfo::Release(void)
{
m_cRef-;
if (0 == m_cRef)
{
delete this;
//Decrement the global object count
g_cObjects-;
//See if it's alright to unload the server
if (::ServerCanUnloadNow())
::UnloadServer();
return 0;
}
return m_cRef;
}//Release
The rest of the implementation of the CUserInfo object is vanilla C++ and not
very COM-specific. However, the full implementation of the CUserInfo
object is provided in Listing 2-8 if you are so inclined. Now let’s turn our
attention to creating the class factory that is ultimately responsible for creating
individual UserInfo COM objects.
So far, we have seen how to define and implement the UserInfo COM, but
we don’t have a way to actually instantiate an instance of the object. Sure, we
could just instantiate a single instance of the object when the DLL server is first
loaded, but what if the client needs to create more than just a single instance of
the UserInfo object? In order to provide the client with a mechanism for
controlling the object instantiation process, COM employs the concept of a class
factory, and has defined the IClassFactory interface. A class factory is an
object that implements the IClassFactory interface and is ultimately
responsible for creating other COM objects. The IClassFactory interface
has only two methods, LockServer and CreateInstance. We will look at
the LockServer function in the section “Server Unloading.” The
CreateInstance function is called whenever a client wants to instantiate an
instance of a particular COM object; through a call to CoCreateInstance,
for example. The CUserInfoFactory is derived from IClassFactory,
and it is the class factory responsible for creating UserInfo objects. The
CreateInstance method of the CUserInfoFactory object can be seen
in Listing 2-3. Notice how once a CUserInfo object is created, CUserInfo
ClassFactory calls the newly created CUserInfo object’s
QueryInterface function. This allows clients using CreateInstance to
create an instance of a COM object and receive a pointer to a specific interface
all in one call. Since an object has an initial reference count of zero upon
instantiation, the call to QueryInterface also serves to increment the
object’s reference count to one. If all goes well, CreateInstance also
increments a global object counter, which is used to keep track of the total
number of objects being served. Since in-process servers cannot maintain their
own global memory, the global object counter is really the total number of
objects being used by a particular client.
Listing 2-3. CUserInfoFactory::CreateInstance
STDMETHODIMP CUserInfoFactory::CreateInstance
(IUnknown* pUnknownOuter, REFIID iid, LPVOID *ppv)
{
HRESULT hr;
CUserInfo *pCUserInfo = NULL;
*ppv = NULL;
//This object doesn't support aggregation
if (NULL != pUnknownOuter)
return CLASS_E_NOAGGREGATION;
//Create the CUserInfo object
pCUserInfo = new CUserInfo();
if (NULL == pCUserInfo)
return E_OUTOFMEMORY;
//Retrieve the requested interface
hr = pCUserInfo->QueryInterface(iid, ppv);
if (FAILED(hr))
{
delete pCUserInfo;
pCUserInfo = NULL;
return hr;
}
//Increment the global object counter
g_cObjects++;
return NOERROR;
}//CreateInstance
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Registering Class Information
In order for COM’s implementation locator services to locate, load, and launch your
server, you must add each COM object’s class information to the system registry, which
is typically done once, as part of the installation process. Therefore, every server must
provide a mechanism through which it can be notified to either register or unregister class
information. COM requires that in-process servers provide this capability through the
DllRegisterServer and DllUnregisterServer functions, which are defined
in <olectl.h>. Applications like REGSVR32.EXE that are used to register and unregister
in-process COM servers simply call the Win32 API function LoadLibrary to load a
particular server, and then call the Win32 API function GetProcAddress to obtain a
pointer to either the DllRegisterServer or DllUnregisterServer function.
When a UserInfo client invokes the DllRegisterServer function, the server
must add the UserInfo CLSID as a subkey under the
HKEY_CLASSES_ROOT\CLSID key and optionally provide a textual description of the
UserInfo COM object. The server must also add the “InprocServer32” subkey and
provide the path of the UserInfo.dll server. The following is a representation of the
information that the UserInfo server must add to the system registry:
HKEY_CLASSES_ROOT
CLSID
{acceeb01-86c7-11d0-94ab-0080c74c7e95} = Description
InprocServer32 = C:\UserInfo\UserInfo.dll
I designed the SetRegKeyValue function to help expedite the process of updating the
system registry (see Listing 2-4). SetRegKeyValue uses the Win32 API functions
RegCreateKeyEx, RegSetValueEx, and RegCloseKey to actually add or update
information in the registry.
Listing 2-4. The SetRegKeyValue function
BOOL SetRegKeyValue(LPTSTR lpszkey, LPTSTR lpszSubKey,
LPTSTR lpszValue)
{
BOOL bOk = FALSE;
long lErrorCode;
HKEY hKey;
_TCHAR szKeY[MAX_STRING_LENGTH + 1];
_tcscpy(szKey, lpszkey);
if (NULL != lpszSubKey)
{
_tcscat(szKey, _TEXT("\\"));
_tcscat(szKey, lpszSubKey);
}
lErrorCode = RegCreateKeyEx(HKEY_CLASSES_ROOT, szKey, O,
NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
NULL, &hKey, NULL);
if (ERROR_SUCCESS == lErrorCode)
{
lErrorCode = RegSetValueEx(hKey, NULL, O, REG_SZ,
(BYTE *)lpszValue, sizeof(lpszValue) /
sizeof(_TCHAR));
if (ERROR_SUCCESS == lErrorCode)
bOk = TRUE;
RegCloseKey(hKey);
}
return bOk;
}//SetRegKeyValue
The following code snippet shows how the DllRegisterServer function uses
SetRegKeyValue to update appropriate information in the registry:
When a client calls the COM API CoCreateInstance, the COM library makes a call
to the COM API function CoGetClassObject to retrieve a pointer to the
IClassFactory interface responsible for creating objects of the desired CLSID,
which is supplied as the first parameter to CoCreateInstance. To obtain the
appropriate IClassFactory pointer from an in-process server, COM checks the
registry to retrieve the pathname of the appropriate server. After obtaining the server’s
pathname, COM calls the COM API function CoLoadLibrary to load the server into
memory. Once the server is loaded into memory, COM calls the Win32 API function
GetProcAddress to request the address of the DllGetClassObject function.
COM requires that every in-process server implement and expose a
DllGetClassObject function. Based on a CLSID that is passed to it,
DllGetClassObject is responsible for creating the appropriate class factory. After
obtaining the appropriate IClassFactory interface pointer, the COM system services
call IClassFactory:: CreateInstance to instantiate the desired COM object.
This entire process is illustrated in Figure 2-1.
if (CLSID_UserInfo == rclsid)
{
//Create the UserInfo classFactory
pCUserInfoFactory = new CUserInfoFactory();
//Check for out of memory error
if (NULL == pCUserInfoFactory)
return E_OUTOFMEMORY;
//Get the requested interface
hr = pCUserInfoFactory->QueryInterface(riid, ppv);
if (FAILED(hr))
{
delete pCUserInfoFactory;
pCUserInfoFactory = NULL;
return hr;
}
}
else
//Object not supported
hr = CLASS_E_CLASSNOTAVAILABLE;
return hr;
}//DllGetClassObject
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Server Unloading
Typically, when the last reference on the last object being served by a server is released, the
server is unloaded. However, if the server is responsible for creating objects that typically
have a short life span, it may be desirable to keep the server loaded in memory even when it
isn’t serving any objects, thus eliminating the overhead associated with loading and
unloading the server. To accommodate this functionality, the IClassFactory interface
supports the LockServer function. LockServer takes a single boolean parameter that
determines whether or not the server should be “locked” in memory. If the value is true, the
global reference counter is incremented. If the value is false, the global reference counter is
decremented. Only when there are no locks and no instantiated objects can the server unload:
BOOL ServerCanUnloadNow(void)
{
//The server can unload if there are no outstanding
//objects or class factory locks
if(O == g_cObjects && O == g_cLocks)
return TRUE;
else
return FALSE;
}//ServerCanUnloadNow
ServerCanUnloadNow is called internally by the server itself, whenever the last
outstanding interface reference count of a supported COM object is released, and also
whenever an object class factory lock is released. In addition, DLL servers call
ServerCanUnloadNow as part of their implementation of DllCanUnloadNow,
which again is called automatically by COM to determine whether or not a server can be
unloaded from memory:
STDAPI DllCanUnloadNow(void)
{
if (ServerCanUnloadNow())
return S_OK;
else
return S_FALSE;
}//DllCanUnloadNow
Whenever the last outstanding interface reference count of a supported COM object is
released, or an object class factory lock is released, the implementation calls
ServerCanUnloadNow to determine whether there are any outstanding objects or class
factory locks. If there aren’t, the implementation calls UnloadServer to unload the
server from memory:
STDMETHODIMP_(ULONG)CUserInfo::Release(void)
{
m_cRef-;
if (0 == m-cRef)
{
delete this;
//Decrement the global object count
g_cObjects-;
//See if it's alright to unload the server
if (::ServerCanUnloadNow())
::UnloadServer();
return 0;
}
return m_cRef;
}//Release
Because COM automatically unloads DLL servers in response to DllCanUnloadNow,
the DLL version of UnloadServer simply returns:
void UnloadServer(void)
{
//Since DLLs aren't responsible for unloading themselves,
//simply return
return;
}//UnloadServer
However, EXE servers are responsible for unloading themselves. Therefore, the EXE
version of UnloadServer unloads the server by posting the WM_QUIT message to the
server’s message queue:
void UnloadServer(void)
{
//Unload the server by posting the WM_QUIT to the message
queue
PostQuitMessage(O);
}//UnloadServer
The last thing that I have done to help encapsulate the server packaging differences is to
physically encapsulate the DLL-specific code in a file called DLLMain.cpp. Similarly, for
EXE servers, the EXE-specific code is physically encapsulated in a file called
EXEMain.cpp. By encapsulating the packaging-specific code, the actual object
implementation files can be included as part of a totally separate project, perhaps to
implement the object in a different execution context.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
All that is needed now to build the UserInfo server is a module definition file (.def).
The module definition file is used to export the DllRegisterServer,
DllUnregisterServer, DllGetClassObject, and DllCanUnloadNow
functions from the DLL. The module definition file for the UserInfo server looks like
the following:
LIBRARY UserInfo
DESCRIPTION "UserInfo In-Process Server."
EXPORTS
DllRegisterServer @1 PRIVATE
DllUnregisterServer @2 PRIVATE
DllGetClassObject @3 PRIVATE
DllCanUnloadNow @4 PRIVATE
The UserInfo server is composed of several files: UserInfo.idl, UserInfo.h,
UserInfo.cpp, DllMain.cpp, and UserInfo.def.
• UserInfo.idl contains the IDL definitions of the UserInfo type library; the
IUserInfo interface; and the UserInfo COM object. UserInfo.idl is compiled
using the MIDL compiler to generate the UserInfo_i.c, UserInfo_i.h, UserInfo.tlb,
UserInfo_p.c, and dlldata.c files, of which only UserInfo_i.c and UserInfo_i.h are
used. UserInfo_i.c contains the declarations of the CLSID, IID, and LIBID, and the
UserInfo_i.h file contains C/C++ definitions for the IUserInfo interface.
UserInfo_p.c and dlldata.c are automatically generated by the MIDL compiler in
case you need to create a proxy/stub pair. Proxy/stub pairs are covered in more detail
in Chapter 3. However, because in-process servers are loaded directly into their
clients’ address space and thus don’t require proxies or stubs, we don’t need the
UserInfo-p.c and dlldata.c files.
• UserInfo.h contains the definition of the CUserInfo C++ object that inherits
from the IUserInfo interface. The UserInfo.h file also contains the definition of
the CUserInfoFactory class factory that is responsible for creating the
UserInfo COM object.
• UserInfo.cpp contains the implementation for both the CUserInfo and
CUserInfoFactory C++ objects.
• DllMain.cpp contains functions specific to implementing and registering
in-process COM servers.
• UserInfo.def is a module definition file used to export functions from the DLL.
DllMain.cpp can be seen in Listing 2-6; UserInfo.h in Listing 2-7; and UserInfo.cpp in
Listing 2-8. As you look at the source code, notice how the DLL-specific code has been
confined to just the DllMain.cpp source file, making it easier to implement UserInfo as
an EXE if you so choose. (You can find more on creating EXE servers in the next chapter.)
Listing 2-6. DllMain.cpp
//
//DLLMain.cpp
//
#define MAX_STRING_LENGTH 255
#define GUID_SIZE 128
#include <objbase.h>
#include <olectl.h> //for DLLRegisterServer and
//DLLUnregisterServer
#include <tchar.h>
#include "UserInfo.h"
//
//Forward declarations
//
BOOL SetRegKeyValue(LPTSTR lpszkey, LPTSTR lpszSubKey,
LPTSTR lpszValue);
BOOL ServerCanUnloadNow(void);
void UnloadServer(void);
//
//Global variables
//
HMODULE g_hModule = NULL;
ULONG g_cObjects = 0;
ULONG g_cLocks = 0;
//
//DllRegisterServer
//
STDAPI DllRegisterServer(void)
{
BOOL bOK;
_TCHAR szModulePath[MAX_PATH + 1];
_TCHAR szCLSID[GUID_SIZE + 1];
_TCHAR szCLSIDKey[MAX_STRING_LENGTH + 1];
wchar_t wszGUID[GUID_SIZE + 1];
//
//DllUnregisterServer
//
STDAPI DllUnregisterServer(void)
{
long lErrorCode;
_TCHAR szCLSID[GUID_SIZE + 1];
_TCHAR szCLSIDKey[MAX_STRING_LENGTH + 1];
_TCHAR szInprocServer32Key[MAX_STRING_LENGTH + 1];
wchar_t wszGUID[GUID_SIZE + 1];
//
//DllGetClassObject
//
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,
LPVOID *ppv)
{
CUserInfoFactory *pCUserInfoFactory = NULL;
HRESULT hr = NOERROR;
if (CLSID_UserInfo == rclsid)
{
//Create the UserInfo classFactory
pCUserInfoFactory = new CUserInfoFactory();
//Check for out of memory error
if (NULL == pCUserInfoFactory)
return E_OUTOFMEMORY;
//Get the requested interface
hr = pCUserInfoFactory->QueryInterface(riid, ppv);
if (FAILED(hr))
{
delete pCUserInfoFactory;
pCUserInfoFactory = NULL;
return hr;
}
}
else
//Object not supported
hr = CLASS_E_CLASSNOTAVAILABLE;
return hr;
}//DllGetClassObject
//
//DllCanUnloadNow
//
STDAPI DllCanUnloadNow(void)
{
if (ServerCanUnloadNow())
return S_OK;
else
return S_FALSE;
}//DllCanUnloadNow
//
//SetRegKeyValue
//
BOOL SetRegKeyValue(LPTSTR lpszkey, LPTSTR lpszSubKey,
LPTSTR lpszValue)
{
BOOL bOk = FALSE;
long lErrorCode;
HKEY hKey;
_TCHAR szKey[MAX_STRING_LENGTH + 1];
_tcscpy(szKey, lpszkey);
if (NULL != lpszSubKey)
{
_tcscat(szKey, _TEXT("\\"));
_tcscat(szKey, lpszSubKey);
}
lErrorCode = RegCreateKeyEx(HKEY_CLASSES_ROOT, szkey, 0,
NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
NULL, &hKey, NULL);
if (ERROR_SUCCESS == lErrorCode)
{
lErrorCode = RegSetValueEx(hKey, NULL, 0, REG_SZ,
(BYTE *)lpszValue, sizeof(lpszValue) /
sizeof(_TCHAR));
if (ERROR_SUCCESS == lErrorCode)
bOk = TRUE;
RegCloseKey(hKey);
}
return bOk;
}//SetRegKeyValue
//
//ServerCanUnloadNow
//
BOOL ServerCanUnloadNow(void)
{
//The server can unload if there are no outstanding
//objects or class factory locks
if(0 == g_cObjects && 0 == g-cLocks)
return TRUE;
else
return FALSE;
}//ServerCanUnloadNow
//
//UnloadServer
//
void UnloadServer(void)
{
//Since DLLs aren't responsible for unloading themselves,
//simply return
return;
}//UnloadServer
//
//DllMain
//
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason,
LPVOID lpReserved)
{
//Save the dll module handle for later use
if (DLL_PROCESS_ATTACH == dwReason)
g_hModule = hModule;
return TRUE;
}//DllMain
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Listing 2-7. UserInfo.h
//
//UserInfo.h
//
#if !defined USERINFO_H
#define USERINFO_H
#include "UserInfo_i.h"
//
//CUserInfo object
//
class CUserInfo : IUserInfo
{
private:
ULONG m-cRef;
short m_nAge;
LPSTR m-lpszName;
BYTE m-bySex;
public:
//IUnknown
STDMETHODIMP QueryInterface(REFIID iid, LPVOID *ppv);
STDMETHODIMP_(ULONG)AddRef(void);
STDMETHODIMP_(ULONG)Release(void);
//IUserInfo
STDMETHODIMP get_Age(short *nRetAge);
STDMETHODIMP put_Age(short nAge);
STDMETHODIMP get_Name(LPSTR *lpszRetName);
STDMETHODIMP put_Name(LPSTR lpszName);
STDMETHODIMP get_Sex(BYTE *byRetSex);
STDMETHODIMP put_Sex(BYTE bySex);
//Constructor
CUserInfo();
//Destructor
~CUserInfo();
};//CUserInfo
//
//CUserInfoFactory
//
class CUserInfoFactory : public IClassFactory
{
private:
ULONG m-cRef;
public:
//IUnknown
STDMETHODIMP QueryInterface(REFIID iid, LPVOID *ppv);
STDMETHODIMP_(ULONG)AddRef(void);
STDMETHODIMP_(ULONG)Release(void);
//IClassFactory
STDMETHODIMP CreateInstance(IUnknown* pUnknownOuter,
REFIID iid, LPVOID *ppv);
STDMETHODIMP LockServer(BOOL block);
//Constructor
CUserInfoFactory*()
{
m_cRef = 0;
}
};//CUserInfoFactory
#endif
Listing 2-8. UserInfo.cpp
//
//UserInfo.cpp
//
#include "UserInfo.h"
//
//Forward declarations
//
extern BOOL ServerCanUnloadNow(void);
extern void UnloadServer(void);
//
//Global variables
//
extern ULONG g_cObjects;
extern ULONG g_cLocks;
//
//CUserInfo
//
//
//get_Age
//
STDMETHODIMP CUserInfo::get_Age(short *nRetAge)
{
*nRetAge = m_nAge;
return NOERROR;
}//get_Age
//
//put-Age
//
STDMETHODIMP CUserInfo::put_Age(short nAge)
{
m_nAge = nAge;
return NOERROR;
}//put_Age
//
//get_Name
//
STDMETHODIMP CUserInfo::get_Name(LPSTR *lpszRetName)
{
*lpszRetName = m_lpszName;
return NOERROR;
}//get_Name
//
//put_Name
//
STDMETHODIMP CUserInfo::put_Name(LPSTR lpszName)
{
long lStringLen;
//
//get_Sex
//
STDMETHODIMP CUserInfo::get_Sex(BYTE *byRetSex)
{
*byRetSex = m_bySex;
return NOERROR;
}//get_Sex
//
//put_Sex
//
STDMETHODIMP CUserInfo::put_Sex(BYTE bySex)
{
m_bySex = bySex;
return NOERROR;
}//put_Sex
//
//QueryInterface
//
STDMETHODIMP CUserInfo::QueryInterface(REFIID iid, LPVOID *ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)this;
else if (IID_IUserInfo == iid)
*ppv = (LPVOID)(IUserInfo *)this;
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown *)*ppv)->AddRef();
return NOERROR;
}//QueryInterface
//
//AddRef
//
STDMETHODIMP_(ULONG)CUserInfo::AddRef(void)
{
return ++m_cRef;
}//AddRef
//
//Release
//
STDMETHODIMP_(ULONG)CUserInfo::Release(void)
{
m_cRef-;
if (0 == m-cRef)
{
delete this;
//Decrement the global object count
g_cObjects-;
//See if it's alright to unload the server
if (::ServerCanUnloadNow())
::UnloadServer();
return 0;
}
return m_cRef;
}//Release
//
//Constructor
//
CUserInfo::CUserInfo()
{
m_cRef = 0;
m_nAge = 0;
m_lpszName = NULL;
m_bySex = 'M';
}//CUserInfo
//
//Destructor
//
CUserInfo::~CUserInfo()
{
if (m_lpszName)
delete[] m_lpszName;
}//~CUserInfo
//
//CUserInfoFactory Class Factory
//
//
//CreateInstance
//
STDMETHODIMP CUserInfoFactory::CreateInstance
(Iunknown* pUnknownOuter, REFIID iid, LPVOID *ppv)
{
HRESULT hr;
CUserInfo *pCUserInfo = NULL;
*ppv = NULL;
//This object doesn't support aggregation
if (NULL != pUnknownOuter)
return CLASS_E_NOAGGREGATION;
//Create the CUserInfo object
pCUserInfo = new CUserInfo();
if (NULL == pCUserInfo)
return E_OUTOFMEMORY;
//Retrieve the requested interface
hr = pCUserInfo->QueryInterface(iid, ppv);
if (FAILED(hr))
{
delete pCUserInfo;
pCUserInfo = NULL;
return hr;
}
//Increment the global object counter
g_cObjects++;
return NOERROR;
}//CreateInstance
//
//LockServer
//
STDMETHODIMP CUserInfoFactory::LockServer(BOOL bLock)
{
if (bLock)
g_cLocks++;
else
{
g_cLocks--;
//See if it's alright to unload the server
if (::ServerCanUnloadNow())
::UnloadServer();
}
return NOERROR;
}//LockServer
//
//QueryInterface
//
STDMETHODIMP CUserInfoFactory::QueryInterface
(REFIID iid, LPVOID *ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)this;
else if (IID-IClassFactory == iid)
*ppv = (LPVOID)(IClassFactory *)this;
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown *)*ppv)->AddRef();
return NOERROR;
}//QueryInterface
//
//AddRef
//
STDMETHODIMP_(ULONG) CUserInfoFactory::AddRef(void)
{
return ++m-cRef;
}//AddRef
//
//Release
//
STDMETHODIMP_(ULONG) CUserInfoFactory::Release(void)
{
m_cRef-;
if (0 == m-cRef)
{
delete this;
return 0;
}
return m_cRef;
}//Release
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Before you can use the UserInfo server, you must register it. To register the UserInfo
server, use REGSVR32.EXE, which should be in either your Windows\System directory or
your Windows\System32 directory, depending on whether you’re running Win95 or Windows
NT. REGSVR32.EXE works in the manner described in the section “Registering Class
Information.” The following shows how to use REGSVR32.EXE to register the UserInfo
server:
C:>Regsvr32 UserInfo.dll
REGSVR32.EXE can also be used to unregister the UserInfo server:
C:>Regsvr32 /u UserInfo.dll
Now all we need is a client capable of using the UserInfo server!
Once the COM Library is initialized, we have to obtain an initial interface pointer to the
UserInfo object. You can obtain an initial interface pointer in several different ways, the most
popular of which is to call CoCreateInstance. The UserInfoClient application calls
CoCreateInstance with the CLSID of the UserInfo COM object and requests a pointer
to its IUnknown interface. However, in order to resolve both the CLSID_UserInfo constant
and the definition of the IUserInfo interface, the UserInfoClient application must
include the UserInfo_i.c and UserInfo_i.h files. As you might recall from earlier in this chapter,
we used the MIDL compiler to compile the UserInfo.idl file, which generated these two files.
Note the use of the CLSCTX_INPROC_SERVER class execution context constant to specify that
we are only interested in an in-process server for the CLSID_UserInfo object:
When the UserInfoClient application has finished manipulating the UserInfo object, it
calls the Release function on all of its outstanding interface pointers, which includes one
IUnknown pointer and one IUserInfo pointer. The UserInfo object then destroys itself:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Uninitializing the COM Library
After releasing the UserInfo COM object, the UserInfoClient prepares to terminate
itself by uninitializing the COM Library with a call to CoUninitialize. Every successful
call to CoInitialize must be matched with a call to CoUninitialize. Once
CoUninitialize has been called, no other COM APIs should be called, with the exception
of the CoGetMalloc function and the memory allocation calls.
//
//UserInfoClient.cpp
//
#include <windows.h>
#include <objbase.h>
#include <tchar.h>
#include "UserInfo_i.h"
//
//Forward declarations
//
void DisplayMessage(LPTSTR lpMessage);
void DisplayUserInfo(IUserInfo *pIUserInfo,
LPTSTR lpszRetrievedMsg);
//
//Global variables
//
const_TCHAR g_lpszApplicationTitle[] = _TEXT("UserInfoClient");
//
//DisplayMessage
//
void DisplayMessage(LPTSTR lpszmessage)
{
MessageBox(NULL, lpszMessage, g_lpszApplicationTitle,
MB_OK | MB_ICONEXCLAMATION);
}//DisplayMessage
//
//DisplayUserInfo
//
void DisplayUserInfo(IUserInfo *pIUserInfo, LPTSTR
lpszRetrievedMsg)
{
char szAge[25];
LPSTR lpszName = NULL;
short nAge;
unsigned char bySex;
char szDisplayText[255];
_TCHAR szMsgText[255];
long lStringLen;
DisplayMessage(lpszRetrievedMsg);
#ifdef _UNICODE
//UNICODE
//Convert from the multibyte character set to the
//wide character set
mbstowcs(szMsgText, szDisplayText, sizeof(szMsgText)
/ sizeof(_TCHAR));
#else
//SBCS and MBCS
_tcscpy(szMsgText, szDisplayText);
#endif
DisplayMessage(szMsgText);
}//DisplayUserInfo
//
//WinMain
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HRESULT hr;
IUnknown *pIUnknown = NULL;
IUserInfo *pIUserInfo = NULL;
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("The UserInfo object has
been created."));
//Begin using the object
Summary
We covered a lot of ground in this chapter, primarily the responsibilities of the COM server and
the COM client.
COM servers are responsible for:
• Allocating GUIDs for each supported object, interface, and type library
• Defining the interfaces supported by each object
• Implementing the functions defined by each interface
• Implementing a class factory capable of creating each supported object
• Exposing a class factory for each supported object
• Registering the appropriate class information
• Unloading themselves when appropriate
COM clients are responsible for:
• Initializing the COM Library
• Obtaining initial and subsequent interfaces
• Manipulating the COM object
• Releasing the COM object when it is no longer needed
• Uninitializing the COM Library
In the next chapter, we build an out-of-process server and examine how its architecture differs
from the UserInfo in-process server that we created in this chapter.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Chapter 3
Building Out-of-Process Servers
IN THIS CHAPTER
• The similarities and differences between in-process and out-of-process
COM servers.
• How out-of-process servers expose their class factories.
• How out-of-process servers expose their registration facilities.
• How to build and register a proxy/stub pair. In the previous chapter,
you learned the responsibilities of COM servers and clients by building
the UserInfo in-process server and the UserInfoClient
application. In this chapter you will further your understanding of COM
clients and servers by building the UserInfoHandler out-of-process
server.
Age short
Name LPSTR
Sex unsigned char
Allocating CLSIDs
b04faa80-8bef-11d0-94ab-00a024a85a21
b04faa81-8bef-11d0-94ab-00a024a85a21
b04faa82-8bef-11d0-94ab-00a024a85a21
b04faa83-8bef-11d0-94ab-00a024a85a21
b04faa84-8bef-11d0-94ab-00a024a85a21
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Defining an Object’s Interfaces
Listing 3-1 shows the IDL definitions for the UserInfoHandler type library, the IUserInfo,
ICopyInfo, IReverseInfo, and ISwapInfo interfaces; and the UserInfo and
UserInfoHandler COM objects. Notice that the ICopyInfo, IReverseInfo, and
ISwapInfo interfaces all inherit from IUnknown, like IUserInfo.
Listing 3-1. UserInfoHandler.idl
//
//UserInfoHandler.idl
//
import "unknwn.idl";
//IID_IUserInfo
//These are the attributes of the IUserInfo interface
[
object,
uuid(acceeb02-86c7-11d0-94ab-0080c74c7e95),
helpstring("IUserInfo Interface.")
]
//Declaration of the IUserInfo interface
interface IUserInfo : IUnknown
{
//List of function definitions for each method supported
//by the interface
//
//[attributes] returntype [calling convention]
//funcname(params);
//
[propget, helpstring("Sets or returns the age of the
user.")]
HRESULT Age([out, retval] short *nRetAge);
[propput, helpstring("Sets or returns the age of the
user.")]
HRESULT Age([in] short nAge);
[propget, helpstring("Sets or returns the name of the
user.")]
HRESULT Name([out, retval] LPSTR *lpszRetName);
[propput, helpstring("Sets or returns the name of the
user.")]
HRESULT Name([in] LPSTR lpszName);
[propget, helpstring("Sets or returns the sex of the
user.")]
HRESULT Sex([out, retval] unsigned char *byRetSex);
[propput, helpstring("Sets or returns the sex of the
user.")]
HRESULT Sex([in] unsigned char bySex);
}
//ICopyInfo
//These are the attributes of the ICopyInfo interface
[
object,
uuid(b04faa82-8bef-11d0-94ab-00a024a85a21),
helpstring("ICopyInfo Interface.")
]
//Definition of the ICopyInfo interface
interface ICopyInfo : IUnknown
{
//List of function definitions for each method supported
//by the interface
//
//[attributes] returntype [calling convention] funcname(params);
//
[helpstring("Copies the Age property from one UserInfo object
to another.")]
HRESULT CopyAge([in] IUserInfo *lpDest, [in]
IUserInfo *lpSrc);
[helpstring("Copies the Name property from one UserInfo object
to another.")]
HRESULT CopyName([in] IUserInfo *lpDest, [in]
IUserInfo *lpSrc);
[helpstring("Copies the Sex property from one UserInfo object
to another.")]
HRESULT CopySex([in] IUserInfo *lpDest, [in]
IUserInfo *lpSrc);
[helpstring("Copies all of the properties from one UserInfo
object to another.")]
HRESULT CopyAll([in] IUserInfo *lpDest, [in]
IUserInfo *lpSrc);
}
//IReverseInfo
//These are the attributes of the IReverseInfo interface
[
object,
uuid(b04faa83-8bef-11d0-94ab-00a024a85a21),
helpstring("IReverseInfo Interface.")
]
//Definition of the IReverseInfo interface
interface IReverseInfo : IUnknown
{
//List of function definitions for each method supported
//by the interface
//
//[attributes] returntype [calling convention]
// funcname(params);
//
[helpstring("Reverses the Age property of a UserInfo
object.")]
HRESULT ReverseAge([in] IUserInfo *lpIUserInfo);
[helpstring("Reverses the Name property of a UserInfo
object.")]
HRESULT ReverseName([in] IUserInfo *lpIUserInfo);
[helpstring("Reverses the Sex property of a UserInfo
object.")]
HRESULT ReverseSex([in] IUserInfo *lpIUserInfo);
[helpstring("Reverses all of the properties of a
UserInfo object.")]
HRESULT ReverseAll([in] IUserInfo *lpIUserInfo);
}
//ISwapInfo
//These are the attributes of the ISwapInfo interface
[
object,
uuid(b04faa84-8bef-11d0-94ab-00a024a85a21),
helpstring("ISwapInfo Interface.")
]
//Definition of the ISwapInfo interface
interface ISwapInfo : IUnknown
{
//List of function definitions for each method supported
//by the interface
//
//[attributes] returntype [calling convention]
// funcname(params);
//
[helpstring("Swaps the Age property of two UserInfo
objects.")]
HRESULT SwapAge([in] IUserInfo *lpIUserInfo1, [in]
IUserInfo *lpIUserInfo2);
[helpstring("Swaps the Name property of two UserInfo objects.")]
HRESULT SwapName([in] IUserInfo *lpIUserInfo1, [in]
IUserInfo *lpIUserInfo2);
[helpstring("Swaps the Sex property of two UserInfo
objects.")]
HRESULT SwapSex([in] IUserInfo *lpIUserInfo1, [in]
IUserInfo *lpIUserInfo2);
[helpstring("Swaps all of the properties of two
UserInfo objects.")]
HRESULT SwapAll([in] IUserInfo *lpIUserInfo1, [in]
IUserInfo *lpIUserInfo2);
}
//LIBID_UserInfoHandler
//These are the attributes of the type library
[
uuid(b04faa80-8bef-11d0-94ab-00a024a85a21),
helpstring("UserInfoHandler Type Library."),
version(1.0)
]
//Definition of the UserInfoHandler type library
library UserInfoHandler
{
importlib("stdole32.tlb");
//CLSID_UserInfo
//Attributes of the UserInfo object
[
uuid(acceeb01-86c7-11d0-94ab-0080c74c7e95),
helpstring("UserInfo Object.")
]
//Definition of the UserInfo object
coclass UserInfo
{
//List all of the interfaces supported by the object
[default] interface IUserInfo;
}
//CLSID_UserInfoHandler
//Attributes of the UserInfoHandler object
[
uuid(b04faa81-8bef-11d0-94ab-00a024a85a21),
helpstring("UserInfoHandler Object.")
]
//Definition of the UserInfoHandler object
coclass UserInfoHandler
{
//List all of the interfaces supported by the object
[default] interface ICopyInfo;
interface IReverseInfo;
interface ISwapInfo;
}
}
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The UserInfoHandler.idl file is compiled using the MIDL compiler to generate five
additional files: UserInfoHandler.tlb, UserInfoHandler_i.c, UserInfoHandler_i.h,
UserInfoHandler_p.c, and dlldata.c. You should be familiar with the UserInfoHandler_i.c
and UserInfoHandler_i.h files, as they are similar to the UserInfo_i.c and UserInfo_i.h files
used in Chapter 2 to create the UserInfo in-process server. The UserInfoHandler_i.c file
contains definitions of the human-readable constants that are used to refer to the type library:
the UserInfo and UserInfoHandler object classes: and the IUserInfo,
ICopyInfo, IReverseInfo, and ISwapInfo interfaces:
Implementing the IUserInfo interface is a snap, as we already have the code from the last
chapter. However, we must still provide implementations for the ICopyInfo,
IReverseInfo, and ISwapInfo interfaces.
To implement each of these interfaces, we must:
• Define a C++ class that multiply inherits from the ICopyInfo, IReverseInfo,
and ISwapInfo abstract base classes.
• Implement each function defined by the derived C++ class.
Listing 3-2 contains the declaration of the CUserInfoHandler C++ class, which
represents the UserInfoHandler COM object. Notice that even though each interface
defines the required IUnknown functions QueryInterface, AddRef, and Release as
its first three functions, there is only one implementation of IUnknown for the entire
CUserInfoHandler object.
Listing 3-2. Definition of the CUserInfoHandler C++ object
STDMETHODIMP CUserInfoHandler::QueryInterface
(REFIID iid, LPVOID *ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)(ICopyInfo *)this;
else if (IID_ICopyInfo == iid)
*ppv = (LPVOID)(ICopyInfo *)this;
else if (IID_IReverseInfo == iid)
*ppv = (LPVOID)(IReverseInfo *)this;
else if (IID_ISwapInfo == iid)
*ppv = (LPVOID)(ISwapInfo *)this;
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown*)*ppv)->AddRef( );
return NOERROR;
}//QueryInterface
Implementations of the remaining interface functions are pretty straightforward and use the
IUserInfo interface to manipulate the information maintained by the UserInfo object.
The following code snippet shows how the CopyAge function uses the IUserInfo
interface to copy the Age property from one UserInfo object to another:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Implementing a Class Factory
The UserInfoHandler server has two class factories: one for creating UserInfo
objects and one for creating UserInfoHandler objects. The UserInfo class factory is
lifted directly from the code in Chapter 2, and the implementation of the
UserInfoHandler class factory is very similar. Implementation of the
UserInfoHandler class factory’s CreateInstance method can be seen in Listing
3-3.
Listing 3-3. Implementation of the UserInfoHandler class factory’s
CreateInstance method
STDMETHODIMP CUserInfoHandlerFactory::CreateInstance
(IUnknown* pUnknownOuter, REFIID iid,
LPVOID *ppv)
{
HRESULT hr;
CUserInfoHandler *pCUserInfoHandler = NULL;
*ppv = NULL;
//This object doesn't support aggregation
if (NULL != pUnknownOuter)
return CLASS_E_NOAGGREGATION;
//Create the CUserInfo object
pCUserInfoHandler = new CUserInfoHandler( );
if (NULL == pCUserInfoHandler)
return E_OUTOFMEMORY;
//Retrieve the requested interface
hr = pCUserInfoHandler->QueryInterface(iid, ppv);
if (FAILED(hr))
{
delete pCUserInfoHandler;
pCUserInfoHandler = NULL;
return hr;
}
//Increment the global object counter
g_cObjects++;
return NOERROR;
}//CreateInstance
Until this point, developing the UserInfoHandler out-of-process server has been
identical to developing the UserInfo in-process server presented in the last chapter. But
from this point on, you will notice that things will be slightly different. Because
out-of-process servers are in a process space apart from their clients, they cannot expose
functions and have clients access them with the Win32 API function GetProcAddress.
Therefore, out-of-process servers cannot expose DllRegister or DllUnregister to
update registry information. To signal an out-of-process server to update information in the
registry, simply run the server and specify either “/RegServer” or “-RegServer” on the
command line.
C:>UserInfoHandler /RegServer
Likewise, to remove registry entries, run the server and specify either “/UnRegServer” or
“-UnRegServer” on the command line:
C:>UserInfoHandler /UnRegServer
This means that the out-of-process servers must parse the command line for these two
arguments. Because an out-of-process server is an executable, it is possible for a user to
execute it directly from the command line without using a client. To determine the context
in which an out-of-process server is started, COM defines the “/Embedding” or
“-Embedding” command-line arguments. Whenever your server is started by a COM client,
COM will pass the “/Embedding” command-line argument to your server. Listing 3-4 shows
how the UserInfoHandler object searches the command line to gather and process any
command-line arguments as part of the WinMain entry-point function, which is called by
the operating system to actually start the application. Out-of-process servers also write
slightly different information to the system registry. Instead of adding the “InprocServer32”
subkey to the CLSID registry entry, out-of-process servers add the “LocalServer32” subkey:
HKEY_CLASSES_ROOT
CLSID
{b04faa81-8bef-11d0-94ab-00a024a85a21} = Description
LocalServer32 = C:\UserInfoHandler\UserInfoHandler.dll
The restriction on exporting functions from an out-of-process server also means that
UserInfoHandler server must devise a different mechanism for exposing class
factories. The COM Library provides the CoRegisterClassObject and
CoRevokeClassObject API functions for exposing object class factories. When an
out-of-process server is first loaded, it must create each class factory that it supports.
Information regarding each class factory must then be registered in a system-wide table with
a call to CoRegisterClassObject as quickly as possible (see sidebar).
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
If the call to CoRegisterClassObject is successful, the server’s supported class factories
will be exposed for client consumption. Every successful call to
CoRegisterClassObject must be paired with a call to CoRevokeClassObject as
part of the server’s shutdown procedure in order to remove registration information exposed by
CoRegisterClassObject. Listing 3-4 shows UserInfoHandler’s WinMain
entry-point function, which is called by the operating system to actually start the application.
Notice that UserInfoHandler registers two class factories: one for the UserInfo object
and one for the UserInfoHandler object. Also notice the calls to CoInitialize and
CoUninitialize. Out-of-process servers are required to call the CoInitialize function
— which is provided to initialize the COM Library — before any other COM Library calls,
except for the CoGetMalloc function and other memory allocation calls. Every successful
call to CoInitialize must be matched with a call to CoUninitialize, so at application
shutdown, every out-of-process server is responsible for calling CoUninitialize. Once
CoUninitialize has been called, no other COM APIs should be called, with the exception
of the CoGetMalloc function and other memory allocation calls.
Listing 3-4. The UserInfoHandler WinMain function
g_hModule = GetModuleHandle(NULL);
// Read the command line.
#ifdef _UNICODE
//UNICODE
szCmdLine = GetCommandLine( );
#else
//SBCS and MBCS
szCmdLine = lpCmdLine;
#endif
//Find the first token
szNextToken = _tcstok(szCmdLine, szTokens);
while (NULL != szNextToken)
{
if (0 == _tcsicmp(szNextToken, _TEXT("UnregServer")))
{
::UnregisterServer(CLSID_UserInfoHandler);
::UnregisterServer(CLSID-UserInfo);
return FALSE;
}
else if (0 == _tcsicmp(szNextToken,
_TEXT("RegServer")))
{
::RegisterServer(CLSID_UserInfoHandler,
_TEXT("DCOM Enterprise Apps -
UserInfoHandler Object."));
::RegisterServer(CLSID_UserInfo,
_TEXT("DCOM Enterprise Apps -
UserInfo Object."));
return FALSE;
}
else if (0 == _tcsicmp(szNextToken,
_TEXT("Embedding")))
{
break;
}
//Find the next token
szNextToken = _tcstok(NULL, szTokens);
}
// Initialize the COM Library.
hr = CoInitialize(NULL);
if (FAILED(hr))
return FALSE;
//Create the UserInfo class factory
pCUserInfoFactory = new CUserInfoFactory( );
//Check for out of memory error
if (NULL != pCUserInfoFactory)
{
//Register the UserInfo class factory
hr = CoRegisterClassObject(CLSID_UserInfo,
(IUnknown *)pCUserInfoFactory, CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE, &g_dwRegisterUserInfo);
if (FAILED(hr))
{
delete pCUserInfoFactory;
pCUserInfoFactory = NULL;
}
else
{
//Create the UserInfoHandler class factory
pCUserInfoHandlerFactory = new
CUserInfoHandlerFactory( );
//Check for out of memory error
if (NULL != pCUserInfoHandlerFactory)
{
//Register the UserInfoHandler class factory
hr = CoRegisterClassObject
(CLSID_UserInfoHandler, (IUnknown *)
pCUserInfoHandlerFactory,
CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE,
&g_dwRegisterUserInfoHandler);
if (FAILED(hr))
{
delete pCUserInfoHandlerFactory;
pCUserInfoHandlerFactory = NULL;
}
else
{
while (GetMessage(&msg, NULL, 0, 0))
DispatchMessage(&msg);
//Unregister the UserInfoHandler
//class factory
CoRevokeClassObject
(g_dwRegisterUserInfoHandler);
}
}
//Unregister the UserInfo class factory
CoRevokeClassObject(g_dwRegisterUserInfo);
}
}
//Uninitialize the COM Library.
CoUninitialize( );
return (msg.wParam);
}//WinMain
Server Unloading
Unlike in-process servers, out-of-process servers are totally capable of unloading themselves.
When there are no locks, and no instantiated objects, out-of-process servers can unload
themselves by posting a WM_QUIT message to their primary thread’s message queue using the
Win32 API function PostQuitMessage.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Marshaling
Since the UserInfoHandler server maintains a process space apart from its clients,
we’ll need to create a proxy/stub pair in order for the client and server to communicate
across process boundaries. Whenever a client invokes an interface function of an
out-of-process server, the proxy — which runs in the client’s address space — packages the
required parameters and communicates them to the corresponding stub running in the
server’s address space. The stub unpackages the parameters and executes the function call,
then packages any return values and transmits them back to the proxy. The proxy then
unpackages the return values and forwards them to the actual client. When a proxy and stub
are located on the same physical machine, they communicate using Local Procedure Calls
(LPCs). However, when they are located on separate machines, proxies and stubs
communicate using Remote Procedure Calls (RPCs). The entire process, including the
determination of when to use LPCs as opposed to RPCs, is totally transparent to the
developer. The entire marshaling process is shown in Figure 3-1.
Because the proxy and stub are contained in the same module, we only have to build one
proxy/stub pair. To build a proxy/stub pair for the UserInfoHandler server, we must
first create a separate DLL project. Add into this project the following files:
UserInfoHandler_i.c, UserInfoHandler_i.h, UserInfoHandler_p.c, and dlldata.c. As you
may recall, all of these files were generated by the MIDL compiler when it processed our
UserInfoHandler.idl file. You are already familiar with UserInfoHandler_i.c and
UserInfoHandler_i.h, so we’ll concentrate on UserInfoHandler_p.c and dlldata.c.
UserInfoHandler_p.c actually contains all the code necessary to build a proxy and a stub
for the interfaces defined in UserInfoHandler.idl. All it’s lacking is the code responsible
for creating the DLL itself. The code responsible for creating the DLL is contained in
dlldata.c. Before you are ready to build your proxy project, you must do two things:
• You must include REGISTER_PROXY_DLL in your preprocessor definitions.
• You must create a .def file for the proxy project.
By including REGISTER_PROXY_DLL in your preprocessor definitions, you instruct the
compiler to automatically include default implementations for DllGetClassObject,
DllCanUnloadNow, GetProxyDllInfo, DllRegisterServer, and
DllUnregisterServer. This additional code is necessary in order for the proxy/stub
pair to be able to register itself.
As you can probably imagine, proxy/stub pairs require slightly different registry settings
than COM servers. Under the registry key HKEY_CLASSES_ROOT\Interface, the
proxy/stub will add the IID of each supported interface as a subkey. Under each of these
subkeys, the proxy/stub will add the ProxyStubClsid32 subkey, setting its value equal to the
CLSID for the proxy/stub. This same CLSID is then registered under the
HKEY_CLASSES_ROOT\CLSID key, and it has the InprocServer32 subkey, with a value
equal to the path of the proxy/stub DLL:
HKEY_CLASSES_ROOT
CLSID
{acceeb02-86c7-11d0-94ab-0080c74c7e95} = PSFactoryBuffer
InprocServer32 = C:\UserInfoHandlerProxy.dll
.
.
.
HKEY_CLASSES_ROOT
Interface
{acceeb02-86c7-11d0-94ab-0080c74c7e95} = IUserInfo
ProxyStubClsid32 =
{acceeb02-86c7-11d0-94ab-0080c74c7e95}
Because these entry points must be exposed, you must create a .def file to export them.
Following is the contents of the UserInfoHandlerProxy.def file:
LIBRARY UserInfoHandlerProxy
DESCRIPTION "UserInfoHandler Proxy."
EXPORTS
DllRegisterServer @1 PRIVATE
DllUnregisterServer @2 PRIVATE
GetProxyDllInfo @3 PRIVATE
DllGetClassObject @4 PRIVATE
DllCanUnloadNow @5 PRIVATE
Now you have everything you need to build your proxy/stub pair, and you only had to write
eight lines of code in a .def file!
The UserInfoHandlerProxy.dll is registered just like any other DLL, using
REGSVR32.EXE:
C:>Regsvr32 UserInfoHandlerProxy.dll
It is also unregistered like any other DLL:
C:>Regsvr32 UserInfoHandlerProxy.dll /u
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Whenever a client invokes a function on the IUserInfo, ICopyInfo, IReverseInfo, or
ISwapInfo interfaces, your new proxy/stub pair will be invoked automatically to
appropriately marshal and unmarshal any required data. Listings 3-5, 3-6, and 3-7 contain the
source code for EXEMain.cpp, UserInfoHandler.h, and UserInfoHandler.cpp. As you look
at the source code, notice how the EXE-specific code has been confined to just the
EXEMain.cpp source file, making it easier to implement the UserInfoHandler COM
object as a DLL if you so choose.
Listing 3-5. EXEMain.cpp
//
//ExeMain.cpp
//
#define MAX_STRING_LENGTH 255
#define GUID_SIZE 128
#include <objbase.h>
#include <tchar.h>
#include "UserInfo.h"
#include "UserInfoHandler.h"
//
//Forward declarations
//
BOOL RegisterServer(CLSID clsid, LPSTR lpszDescription);
BOOL UnregisterServer(CLSID clsid);
BOOL SetRegKeyValue(LPTSTR lpszkey, LPTSTR lpszSubKey,
LPTSTR lpszValue);
BOOL ServerCanUnloadNow(void);
void UnloadServer(void);
//
//Global variables
//
HMODULE g_hModule = NULL;
ULONG g_cObjects = 0;
ULONG g_cLocks = 0;
DWORD g_dwRegisterUserInfo;
DWORD g_dwRegisterUserInfoHandler;
//
//Register the server
//
BOOL RegisterServer(CLSID clsid, LPSTR lpszDescription)
{
BOOL bOK;
_TCHAR szModulePath[MAX_PATH + 1];
_TCHAR szCLSID[GUID_SIZE + 1];
_TCHAR szCLSIDKey[MAX_STRING_LENGTH + 1];
wchar_t wszGUID[GUID_SIZE + 1];
return bOK;
}//RegisterServer
//
//Unregister the server
//
BOOL UnregisterServer(CLSID clsid)
{
long lErrorCode;
_TCHAR szCLSID[GUID_SIZE + 1];
_TCHAR szCLSIDKey[MAX_STRING_LENGTH + 1];
_TCHAR szLocalServer32Key[MAX_STRING_LENGTH + 1];
wchar_t wszGUID[GUID_SIZE + 1];
//
//SetRegKeyValue
//
BOOL SetRegKeyValue(LPTSTR lpszkey, LPTSTR lpszSubKey,
LPTSTR lpszValue)
{
BOOL bOk = FALSE;
long lErrorCode;
HKEY hkey;
_TCHAR szKey[MAX_STRING_LENGTH + 1];
_tcscpy(szKey, lpszKey);
if (NULL != lpszSubKey)
{
_tcscat(szKey, _TEXT("\\"));
_tcscat(szKey, lpszSubKey);
}
lErrorCode = RegCreateKeyEx(HKEY_CLASSES_ROOT, szKy, 0,
NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
NULL, &hKey, NULL);
if (ERROR_SUCCESS == lErrorCode)
{
lErrorCode = RegSetValueEx(hKey, NULL, 0, REG_SZ,
(BYTE *)lpszValue, sizeof(lpszValue) /
sizeof(_TCHAR));
if (ERROR_SUCCESS == lErrorCode)
bOk = TRUE;
RegCloseKey(hKey);
}
return bOk;
}//SetRegKeyValue
//
//ServerCanUnloadNow
//
BOOL ServerCanUnloadNow(void)
{
//The server can unload if there are no outstanding
//objects or class factory locks
if(0 == g_cObjects && 0 == g_cLocks)
return TRUE;
else
return FALSE;
}//ServerCanUnloadNow
//
//UnloadServer
//
void UnloadServer(void)
{
//Unload the server by posting the WM_QUIT to the
//message queue
PostQuitMessage(0);
}//UnloadServer
//
//WinMain
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HRESULT hr;
MSG msg;
_TCHAR szTokens[ ] = _TEXT("-/");
LPTSTR szNextToken;
LPTSTR szCmdLine;
CUserInfoFactory *pCUserInfoFactory = NULL;
CUserInfoHandlerFactory *pCUserInfoHandlerFactory = NULL;
g_hModule = GetModuleHandle(NULL);
// Read the command line.
#ifdef _UNICODE
//UNICODE
szCmdLine = GetCommandLine();
#else
//SBCS and MBCS
szCmdLine = lpCmdLine;
#endif
//Find the first token
szNextToken = _tcstok(szCmdLine, szTokens);
while (NULL != szNextToken)
{
if (0 == _tcsicmp(szNextToken, _TEXT("UnregServer")))
{
::UnregisterServer(CLSID_UserInfoHandler);
::UnregisterServer(CLSID_UserInfo);
return FALSE;
}
else if (0 == _tcsicmp(szNextToken,
_TEXT("RegServer")))
{
::RegisterServer(CLSID_UserInfoHandler,
_TEXT("DCOM Enterprise Apps -
UserInfoHandler Object."));
::RegisterServer(CLSID_UserInfo,
_TEXT("DCOM Enterprise Apps -
UserInfo Object."));
return FALSE;
}
else if (0 == _tcsicmp(szNextToken,
_TEXT("Embedding")))
{
break;
}
//Find the next token
szNextToken = _tcstok(NULL, szTokens) ;
}
// Initialize the COM Library.
hr = CoInitialize(NULL);
if (FAILED(hr))
return FALSE;
//Create the UserInfo class factory
pCUserInfoFactory = new CUserInfoFactory();
//Check for out of memory error
if (NULL != pCUserInfoFactory)
{
//Register the UserInfo class factory
hr = CoRegisterClassObject(CLSID_UserInfo,
(IUnknown *)pCUserInfoFactory,
CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE,
&g_dwRegisterUserInfo);
if (FAILED(hr))
{
delete pCUserInfoFactory;
pCUserInfoFactory = NULL;
}
else
{
//Create the UserInfoHandler class factory
pCUserInfoHandlerFactory = new
CUserInfoHandlerFactory();
//Check for out of memory error
if (NULL != pCUserInfoHandlerFactory)
{
//Register the UserInfoHandler class
//factory
hr = CoRegisterClassObject
(CLSID_UserInfoHandler, (IUnknown *)
pCUserInfoHandlerFactory,
CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE,
&g_dwRegisterUserInfoHandler);
if (FAILED(hr))
{
delete pCUserInfoHandlerFactory;
pCUserInfoHandlerFactory = NULL;
}
else
{
while (GetMessage(&msg, NULL, 0, 0))
DispatchMessage(&msg);
//Unregister the UserInfoHandler
//class factory
CoRevokeClassObject
(g_dwRegisterUserInfoHandler);
}
}
//Unregister the UserInfo class factory
CoRevokeClassObject(g_dwRegisterUserInfo);
}
}
//Uninitialize the COM Library.
CoUninitialize( );
return (msg.wParam);
}//WinMain
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Listing 3-6. UserInfoHandler.h
//
//UserInfoHandler.h
//
#if !defined USERINFOHANDLER_H
#define USERINFOHANDLER_H
#include "UserInfoHandler_i.h"
//
//CUserInfoHandler
//
class CUserInfoHandler : public ICopyInfo,
public IReverseInfo,
public ISwapInfo
{
private:
ULONG m_cRef;
public:
//IUnknown
STDMETHODIMP QueryInterface(REFIID iid, LPVOID *ppv);
STDMETHODIMP_(ULONG)AddRef(void);
STDMETHODIMP_(ULONG)Release(void);
//ICopyInfo
STDMETHODIMP CopyAge(IUserInfo *lpDest, IUserInfo *lpSrc);
STDMETHODIMP CopyName(IUserInfo *lpDest, IUserInfo *lpSrc);
STDMETHODIMP CopySex(IUserInfo *lpDest, IUserInfo *lpSrc);
STDMETHODIMP CopyAll(IUserInfo *lpDest, IUserInfo *lpSrc);
//IReverseInfo
STDMETHODIMP ReverseAge(IUserInfo *lpIUserInfo);
STDMETHODIMP ReverseName(IUserInfo *lpIUserInfo);
STDMETHODIMP ReverseSex(IUserInfo *lpIUserInfo);
STDMETHODIMP ReverseAll(IUserInfo *lpIUserInfo);
//ISwapInfo
STDMETHODIMP SwapAge(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2);
STDMETHODIMP SwapName(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2);
STDMETHODIMP SwapSex(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2);
STDMETHODIMP SwapAll(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2);
//Constructor
CUserInfoHandler()
{
m_cRef = 0;
}
};//CUserInfoHandler
//
//CUserInfoHandlerFactory
//
class CUserInfoHandlerFactory : public IClassFactory
{
private:
ULONG m_cRef;
public:
//IUnknown
STDMETHODIMP QueryInterface(REFIID iid, LPVOID *ppv);
STDMETHODIMP_(ULONG)AddRef(void);
STDMETHODIMP_(ULONG)Release(void);
//IClassFactory
STDMETHODIMP CreateInstance(IUnknown* pUnknownOuter,
REFIID iid, LPVOID *ppv);
STDMETHODIMP LockServer(BOOL bLock);
//Constructor
CUserInfoHandlerFactory()
{
m_cRef = 0;
}
};//CUserInfoHandlerFactory
#endif
Listing 3-7. UserInfoHandler.cpp
//
//UserInfoHandler.cpp
//
#include "UserInfoHandler.h"
//
//Forward declarations
//
extern BOOL ServerCanUnloadNow(void);
extern void UnloadServer(void);
//
//Global variables
//
extern ULONG g_cObjects;
extern ULONG g_cLocks;
//
//CUserInfoHandler
//
//
//CopyAge
//
STDMETHODIMP CUserInfoHandler::CopyAge(IUserInfo *lpDest,
IUserInfo *lpSrc)
{
HRESULT hr;
short nTmpAge;
//
//CopyName
//
STDMETHODIMP CUserInfoHandler::CopyName(IUserInfo *lpDest,
IUserInfo *lpSrc)
{
HRESULT hr;
LPSTR lpszTmpName = NULL;
//
//CopySex
//
STDMETHODIMP CUserInfoHandler::CopySex(IUserInfo *lpDest,
IUserInfo *lpSrc)
{
HRESULT hr;
BYTE byTmpSex;
//
//CopyAll
//
STDMETHODIMP CUserInfoHandler::CopyAll(IUserInfo *lpDest,
IUserInfo *lpSrc)
{
HRESULT hr;
//
//ReverseAge
//
STDMETHODIMP CUserInfoHandler::ReverseAge(IUserInfo *lpIUserInfo)
{
HRESULT hr;
short nTmpAge;
short nReversedAge;
char szAgeString[10];
char szReversedAgeString[10];
//
//ReverseName
//
STDMETHODIMP CUserInfoHandler::ReverseName(IUserInfo
*lpIUserInfo)
{
HRESULT hr;
LPSTR lpszTmpName = NULL;
LPSTR lpszReversedName = NULL;
//
//ReverseSex
//
STDMETHODIMP CUserInfoHandler::ReverseSex(IUserInfo
*lpIUserInfo)
{
HRESULT hr;
BYTE byTmpSex;
//
//ReverseAll
//
STDMETHODIMP CUserInfoHandler::ReverseAll(IUserInfo
*lpIUserInfo)
{
HRESULT hr;
//
//SwapAge
//
STDMETHODIMP CUserInfoHandler::SwapAge(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2)
{
HRESULT hr;
short nTmpAge1;
short nTmpAge2;
//
//SwapName
//
STDMETHODIMP CUserInfoHandler::SwapName(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2)
{
HRESULT hr;
LPSTR lpszTmpName1 = NULL;
LPSTR lpszTmpName2 = NULL;
LPSTR lpszCopiedName1 = NULL;
LPSTR lpszCopiedName2 = NULL;
long lStringLen;
//
//SwapSex
//
STDMETHODIMP CUserInfoHandler::SwapSex(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2)
{
HRESULT hr;
BYTE byTmpSex1;
BYTE byTmpSex2;
//
//SwapAll
//
STDMETHODIMP CUserInfoHandler::SwapAll(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2)
{
HRESULT hr;
//Swap each property
//Swap Age
hr = SwapAge(lpIUserInfo1, lpIUserInfo2);
if (SUCCEEDED(hr))
{
//Swap Name
hr = SwapName(lpIUserInfo1, lpIUserInfo2);
if (SUCCEEDED(hr))
{
//Swap Sex
hr = SwapSex(lpIUserInfo1, lpIUserInfo2);
}
}
return hr;
}//SwapAll
//
//QueryInterface
//
STDMETHODIMP CUserInfoHandler::QueryInterface(REFIID iid,
LPVOID *ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)(ICopyInfo *)this;
else if (IID_ICopyInfo == iid)
*ppv = (LPVOID)(ICopyInfo *)this;
else if (IID_IReverseInfo == iid)
*ppv = (LPVOID)(IReverseInfo *)this;
else if (IID_ISwapInfo == iid)
*ppv = (LPVOID)(ISwapInfo *)this;
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown *)*ppv)->AddRef();
return NOERROR;
}//QueryInterface
//
//AddRef
//
STDMETHODIMP_(ULONG)CUserInfoHandler::AddRef(void)
{
return ++m_cRef;
}//AddRef
//
//Release
//
STDMETHODIMP_(ULONG)CUserInfoHandler::Release(void)
{
m_cRef-;
if (0 == m_cRef)
{
delete this;
//Decrement the global object count
g_cObjects-;
//See if it's alright to unload the server
if (::ServerCanUnloadNow( ))
::UnloadServer( );
return 0;
}
return m_cRef;
}//Release
//
//CUserInfoHandlerFactory Class Factory
//
//
//CreateInstance
//
STDMETHODIMP CUserInfoHandlerFactory::CreateInstance
(IUnknown* pUnknownOuter, REFIID iid, LPVOID *ppv)
{
HRESULT hr;
CUserInfoHandler *pCUserInfoHandler = NULL;
*ppv = NULL;
//This object doesn't support aggregation
if (NULL != pUnknownOuter)
return CLASS_E_NOAGGREGATION;
//Create the CUserInfo object
pCUserInfoHandler = new CUserInfoHandler( );
if (NULL == pCUserInfoHandler)
return E_OUTOFMEMORY;
//Retrieve the requested interface
hr = pCUserInfoHandler->QueryInterface(iid, ppv);
if (FAILED(hr))
{
delete pCUserInfoHandler;
pCUserInfoHandler = NULL;
return hr;
}
//Increment the global object counter
g_cObjects++;
return NOERROR;
}//CreateInstance
//
//LockServer
//
STDMETHODIMP CUserInfoHandlerFactory::LockServer(BOOL bLock)
{
if (bLock)
g_cLocks++;
else
{
g_cLocks-;
//See if it's alright to unload the server
if (::ServerCanUnloadNow( ))
::UnloadServer( );
}
return NOERROR;
}//LockServer
//
//QueryInterface
//
STDMETHODIMP CUserInfoHandlerFactory::QueryInterface
(REFIID iid, LPVOID *ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)this;
else if (IID_IClassFactory == iid)
*ppv = (LPVOID)(IClassFactory *)this;
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown *)*ppv)->AddRef( );
return NOERROR;
}//QueryInterface
//
//AddRef
//
STDMETHODIMP_(ULONG) CUserInfoHandlerFactory::AddRef(void)
{
return ++m_cRef;
}//AddRef
//
//Release
//
STDMETHODIMP_(ULONG) CUserInfoHandlerFactory::Release(void)
{
m_cRef-;
if (0 == m_cRef)
{
delete this;
return 0;
}
return m_cRef;
}//Release
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The UserInfoHandlerClient application is, as its name suggests, a
sample UserInfoHandler client. The UserInfoHandlerClient puts
the UserInfoHandler server through its paces by using several functions
from each of the supported interfaces. I have included the source code for the
UserInfoHandlerClient application on the CD-ROM for your viewing
pleasure, so enjoy!
Summary
In this chapter, we learned:
• That because out-of-process servers don’t share the same address
space as their clients, they must rely on proxy/stub pairs to marshal and
unmarshal data.
• How to use the MIDL-generated files to create proxy/stub pairs.
• The different techniques that out-of-process servers must use to
expose COM’s required registration and class factory information, as
they cannot export functions because of process boundary restraints.
• That components are implemented in the exact same manner,
regardless of whether they are part of an in-process or out-of-process
server.
In the next chapter you learn about containment and aggregation, COM’s
object-reuse techniques.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Chapter 4
Reusing COM Objects
IN THIS CHAPTER
• How to reuse a COM object through containment
• How to reuse a COM object through aggregation
OFTEN, THE COM objects at your disposal may provide only a limited subset of the
functionality that you desire; when that happens, you must develop new components that
fully implement the desired level of functionality. However, COM provides two techniques
for reusing and extending the functionality of existing COM objects. These two
mechanisms, containment and aggregation, are the focus of this chapter.
Understanding Containment
Containment is the simplest form of COM object reuse. In containment, the new COM
object, known as the outer object, simply acts as a client of the existing COM object,
which is known as the inner object. Like all COM objects, the outer object exposes its own
set of interfaces. However, instead of being solely responsible for providing the
implementation for each of its interface functions, the outer object relies on the
functionality supplied by the inner object for assistance (see Figure 4-1).
Figure 4-1 In containment, the outer object acts as a client of the inner object.
To further illustrate the containment technique, we will build the MemberInfo COM
object. Using the containment technique, the MemberInfo COM object expands on the
UserInfo object that we developed in Chapter 2. While the UserInfo object provides
such basic information as the user’s name and age, the MemberInfo object provides
additional information such as the user’s mailing address and telephone number.
The MemberInfo object begins with the usual routine of allocating GUIDs and defining
interfaces. The MemberInfo object has only one interface, IMemberInfo, which
defines the properties of the MemberInfo object (see Table 4-1).
Table 4-1 Memberinfo Object Properties
The MemberInfo object and the IMemberInfo interface are then defined in the
MemberInfo.idl file using IDL (see Listing 4-1). The MemberInfo.idl file is then
compiled using MIDL to generate a type library and the support files necessary to create an
IMemberInfo interface proxy/stub pair.
Listing 4-1. MemberInfo.idl
//
//MemberInfo.idl
//
import "unknwn.idl";
//IID_IMemberInfo
//These are the attributes of the IMemberInfo interface
[
object,
uuid(930e5c02-a792-11d0-94ab-00a024a85a21),
helpstring("IMemberInfo Interface.")
]
//Declaration of the IMemberInfo interface
interface IMemberInfo : IUnknown
{
//List of function definitions for each method supported
//by the interface
//
//[attributes] returntype [calling convention]
// funcname(params);
//
[propget, helpstring("Sets or returns the address of the
member.")]
HRESULT Address([out, retval] LPSTR *lpszRetAddress);
[propput, helpstring("Sets or returns the address of the
member.")]
HRESULT Address([in] LPSTR lpszAddress);
[propget, helpstring("Sets or returns the city of the
member.")]
HRESULT City([out, retval] LPSTR *lpszRetCity);
[propput, helpstring("Sets or returns the city of the
member.")]
HRESULT City([in] LPSTR lpszCity);
[propget, helpstring("Sets or returns the name of the
member.")]
HRESULT Name([out, retval] LPSTR *lpszRetName);
[propput, helpstring("Sets or returns the name of the
member.")]
HRESULT Name([in] LPSTR lpszName);
[propget, helpstring("Sets or returns the phone number
of the member.")]
HRESULT Phone([out, retval] LPSTR *lpszRetPhone);
[propput, helpstring("Sets or returns the phone number
of the member.")]
HRESULT Phone([in] LPSTR lpszPhone);
[propget, helpstring("Sets or returns the sex of the
member.")]
HRESULT Sex([out, retval] unsigned char *byRetSex);
[propput, helpstring("Sets or returns the sex of the
member.")]
HRESULT Sex([in] unsigned char bySex);
[propget, helpstring("Sets or returns the state of the
member.")]
HRESULT State([out, retval] LPSTR *lpszRetState);
[propput, helpstring("Sets or returns the state of the
member.")]
HRESULT State([in] LPSTR lpszState);
[propget, helpstring("Sets or returns the zip code of the
member.")]
HRESULT Zip([out, retval] LPSTR *lpszRetZip);
[propput, helpstring("Sets or returns the zip code of the
member.")]
//LIBID_MemberInfo
//These are the attributes of the type library
[
uuid(930e5c00-a792-11d0-94ab-00a024a85a21),
helpstring("MemberInfo Type Library."),
version(1.0)
]
//Definition of the MemberInfo type library
library MemberInfo
{
//CLSID_MemberInfo
//Attributes of the MemberInfo object
[
uuid(930e5c01-a792-11d0-94ab-00a024a85a21),
helpstring("MemberInfo Object.")
]
//Definition of the MemberInfo object
coclass MemberInfo
{
//List all of the interfaces supported by the object
[default] interface IMemberInfo;
}
}
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The next step is to define the CMemberInfo C++ class and provide
implementations for the IMemberInfo interface. The containment process begins
with the creation of the inner object, which in this case is the UserInfo object. The
COM API CoCreateInstance is used to create the inner UserInfo object as
part of the CMemberInfo::Initialize function. Notice that the
CMemberInfo member variable m_pIUserInfo is used to maintain the
reference to the IUserInfo interface returned by the call to
CoCreateInstance:
HRESULT CMemberInfo::Initialize(void)
{
return CoCreateInstance(CLSID_UserInfo, NULL,
CLSCTX_INPROC_SERVER, IID_IUserInfo,
(LPVOID *)&m_pIUserInfo);
}//Initialize
CMemberInfo::Initialize is called within
CMemberInfoFactory::CreateInstance, the class factory function
responsible for creating MemberInfo objects:
STDMETHODIMP CMemberInfoFactory::CreateInstance
(IUnknown* pUnknownOuter, REFIID iid,
LPVOID *ppv)
{
HRESULT hr;
CMemberInfo *pCMemberInfo = NULL;
*ppv = NULL;
//This object doesn’t support aggregation
if (NULL != pUnknownOuter)
return CLASS_E_NOAGGREGATION;
//Create the CMemberInfo object
pCMemberInfo = new CMemberInfo();
if (NULL == pCMemberInfo)
return E_OUTOFMEMORY;
//Initialize the new object
hr = pCMemberInfo->Initialize();
if (FAILED(hr))
goto cleanUP;
//Retrieve the requested interface
hr = pCMemberInfo->QueryInterface(iid, ppv);
if (FAILED(hr))
goto cleanUP;
//Increment the global object counter
g_cObjects++;
return NOERROR;
cleanUP:
//Some type of error occurred, cleanup before
//returning
if (pCMemberInfo)
{
delete pCMemberInfo;
pCMemberInfo = NULL;
}
return hr;
}//CreateInstance
The MemberInfo object then uses the IUserInfo interface reference stored in
the m_pIUserInfo member variable to manipulate the inner UserInfo object
during the MemberInfo object’s implementation of both the Name and Sex
properties:
CMemberInfo::~CMemberInfo()
{
if (m_pIUserInfo)
m_pIUserInfo->Release();
if (m_lpszAddress)
delete[] m_lpszAddress;
if (m_lpszCity)
delete[] m_lpszCity;
if (m_lpszPhone)
delete[] m_lpszPhone;
if (m_lpszState)
delete[] m_lpszState;
if (m_lpszZip)
delete[] m_lpszZip;
}//~CMemberInfo
Since the MemberInfo object is an in-process object, the MemberInfo client
performs the required calls to CoInitialize and CoUninitialize, relieving
the MemberInfo object of that responsibility. Definitions of the CMemberInfo
and CMemberInfoFactory C++ classes can be seen in MemberInfo.h, which is
provided in Listing 4-2. Their implementations can be seen in MemberInfo.cpp,
which is also provided, in Listing 4-3.
Listing 4-2. MemberInfo.h
//
//MemberInfo.h
//
#if !defined MEMBERINFO_H
#define MEMBERINFO_H
#include "MemberInfo_i.h"
#include "UserInfo_i.h"
//
//CMemberInfo
//
class CMemberInfo : IMemberInfo
{
private:
ULONG m_cRef;
IUserInfo *m_pIUserInfo;
LPSTR m_lpszAddress;
LPSTR m_lpszCity;
LPSTR m_lpszPhone;
LPSTR m_lpszState;
LPSTR m_lpszZip;
public:
//IUnknown
STDMETHODIMP QueryInterface(REFIID iid, LPVOID *ppv);
STDMETHODIMP_(ULONG)AddRef(void);
STDMETHODIMP_(ULONG)Release(void);
//IMemberInfo
STDMETHODIMP get_Address(LPSTR *lpszRetAddress);
STDMETHODIMP put_Address(LPSTR lpszAddress);
STDMETHODIMP get_City(LPSTR *lpszRetCity);
STDMETHODIMP put_City(LPSTR lpszCity);
STDMETHODIMP get_Name(LPSTR *lpszRetName);
STDMETHODIMP put_Name(LPSTR lpszName);
STDMETHODIMP get_Phone(LPSTR *lpszRetPhone);
STDMETHODIMP put_Phone(LPSTR lpszPhone);
STDMETHODIMP get_Sex(BYTE *byRetSex);
STDMETHODIMP put_Sex(BYTE bySex);
STDMETHODIMP get_State(LPSTR *lpszRetState);
STDMETHODIMP put_State(LPSTR lpszState);
STDMETHODIMP get_Zip(LPSTR *lpszRetZip);
STDMETHODIMP put_Zip(LPSTR lpszZip);
HRESULT Initialize(void);
//Constructor
CMemberInfo();
//Destructor
~CMemberInfo();
};//CMemberInfo
//
//CMemberInfoFactory
//
class CMemberInfoFactory : public IClassFactory
{
private:
ULONG m_cRef;
public:
//IUnknown
STDMETHODIMP QueryInterface(REFIID iid, LPVOID *ppv);
STDMETHODIMP_(ULONG)AddRef(void);
STDMETHODIMP_(ULONG)Release(void);
//IClassFactory
STDMETHODIMP CreateInstance(IUnknown* pUnknownOuter,
REFIID iid, LPVOID *ppv);
STDMETHODIMP LockServer(BOOL bLock);
//Constructor
CMemberInfoFactory()
{
m_cRef = 0;
}
};//CMemberInfoFactory
#endif
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Listing 4-3. MemberInfo.pp
//
//MemberInfo.cpp
//
#include "MemberInfo.h"
//
//Forward declarations
//
extern BOOL ServerCanUnloadNow(void);
extern void UnloadServer(void);
//
//Global variables
//
extern ULONG g_cObjects;
extern ULONG g_cLocks;
//
//CMemberInfo
//
//
//get_Address
//
STDMETHODIMP CMemberInfo::get_Address(LPSTR *lpszRetAddress)
{
*lpszRetAddress = m_lpszAddress;
return NOERROR;
}//get_Address
//
//put_Address
//
STDMETHODIMP CMemberInfo::put_Address(LPSTR lpszAddress)
{
long lStringLen;
//
//get_City
//
STDMETHODIMP CMemberInfo::get_City(LPSTR *lpszRetCity)
{
*lpszRetCity = m_lpszCity;
return NOERROR;
}//get_City
//
//put_City
//
STDMETHODIMP CMemberInfo::put_City(LPSTR lpszCity)
{
long lStringLen;
//
//get_Name
//
STDMETHODIMP CMemberInfo::get_Name(LPSTR *lpszRetName)
{
return m_pIUserInfo->get_Name(lpszRetName);
}//get_Name
//
//put_Name
//
STDMETHODIMP CMemberInfo::put_Name(LPSTR lpszName)
{
return m_pIUserInfo->put_Name(lpszName);
}//put_Name
//
//get_Phone
//
STDMETHODIMP CMemberInfo::get_Phone(LPSTR *lpszRetPhone)
{
*lpszRetPhone = m_lpszPhone;
return NOERROR;
}//get_Phone
//
//put_Phone
//
STDMETHODIMP CMemberInfo::put_Phone(LPSTR lpszPhone)
{
long lStringLen;
//
//put_Sex
//
STDMETHODIMP CMemberInfo::put_Sex(BYTE bySex)
{
return m_pIUserInfo->put_Sex(bySex);
}//put_Sex
//
//get_State
//
STDMETHODIMP CMemberInfo::get_State(LPSTR *lpszRetState)
{
*lpszRetState = m_lpszState;
return NOERROR;
}//get_State
//
//put_State
//
STDMETHODIMP CMemberInfo::put_State(LPSTR lpszState)
{
long lStringLen;
//
//get_Zip
//
STDMETHODIMP CMemberInfo::get_Zip(LPSTR *lpszRetZip)
{
*lpszRetZip = m_lpszZip;
return NOERROR;
}//get_Zip
//
//put_Zip
//
STDMETHODIMP CMemberInfo::put_Zip(LPSTR lpszZip)
{
long lStringLen;
//
//QueryInterface
//
STDMETHODIMP CMemberInfo::QueryInterface(REFIID iid,
LPVOID *ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)this;
else if (IID_IMemberInfo == iid)
*ppv = (LPVOID)(IMemberInfo *)this;
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown *)*ppv)->AddRef();
return NOERROR;
}//QueryInterface
//
//AddRef
//
STDMETHODIMP_(ULONG)CMemberInfo::AddRef(void)
{
return ++m_cRef;
}//AddRef
//
//Release
//
STDMETHODIMP_(ULONG)CMemberInfo::Release(void)
{
m_cRef—;
if (0 == m_cRef)
{
delete this;
//Decrement the global object count
g_cObjects—;
//See if it's alright to unload the server
if (::ServerCanUnloadNow())
::UnloadServer();
return 0;
}
return m_cRef;
}//Release
//
//Initialize
//
HRESULT CMemberInfo::Initialize(void)
{
return CoCreateInstance(CLSID_UserInfo, NULL,
CLSCTX_INPROC_SERVER, IID_IUserInfo,
(LPVOID *)&m_pIUserInfo);
}//Initialize
//
//Constructor
//
CMemberInfo::CMemberInfo()
{
m_cRef = 0;
m_pIUserInfo = NULL;
m_lpszAddress = NULL;
m_lpszCity = NULL;
m_lpszPhone = NULL;
m_lpszState = NULL;
m_lpszZip = NULL;
}//CMemberInfo
//
//Destructor
//
CMemberInfo::~CMemberInfo()
{
if (m_pIUserInfo)
m_pIUserInfo->Release();
if (m_lpszAddress)
delete[] m_lpszAddress;
if (m_lpszCity)
delete[] m_lpszCity;
if (m_lpszPhone)
delete[] m_lpszPhone;
if (m_lpszState)
delete[] m_lpszState;
if (m_lpszZip)
delete[] m_lpszZip;
}//~CMemberInfo
//
//CMemberInfoFactory Class Factory
//
//
//CreateInstance
//
STDMETHODIMP CMemberInfoFactory::CreateInstance
(IUnknown* pUnknownOuter, REFIID iid,
LPVOID *ppv)
{
HRESULT hr;
CMemberInfo *pCMemberInfo = NULL;
*ppv = NULL;
//This object doesn't support aggregation
if (NULL != pUnknownOuter)
return CLASS_E_NOAGGREGATION;
//Create the CMemberInfo object
pCMemberInfo = new CMemberInfo();
if (NULL == pCMemberInfo)
return E_OUTOFMEMORY;
//Initialize the new object
hr = pCMemberInfo->Initialize();
if (FAILED(hr))
goto cleanUP;
//Retrieve the requested interface
hr = pCMemberInfo->QueryInterface(iid, ppv);
if (FAILED(hr))
goto cleanUP;
//Increment the global object counter
g_cObjects++;
return NOERROR;
cleanUP:
//Some type of error occurred, cleanup before returning
if (pCMemberInfo)
{
delete pCMemberInfo;
pCMemberInfo = NULL;
}
return hr;
}//CreateInstance
//
//LockServer
//
STDMETHODIMP CMemberInfoFactory::LockServer(BOOL bLock)
{
if (bLock)
g_cLocks++;
else
{
g_cLocks—;
//See if it's alright to unload the server
if (::ServerCanUnloadNow())
::UnloadServer();
}
return NOERROR;
}//LockServer
//
//QueryInterface
//
STDMETHODIMP CMemberInfoFactory::QueryInterface
(REFIID iid, LPVOID *ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)this;
else if (IID_IClassFactory == iid)
*ppv = (LPVOID)(IClassFactory *)this;
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown *)*ppv)->AddRef();
return NOERROR;
}//QueryInterface
//
//AddRef
//
STDMETHODIMP_(ULONG) CMemberInfoFactory::AddRef(void)
{
return ++m_cRef;
}//AddRef
//
//Release
//
STDMETHODIMP_(ULONG) CMemberInfoFactory::Release(void)
{
m_cRef—;
if (0 == m_cRef)
{
delete this;
return 0;
}
return m_cRef;
}//Release
While containment is best suited for those situations in which the interfaces of the inner
object provide some, but not all, of the functionality you desire, there are instances when
an object’s interface provides the exact level of functionality that you desire. So rather
than duplicate and delegate each interface function from the outer object to the inner
object, COM provides an alternative technique for object reuse known as aggregation.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Understanding Aggregation
In aggregation, interfaces of the inner object are exposed directly, as if they
were implemented on the outer object itself (see Figure 4-2).
Figure 4-2 In aggregation, the outer object exposes the interfaces of the inner
object as its own.
Unlike containment, in which the inner object has no idea that it is being used
as part of another object, in aggregation, the inner object is not only aware that
it is being used as part of an aggregate, it must be developed specifically to
support aggregation. To understand why a COM object must be specifically
developed to support aggregation, imagine that you have a pointer to interface
x, which is implemented by the inner object, and you call QueryInterface
requesting interface y, which is implemented by the outer object. Since the
inner object has no knowledge of interface y, it has no recourse but to fail the
QueryInterface call with an E_NOINTERFACE error code. However,
from the perspective of the outer object’s client, this clearly violates COM’s
rules of interface navigation, which state that a client must be able to get to any
interface defined by an object from any other interface defined on that same
object. As a client of the outer object, you should be able to
QueryInterface on interface x and receive interface y, as well as
QueryInterface on interface y and receive interface x. Solving this
problem requires cooperation from both the inner and outer objects. When the
outer object creates the inner object using CoCreateInstance, the two
objects exchange IUnknown interfaces. The outer object supplies a pointer to
its IUnknown interface to the inner object as the second parameter to
CoCreateInstance, while the inner object returns a pointer to its
IUnknown interface to the outer object as the final parameter to
CoCreateInstance. If a client has a reference to an interface of the outer
object and performs a QueryInterface call for an interface supported by
the inner object, the outer object simply delegates the QueryInterface call
to the inner object using its reference to the inner object’s IUnknown
interface. Likewise, if a client has a reference to an interface of the inner
object, and performs a QueryInterface call for an interface supported by
the outer object, the inner object simply delegates the QueryInterface call
to the outer object using its reference to the outer object’s IUnknown
interface (see Figure 4-3).
Figure 4-3 By exchanging IUnknown interface pointers, the outer object can
delegate to the inner object for further processing requests for interfaces
supported by the inner object. Likewise, the inner object can delegate to the
outer object for further processing requests for interfaces supported by the
outer object.
To illustrate COM’s aggregation technique, we will build the AccountInfo
COM object. The AccountInfo object defines two interfaces —
IAccountInfo and IMemberInfo — for maintaining information
regarding individual credit card accounts. The properties of the
AccountInfo object are listed in Table 4-2.
Table 4-2 AccountInfo Object Interfaces and Properties
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Creation of the AccountInfo object begins with the same routine as always — allocating
GUIDs and defining the object using IDL. Even though the AccountInfo object itself doesn’t
directly implement IMemberInfo, it must still define it as a supported interface in the object
definition. But rather than redefine the IMemberInfo interface, we can just import its
definition from the MemberInfo.idl file using the import directive:
import "MemberInfo.idl";
Once the IMemberInfo interface definition has been imported, and the IAccountInfo
interface defined, the AccountInfo object can be defined (see Listing 4-4).
Listing 4-4. AccountInfo.idl
//
//AccountInfo.idl
//
import "unknwn.idl";
//Import declaration for IMemberInfo
import "MemberInfo/MemberInfo.idl";
//IID_IAcountInfo
//These are the attributes of the IAccountInfo interface
[
object,
uuid(4d463de2-a80b-11d0-94ab-00a024a85a21),
helpstring("IAccountInfo Interface.")
]
//Declaration of the IAccountInfo interface
interface IAccountInfo : IUnknown
{
//List of function definitions for each method
//supported by the interface
//
//[attributes] returntype [calling convention]
// funcname(params);
//
[propget, helpstring("Sets or returns the account number.")]
HRESULT Number([out, retval] long *lRetNumber);
[propput, helpstring("Sets or returns the account number.")]
HRESULT Number([in] long lNumber);
[propget, helpstring("Sets or returns the account balance.")]
HRESULT Balance([out, retval] float *flRetBalance);
[propput, helpstring("Sets or returns the account balance.")]
HRESULT Balance([in] float flBalance);
[propget, helpstring("Sets or returns the account limit.")]
HRESULT Limit([out, retval] long *lRetLimit);
[propput, helpstring("Sets or returns the account limit.")]
HRESULT Limit([in] long lLimit);
}
//LIBID_AccountInfo
//These are the attributes of the type library
[
uuid(4d463de0-a80b-11d0-94ab-00a024a85a21),
helpstring("AccountInfo Type Library."),
version(1.0)
]
//Definition of the AccountInfo type library
library AccountInfo
{
//CLSID_AccountInfo
//Attributes of the AccountInfo object
[
uuid(4d463de1-a80b-11d0-94ab-00a024a85a21),
helpstring("AccountInfo Object.")
]
//Definition of the AccountInfo object
coclass AccountInfo
{
//List all of the interfaces supported by the object
[default] interface IAccountInfo;
interface IMemberInfo;
}
}
Development of the AccountInfo object continues in typical fashion, by compiling the IDL
file using the MIDL compiler and defining the CAccountInfo C++ class. Notice that the
CAccountInfo class doesn’t inherit from IMemberInfo; nor does it define any of the
IMemberInfo interface functions. This is because the AccountInfo object will aggregate
the MemberInfo object in order to provide the implementation for the IMemberInfo
interface. AccountInfo aggregates MemberInfo by exchanging IUnknown interface
pointers with MemberInfo in a call to CoCreateInstance, which is called as part of
CAccountInfo::Initialize. AccountInfo offers its IUnknown pointer to
MemberInfo in the second parameter to CoCreateInstance, and at the same time requests
MemberInfo’s IUnknown pointer, which is to be returned in m_pMemberInfoIUnknown
— the fifth parameter to CoCreateInstance — if the aggregation process is successful:
HRESULT CAccountInfo::Initialize(void)
{
return CoCreateInstance(CLSID_MemberInfo, (IUnknown *)this,
CLSCTX_INPROC_SERVER, IID_IUnknown,
(LPVOID *)&m_pMemberInfoIUnknown);
}//Initialize
CAccountInfo::Initialize is called from within CAccountInfo::Create
Instance, the class factory function responsible for creating AccountInfo objects. Notice
that even though the AccountInfo object aggregates the MemberInfo object, the
AccountInfo object itself cannot be aggregated:
STDMETHODIMP CAccountInfoFactory::CreateInstance
(IUnknown* pUnknownOuter, REFIID iid,
LPVOID *ppv)
{
HRESULT hr;
CAccountInfo *pCAccountInfo = NULL;
*ppv = NULL;
//This object doesn't support aggregation
if (NULL != pUnknownOuter)
return CLASS_E_NOAGGREGATION;
//Create the CAccountInfo object
pCAccountInfo = new CAccountInfo();
if (NULL == pCAccountInfo)
return E_OUTOFMEMORY;
//Initialize the new object
hr = pCAccountInfo->Initialize();
if (FAILED(hr))
goto cleanUP;
//Retrieve the requested interface
hr = pCAccountInfo->QueryInterface(iid, ppv);
if (FAILED(hr))
goto cleanUP;
//Increment the global object counter
g_cObjects++;
return NOERROR;
cleanUP:
//Some type of error occurred, cleanup before
//returning
if (pCAccountInfo)
{
delete pCAccountInfo;
pCAccountInfo = NULL;
}
return hr;
}//CreateInstance
Ultimately, the call to CoCreateInstance results in the invocation of CreateInstance
on the MemberInfo object’s class factory. The MemberInfo object knows that it is being
created as part of an aggregate whenever it detects a nonNULL value in the second parameter to
CreateInstance. In an aggregate situation, the second parameter to CreateInstance
contains the IUnknown interface pointer of the outer object, which in this case is the
AccountInfo object. The MemberInfo object stores the outer object’s IUnknown interface
pointer, also called the controlling unknown, in the m_pControllingIUnknown member
variable, and uses it to delegate calls to the outer object’s IUnknown functions as necessary:
STDMETHODIMP_(ULONG)CMemberInfo::AddRef(void)
{
//Delegate to the controlling IUnknown
return m_pControllingIUnknown->AddRef();
}//AddRef
STDMETHODIMP_(ULONG)CMemberInfo::Release(void)
{
//Delegate to the controlling IUnknown
return m_pControllingIUnknown->Release();
}//Release
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The inner MemberInfo object delegates its IUnknown functions to the controlling unknown,
which in this case happens to be the outer AccountInfo object, in order to properly maintain
the reference count of the outer object. For example, if a client calls AddRef on the
IMemberInfo interface, the reference count of the outer AccountInfo object should be
incremented; likewise, if a client calls Release on the IMemberInfo interface, the reference
count of the outer object should be decremented. This explains why AddRef and Release are
delegated to the controlling unknown, but what about QueryInterface? The
QueryInterface function of the inner object is also delegated to the controlling unknown:
class INonDelegatingUnknown
{
virtual HRESULT STDMETHODCALLTYPE NonDelegatingQueryInterface
(REFIID iid, LPVOID *ppv) = 0;
virtual ULONG STDMETHODCALLTYPE
NonDelegatingAddRef(void) = 0;
virtual ULONG STDMETHODCALLTYPE
NonDelegatingRelease(void) = 0;
};//INonDelegatingUnknown
The CMemberInfo C++ class used to implement the MemberInfo COM object is defined such
that it inherits not only from IMemberInfo, but also from INonDelegatingUnknown:
STDMETHODIMP CMemberInfo::NonDelegatingQueryInterface
(REFIID iid, LPVOID *ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)(INonDelegatingUnknown *)this;
else if (IID_IMemberInfo == iid)
*ppv = (LPVOID)(IMemberInfo *)this;
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown *)*ppv)->AddRef();
return NOERROR;
}//NonDelegatingQueryInterface
STDMETHODIMP_(ULONG)CMemberInfo::NonDelegatingAddRef(void)
{
return ++m_cRef;
}//NonDelegatingAddRef
STDMETHODIMP_(ULONG)CMemberInfo::NonDelegatingRelease(void)
{
m_cRef—;
if (0 == m_cRef)
{
delete this;
//Decrement the global object count
g_cObjects—;
//See if it's alright to unload the server
if (::ServerCanUnloadNow())
::UnloadServer();
return 0;
}
return m_cRef;
}//NonDelegatingRelease
Previous Table of Contents Next
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The MemberInfo object’s class factory is then modified to use the nondelegating unknown to
obtain the initial IUnknown interface pointer in response to the aggregating object’s call to
CoCreateInstance:
STDMETHODIMP CMemberInfoFactory::CreateInstance(IUnknown*
pUnknownOuter, REFIID iid, LPVOID *ppv)
{
HRESULT hr;
CMemberInfo *pCMemberInfo = NULL;
*ppv = NULL;
//This object supports aggregation
//Create the CMemberInfo object
pCMemberInfo = new CMemberInfo(pUnknownOuter);
if (NULL == pCMemberInfo)
return E_OUTOFMEMORY;
//Initialize the new object
hr = pCMemberInfo->Initialize();
if (FAILED(hr))
goto cleanUP;
//Retrieve the requested interface
hr = pCMemberInfo->NonDelegatingQueryInterface(iid, ppv);
if (FAILED(hr))
goto cleanUP;
//Increment the global object counter
g_cObjects++;
return NOERROR;
cleanUP:
//Some type of error occurred, cleanup before returning
if (pCMemberInfo)
{
delete pCMemberInfo;
pCMemberInfo = NULL;
}
return hr;
}//CreateInstance
When the last outstanding AccountInfo interface pointer is released and the object destroys
itself, it releases the last outstanding call on the aggregated object, forcing it to destroy itself as
well:
CAccountInfo::~CAccountInfo()
{
if (m_pMemberInfoIUnknown)
m_pMemberInfoIUnknown->Release();
}//~CAccountInfo
In order for the MemberInfo object to continue to work in a nonaggregated fashion, all we have to
do is use the INonDelegatingUnknown as the controlling unknown. This way, any calls to the
delegating unknown will simply be forwarded to the object’s own INonDelegatingUnknown
interface, which contains the traditional IUnknown implementation. The decision of whether or
not the MemberInfo object is actually being created as part of an aggregate is made in the
CMemberInfo object’s constructor:
CMemberInfo::CMemberInfo(IUnknown* pUnknownOuter)
{
m_cRef = 0;
m_pIUserInfo = NULL;
m_lpszAddress = NULL;
m_lpszCity = NULL;
m_lpszPhone = NULL;
m_lpszState = NULL;
m_lpszZip = NULL;
Summary
In this chapter, you learned that:
• Containment is the easiest way to reuse an existing COM object, with the outer object
simply acting as a client of the inner object.
• Containment is best suited for reusing interfaces that implement some, but not all, of the
desired level of functionality.
• Aggregation requires cooperation from both the outer object and the inner object, and
therefore requires slightly more work.
• Aggregation is best suited for reusing interfaces exactly as is.
• In order to aggregate a component, both the outer and inner component must be located in
the same process space.
• Both methods of component reuse are transparent to the client of the outer object, in the
sense that the client has no idea that the outer object contains or aggregates other inner
objects.
In the next chapter you learn about the benefits of Automation and how to implement COM objects
that support dual interfaces.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Chapter 5
Building Automation Objects
IN THIS CHAPTER
• The benefits and limitations of VTBL interfaces
• The benefits and limitations of Automation
• How to build a COM object that supports both VTBL binding and
Automation
UNTIL THIS POINT, all the interfaces we have defined have been custom
interfaces, which means that we defined them ourselves; they were not defined
by COM, OLE, or ActiveX. Clients use the VTBL definitions of COM
interfaces to perform type checking at compile time in a process known as
VTBL binding. VTBL binding is extremely fast because all type checking is
done at compile time. However, scripting languages like VBScript (VBS) and
Java Script (JScript) are interpreted, not compiled, and their ever-increasing
popularity cannot be ignored. To accommodate the run-time type checking that
is required by these and other scripting and macro languages, Microsoft
defined Automation, the subject of this chapter.
An Introduction to Automation
Automation is a technology, built on top of COM, that is designed to provide a
standard way of exposing COM objects to macro languages, programming
tools, and other COM clients. It was originally created as a way for
applications developed in Visual Basic to control other applications, such as
Microsoft Excel. COM objects that are programmatically controllable via
Automation are called Automation objects. COM clients that are capable of
controlling such Automation objects are called Automation controllers.
Automation controllers communicate with Automation objects via the
Automation-defined IDispatch interface.
Understanding IDispatch
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Understanding Dual Interfaces
A dual interface is an interface whose methods can be called either directly through the VTBL or
indirectly through IDispatch. While implementing dual interfaces does require additional
work, it’s not as complicated as it may seem. There are several requirements for implementing a
dual interface:
• The interface being defined must inherit from IDispatch.
• The interface must be defined using the dual IDL keyword.
• The interface must be defined using Automation-compatible datatypes.
Figure 5-2 shows the memory layout for a VTBL interface that inherits from IUnknown, while
Figure 5-3 shows the memory layout for a dual interface that inherits from IDispatch. Notice
that even though the dual interface inherits from IDispatch, the first three member functions
are those of IUnknownQueryinterface, AddRef, and Release. This is because, like all
COM interfaces, IDispatch itself inherits from IUnknown. Automation controllers use the
IDispatch side of the dual interface, while C/C++ programmers can use the VTBL side of the
dual interface.
Figure 5-2 The memory layout for a VTBL interface that inherits from IUnknown.
Figure 5-3 The memory layout for a dual interface that inherits from IDispatch.
When defining a dual interface, you must use the dual IDL keyword to signal that the interface is
in fact a dual interface:
[
object,
uuid(01234567-89ab-cdef-0123-456789abcdef),
helpstring("ISomeDualInterface interface."),
dual
]
//declaration of the ISomeDualInterface interface
interface ISomeDualInterface : IDispatch
{
...
}
In Chapter 2, we saw the wide range of IDL-supported datatypes useful for defining COM
interfaces (see Table 2-2). However, in order for an interface to be compatible with Automation,
its member functions can only be defined using the Automation-compatible datatypes listed in
Table 5-2.
Table 5-2 Intrinsic IDL Datatypes Supported by Automation
Datatype Description
As you can see, the list of datatypes supported by Automation is a subset of the list of types
supported by IDL. To understand why this is so, you must first understand the variant datatype.
Understanding Variants
As Automation was originally defined as part of Visual Basic, it stands to reason that variants
would also be somehow related to VB. Variants are the default datatypes of VB, and they serve as
a way to store different types of data in a common manner. As Listing 5-1 illustrates, a variant is
essentially a structure that consists of a union of each supported datatype and an indicator variable
used to identify the datatype currently being stored by the variant.
Listing 5-1. Internal structure of a variant
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
When retrieving information from a variant, you first need to determine what type of data
is being stored in it. This is done by referring to the variant’s vt member. Table 5-3 lists
and describes the meanings of the various vt values. The vt value signals which member
of the variant structure actually contains data. For example, if a variant has a vt value of 2,
then the variant is storing a 2-byte integer value in its iVal member.
The same holds true for storing data in a variant. The first thing that you’ll want to do is
identify the type of data that you’re storing in the variant by assigning the appropriate
value to the variant’s vt member. For example, to store a long integer value in a variant,
set the variant’s vt member to 3, then store the actual long data in the variant’s Val
member. The following code snippet demonstrates how a variant can be used to store
various types of data:
VARIANT ageVariant;
VARIANT nameVariant
Because variants can be used to store various datatypes in a single common format,
Automation uses variants as a way to pass different types of arguments to dispinterface
functions. However, as variants only support a limited number of datatypes, and
Automation relies on variants, Automation is limited to supporting only those datatypes
supported by variants. Table 5-4 lists Win32 API functions that are useful for working with
variants.
Table 5-4 Win32 API Functions Useful for Working with Variants
Function Purpose
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Understanding BSTRs
Function Purpose
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Understanding SAFEARRAYs
The other datatype that you may not have recognized is the SAFEARRAY. A
SAFEARRAY is exactly what its name implies, an array that incorporates its
own safety mechanisms to prevent writing beyond the bounds of the array. A
SAFEARRAY accomplishes this by maintaining information regarding the
following:
• The number of dimensions, stored in the cDims member variable
• The upper and lower bounds of each dimension of the array, stored in
the rgsabound array member variable
• The size of each element in the array, stored in the cbElements
member variable
Following is the definition of the SAFEARRAY datatype:
Function Purpose
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The IDL definition of the IAccountInfoDispatch interface can be seen in Listing 5-2. As
you look at the definition, notice the use of the dual IDL keyword in the Attributes section
of the interface definition, signaling that the interface is in fact a dual interface that must be
Automation-compatible.
Listing 5-2. IDL definition of the IAccountInfoDispatch interface
//IAccountInfoDispatch
//These are the attributes of the IAccountInfoDispatch interface
[
object,
uuid(dd3b2be4-a91e-11d0-94ab-00a024a85a21),
helpstring("IAccountInfoDispatch Interface."),
dual
]
//Declaration of the IAccountInfoDispatch interface
interface IAccountInfoDispatch : IDispatch
{
//List of function definitions for each method supported by
//the interface
//
//[attributes] returntype [calling convention]
// funcname(params);
//
[id(0), propget, helpstring("Sets or returns the account
number.")]
HRESULT Number([out, retval] long *lRetNumber);
[id(0), propput, helpstring("Sets or returns the account
number.")]
HRESULT Number([in] long lNumber);
[propget, helpstring("Sets or returns the account balance.")]
HRESULT Balance([out, retval] float *flRetBalance);
[propput, helpstring("Sets or returns the account balance.")]
HRESULT Balance([in] float flBalance);
[propget, helpstring("Sets or returns the account limit.")]
HRESULT Limit([out, retval] long *lRetLimit);
[propput, helpstring("Sets or returns the account limit.")]
HRESULT Limit([in] long lLimit);
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The IAccountInfoDispatch dual interface allows Automation controllers to manipulate the
AccountInfoAuto object. The custom IAccountInfo and IMemberInfo interfaces allow
C++ programmers to manipulate the AccountInfoAuto object without the burden of being
Automation controllers. While it is very beneficial to support both C++ and
Automation-compatible clients, it can be a very time-consuming task to implement special
interfaces for each. To reduce the amount of additional code necessary to support both C++ and
Automation-compatible clients, the IAccountInfoDispatch dual interface will delegate its
implementation to either the IAccountInfo or IMemberInfo custom interfaces, after some
basic preprocessing (see Listing 5-3). As you look at the listing, notice the use of the
SysAllocString and SysStringLen Win32 API functions to manipulate BSTR data.
Listing 5-3. The properties of the IAccountInfoDispatch dual interface are implemented by
delegating to either the IAccountInfo or IMemberInfo custom interfaces, after some basic
preprocessing.
//
//get_Address
//
STDMETHODIMP CAccountInfoAuto::get_Address(BSTR *bstrRetAddress)
{
HRESULT hr;
LPSTR szAddress = NULL;
LPOLESTR olestrAddress = NULL;
long lStringLen;
//
//put_Address
//
STDMETHODIMP CAccountInfoAuto::put_Address(BSTR bstrAddress)
{
HRESULT hr;
LPSTR szAddress = NULL;
long lStringLen;
//
//put_Address
//
STDMETHODIMP CAccountInfoAuto::put_Address(LPSTR lpszAddress)
{
long lStringLen;
In order for an Automation controller to perform run-time type checking for an Automation object,
the object must provide a type library. The type library is just one of the files created by the MIDL
compiler whenever you compile your IDL file. The library IDL keyword signals to the MIDL
compiler that you want to have a type library generated:
//LIBID_AccountInfoAuto
//These are the attributes of the type library
[
uuid(dd3b2be0-a91e-11d0-94ab-00a024a85a21),
helpstring("AccountInfoAuto Type Library."),
version(1.0)
]
//Definition of the AccountInfoAuto type library
library AccountInfoAuto
{
importlib("stdole32.tlb");
//CLSID AccountInfoAuto
//Attributes of the AccountInfoAuto object
[
uuid(dd3b2be1-a91e-11d0-94ab-00a024a85a21),
helpstring("AccountInfoAuto object")
]
//Definition of the AccountInfoAuto object
coclass AccountInfoAuto
{
//List all of the interfaces supported by the object
[default] interface IAccountInfoDispatch;
interface IAccountInfo;
interface IMemberInfo;
};
}
You may have noticed that the library definition also contains the definition of the
AccountInfoAuto object. Unlike interface definitions, object definitions cannot exist outside
of a library definition. Once the MIDL compiler has compiled the IDL file and generated a
type library, you need to bind it to your compiled executable (DLL or EXE). To bind the type
library to your executable, you need to include the type library as a resource to your application’s
project. Because you can bind multiple type libraries to a single executable (possibly to support
multiple languages), you need to number each type library as a way of uniquely identifying each
one. The following line of code is from the AccountInfoAuto.rc resource file and is used to bind
the AccountInfoAuto type library with the AccountInfoAuto.dll:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Implementing IDispatch
Our introduction to Automation already described the basics of the IDispatch interface.
Now you will see how to actually implement this most important interface. We will begin
with IDispatch::GetTypeInfoCount. Automation controllers interested in obtaining
a pointer to a type library will call IDispatch::GetTypeInfoCount to determine if the
Automation object exposes type information for the IDispatch interface. The Automation
controller uses the type information to perform any required run-time syntax checking. If the
object does in fact expose type information, IDispatch::GetTypeInfoCount will
respond with a value of 1; otherwise, it responds with 0:
*pctinfo = 1;
return NOERROR;
}//GetTypeInfoCount
Once the Automation controller knows that the object exposes type information, all it has to
do is call IDispatch::GetTypeInfo to obtain a pointer to the type information for the
IDispatch interface. Before the object can hand out such a pointer, it must first load the
type information for the IDispatch pointer into memory. This is done as part of the
initialization process for the CAccountInfoAuto object:
HRESULT CAccountInfoAuto::Initialize()
{
HRESULT hr;
return hr;
}//Initialize
The LoadTypeInfo function (see Listing 5-4) takes a pointer that will point to the actual
type information of interest — a GUID identifying the type library itself and a GUID
identifying the object or interface whose type information you are interested in retrieving.
LoadTypeInfo does three things:
• It loads the type library into memory if it is not already loaded.
• It registers the type library if it is not already registered.
• It retrieves the type information for an object or interface identified by GUID.
Upon entry, LoadTypeInfo attempts to load an already registered type library identified by
the incoming rguid parameter. If the call is successful, the type library was in fact already
loaded and registered. If the call is unsuccessful, the type library is registered in a call to
LoadTypeLib. LoadTypeLib will only register the type library if the filename passed to
it is not a fully qualified filename. To ensure that the type library is registered, a call to
RegisterTypeLib is made. Once the type library is loaded and a pointer to it is obtained,
a call to GetTypeInfoOfGuid is made to obtain the type information about the object or
interface identified by the incoming CLSID parameter. Finally, the pointer to the type library
is returned in the incoming pptinfo parameter.
Listing 5-4. The LoadTypeInfo function
*pptinfo = NULL;
// Load the type library.
hr = LoadRegTypeLib(rguid, 1, 0, lcid, &ptlib);
if (FAILED(hr))
{
//Library wasn't registered, try to load it from the
//server itself
GetModuleFileName(g_hModule, szModuleName,
sizeof(szModuleName) / sizeof(_TCHAR));
#ifdef _UNICODE
//UNICODE
_tcscpy(wszModuleName, szModuleName);
#else
//SBCS and MBCS
//Convert from the multibyte character set to the wide
//character set
mbstowcs(wszModuleName, szModuleName,
sizeof(szModuleName) / sizeof(_TCHAR));
#endif
//If LoadTypeLib is successful, it will register
//the type library automatically but only if
//the entire path of the library is NOT specified
hr = LoadTypeLib(wszModuleName, &ptlib) ;
if(FAILED(hr))
return hr;
return NOERROR;
}//LoadTypeInfo
Now that the object has a pointer to the type information for the IDispatch interface, it is
capable of responding to Automation controller requests for IDispatch’s type information;
these requests arrive via calls to IDispatch::GetTypeInfo.
*ppinfo = NULL;
//ref count
m_pTypeInfo->AddRef();
//return the type information
*ppinfo = m_pTypeInfo;
return NOERROR;
}//GetTypeInfo
Incoming calls to IDispatch::GetIDsOfNames and IDispatch::Invoke can be
delegated to member functions of the m_pTypeInfo IDispatch type information
interface pointer:
//
//GetIDsOfNames
//
STDMETHODIMP CAccountInfoAuto::GetIDsOfNames(REFIID riid,
OLECHAR **rgszNames, UINT cNames, LCID lcid,
DISPID *rgdispid)
{
if (IID_NULL != riid)
return DISP_E_UNKNOWNINTERFACE;
//
//Invoke
//
STDMETHODIMP CAccountInfoAuto::Invoke(DISPID dispidMember,
REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS *pdispparams, VARIANT *pvarResult,
EXCEPINFO *pexcepinfo, UINT *puArgErr)
{
if (IID_NULL != riid)
return DISP_E_UNKNOWNINTERFACE;
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Now that IDispatch has been implemented, we need to expose it via QueryInterface.
Because IAccountInfoDispatch inherits from IDispatch, we can simply cast
IAccountInfoDispatch into an IDispatch pointer:
Aside from simply registering the CLSID, Automation servers are required to add the following
registry entries:
• A programmatic ID (ProgID)
• Type library information
• Automation interface proxy/stub information
Automation controllers use ProgIDs as a way to refer to Automation objects with
human-readable names. ProgIDs are of two different types: version-dependent and
version-independent. Unlike CLSIDs, which are generated by a COM API call, you as a
developer are ultimately responsible for creating your own ProgIDs, which typically use the
following syntax:
AppName.ObjectName.VersionNumber
where:
AppName is the name of the binary executable
ObjectName is the name of the COM object
VersionNumber is the version of the COM object
The following lines of VB code demonstrate how an Automation controller would use a
version-dependent ProgID to create a version 6 Word document and a version-independent
ProgID to create a Word document in the latest version of Microsoft Word:
HKEY_CLASSES_ROOT
CLSID
{12345678-ABCD-1234-5678-9ABCDEFOOOOO} = Description
ProgID = AppName.ObjectName.VersionNumber
VersionIndependentProgID = AppName.ObjectName
InprocServer32 = C:\SomeServer.dll
LocalServer32 = C:\SomeServer.exe
ProgID entries must also be placed directly under the HKEY_CLASSES_ROOT key:
HKEY_CLASSES_ROOT
AppName.ObjectName = Description
CLSID = {12345678-ABCD-1234-5678-9ABCDEFOOOOO}
AppName.ObjectName.VersionNumber = Description
CLSID = {12345678-ABCD-1234-5678-9ABCDEFOOOOO}
Automation servers are also required to register their type libraries. This can be done either
explicitly or through calls to LoadTypeLib or RegisterTypeLib, as we have done in
LoadTypeInfo (see Listing 5-4). Regardless of which technique is used, the Automation
server is responsible for adding the following registry entries under the HKEY_CLASSES_ROOT
key:
HKEY_CLASSES_ROOT
TypeLib
{12345678-ABCD-1234-5678-9ABCDEFOOOOO} = Description
major.minor = Description
lcid
platform = C:\SomeServer.dll
HELPDIR = C:\
FLAGS = 0
Here are descriptions of the key identifiers in the above code snippet:
major.minor The version number of the type library.
lcid A one- to four-hex-digit string representation of the locale ID (LCID) cannot
include leading zeros or 0x. A value of 0 represents the
LANG_SYSTEM_DEFAULT(0).
Platform The target operating system platform: Win 16, Win32, or Mac.
Lastly, the Automation server is responsible for registering a proxy/stub pair for each supported
interface. Luckily, the Automation library supplies a default Automation proxy/stub pair that can
be used by any Automation-compatible interface. The CLSID for the default Automation
proxy/stub pair is {00020424-0000-0000-C000-000000000046}.
HKEY_CLASSES_ROOT
Interface
{12345678-ABCD-1234-5678-9ABCDEFOOOOO} = Description
TypeLib = {12345678-ABCD-1234-5678-9ABCDEFOOOOO}
ProxyStubClSID = {00020424-0000-0000-C000-000000000046}
That’s it! While building an Automation object requires a little more work than building a vanilla
COM object, it is not necessarily harder. Also, by implementing dual interfaces such that they
delegate to custom interfaces, you can create COM objects that appeal to a larger developer
audience. In the next chapter, we will build two client applications that make use of the
AccountInfoAuto Automation object. One uses the VTBL side of the dual interface; the
other is an Automation controller that uses the IDispatch side of the dual interface. The full
source code for the AccountInfoAuto Automation object can be found on the companion
CD-ROM.
Summary
In this chapter you learned that:
• Despite its inherent speed, VTBL binding does not allow for run-time type checking.
• While Automation objects support run-time type checking, they require more work to
implement than VTBL binding, and they are somewhat slower because of their additional
overhead.
• Supporting dual interfaces allows clients to bind using either the VTBL side or the
IDispatch side of an interface.
• In order to support IDispatch, interfaces are restricted to using only the datatypes
allowed by variants.
In Chapter 6, you learn how to build client applications that use the VTBL and IDispatch sides of
the dual interface for this Automation object.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Chapter 6
Building Automation Controllers
IN THIS CHAPTER
• How to access the properties and methods of an Automation object from the VTBL side
of a dual interface
• How to access the properties and methods of an Automation object from the
IDispatch side of a dual interface
• How to use variants to pass arguments to Automation-compatible interface functions
• About DISPIDs, which can be obtained at both compile time and run time
Number long
Balance float
Limit long
Name BSTR
Sex unsigned char
Address BSTR
City BSTR
State BSTR
Zip BSTR
Phone BSTR
After the COM library has been successfully initialized, the AccountInfoAutoVTBL
application calls CoCreateInstance to create a new AccountInfoAuto object and
request an initial pointer to its IUnknown interface. Notice the use of the
CLSCTX_INPROC_SERVER class execution context constant to specify that we are only
interested in an in-process server for the AccountInfoAuto object, which is identified by the
CLSID_AccountInfoAuto constant:
//pointer to Iunknown
hr = CoCreateInstance(CLSID_AccountInfoAuto, NULL,
CLSCTX_INPROC_SERVER, IID_IUnknown,
(LPVOID *)&pIUnknown);
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("The AccountInfoAuto object
has been created."));
.
.
.
}
else
DisplayMessage(_TEXT("The AccountInfoAuto object
couldn't be created."));
While we are only interested in the CLSCTX_INPROC_SERVER execution context, COM
defines several other execution contexts, which can be found in Table 2-3.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Manipulating the COM Object
Once the AccountInfoAuto object has been successfully instantiated, and we have
obtained an initial IUnknown interface, we can use QueryInterface to navigate to the
IAccountInfoDispatch interface. Ultimately, we will use the
IAccountInfoDispatch interface to set each property defined by the
IAccountInfoDispatch interface. (A complete list of the properties defined by the
IAccountInfoDispatch interface can be found in Table 6-1.)
DisplayMessage(_TEXT("Changed to the
IAccountInfoDispatch interface."));
bstrAddress = SysAllocString(OLESTR("1 One Way"));
bstrCity = SysAllocString(OLESTR("Essex"));
bstrName = SysAllocString(OLESTR("John E. Doe"));
bstrPhone = SysAllocString(OLESTR
("(555) 555-0122"));
bstrState = SysAllocString(OLESTR("IL"));
bstrZip = SysAllocString(OLESTR("60606"));
//Set each property
.
.
.
Next, AccountInfoAutoVTBL sets each property defined by the
IAccountInfoDispatch interface, and frees the resources allocated to the various BSTR
variables by calling SysFreeString:
SysFreeString(bstrAddress);
SysFreeString(bstrCity);
SysFreeString(bstrName); SysFreeString(bstrPhone);
SysFreeString(bstrState); SysFreeString(bstrZip);
DisplayAccountInfoDispatch(pIAccountInfoDispatch,
_TEXT("Each property has been retreived."));
After each property has been set and SysFreeString is called to free the resources
allocated to the various BSTR variables, DisplayAccountInfoDispatch is called to
retrieve, format, and display the values of the various properties defined by the
IAccountInfoDispatch interface. DisplayAccountInfoDispatch takes two
parameters: pIAccountInfoDispatch — an IAccountInfoDispatch pointer; and
lpszRetreivedMsg — a string pointer to a message to be displayed via
DisplayMessage. DisplayAccountInfoDispatch begins by retrieving the various
properties of the incoming pIAccountInfoDispatch parameter, and displaying the
incoming message pointed to by the lpszRetrievedMsg parameter. Next,
DisplayAccountInfoDispatch builds a single string consisting of the name of each
property, followed by the value of that particular property. A return character (\ r) separates
each property, name/property value pair, and all string values are converted to the appropriate
character format, as part of the property, formatting process. Once all of the properties have
been retrieved and formatted, the entire resulting string is displayed using
DisplayMessage. The source code for the DisplayAccountInfoDispatch function
can be seen in Listing 6-1.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Listing 6-1. The DisplayAccountInfoDispatch function
void DisplayAccountInfoDispatch(IAccountInfoDispatch
*pIAccountInfoDispatch, LPTSTR lpszRetrievedMsg)
{
long lAccountNumber;
float flAccountBalance;
long lAccountLimit;
BSTR bstrAddress = NULL;
BSTR bstrCity = NULL;
BSTR bstrName = NULL;
BSTR bstrPhone = NULL;
unsigned char bySex;
BSTR bstrState = NULL;
BSTR bstrZip = NULL;
_TCHAR szAccountNumber[255];
_TCHAR szAccountBalance[255];
_TCHAR szAccountLimit[255];
_TCHAR szDisplayText[255];
_TCHAR szConvertedText[255];
_TCHAR charSex;
long lStringLen;
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrZip, -1,
szConvertedText, sizeof(szConvertedText) /
sizeof(_TCHAR), NULL, NULL);
#endif
_tcscat(szDisplayText, szConvertedText);
}
//Add a carriage return
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');
//Format the Phone Number
_tcscat(szDisplayText, _TEXT("Phone: "));
if (bstrPhone)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrPhone);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrPhone, -1,
szConvertedText, sizeof(szConvertedText) /
sizeof(_TCHAR), NULL, NULL);
#endif
_tcscat(szDisplayText, szConvertedText);
}
lStringLen = _tcsclen(szDisplayText);
//Null terminate the string
szDisplayText[lStringLen] = _T('\0');
DisplayMessage(szDisplayText);
}//DisplayAccountInfoDispatch
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Releasing the COM Object
Once all the outstanding interface references have been Released, all that’s left to do is
uninitialize the COM library by calling CoUninitialize:
//
//AccountInfoAutoVTBL.cpp
//
#include <windows.h>
#include <objbase.h>
#include <tchar.h>
#include <stdio.h> //for sprintf
#include "AccountInfoAuto_i.h"
//
//Forward declarations
//
void DisplayMessage(LPTSTR lpMessage);
void DisplayAccountInfoDispatch(IAccountInfoDispatch
*pIAccountInfoDispatch, LPTSTR lpszRetrievedMsg);
//
//Global variables
//
const _TCHAR g_lpszApplicationTitle[] =
_TEXT("AccountInfoAutoVTBL");
//
//DisplayMessage
//
void DisplayMessage(LPTSTR lpszMessage)
{
MessageBox(NULL, lpszMessage, g_lpszApplicationTitle,
MB-OK | MB_ICONEXCLAMATION);
}//DisplayMessage
//
//DisplayAccountInfoDispatch
//
void DisplayAccountInfoDispatch(IAccountInfoDispatch
*pIAccountInfoDispatch, LPTSTR lpszRetrievedMsg)
{
long lAccountNumber;
float flAccountBalance;
long lAccountLimit;
BSTR bstrAddress = NULL;
BSTR bstrCity = NULL;
BSTR bstrName = NULL;
BSTR bstrPhone = NULL; unsigned char bySex;
BSTR bstrState = NULL;
BSTR bstrZip = NULL;
_TCHAR szAccountNumber[255];
_TCHAR szAccountBalance[255];
_TCHAR szAccountLimit[255];
_TCHAR szDisplayText[255];
_TCHAR szConvertedText[255];
_TCHAR charSex;
long lStringLen;
//Retrieve each property
pIAccountInfoDispatch->get_Number(&lAccountNumber);
pIAccountInfoDispatch->get_Balance(&flAccountBalance);
pIAccountInfoDispatch->get_Limit(&lAccountLimit);
pIAccountInfoDispatch->get_Address(&bstrAddress);
pIAccountInfoDispatch->get_City(&bstrCity);
pIAccountInfoDispatch->get_Name(&bstrName);
pIAccountInfoDispatch->get_Phone(&bstrPhone);
pIAccountInfoDispatch->get_Sex(&bySex);
pIAccountInfoDispatch->get_State(&bstrState);
pIAccountInfoDispatch->get_Zip(&bstrZip);
DisplayMessage(lpszRetrievedMsg);
szDisplayText[lStringLen + 1] = _T('\0');
DisplayMessage(szDisplayText);
}//DisplayAccountInfoDispatch
//
//WinMain
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR IpCmdLine, int nCmdShow)
{
HRESULT hr;
BSTR bstrAddress = NULL;
BSTR bstrCity = NULL;
BSTR bstrName = NULL;
BSTR bstrPhone = NULL;
BSTR bstrState = NULL;
BSTR bstrZip = NULL;
IUnknown *pIUnknown = NULL;
IAccountInfoDispatch *pIAccountInfoDispatch NULL;
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("The AccountInfoAuto object has
been created."));
//Begin using the object
SysFreeString(bstrAddress);
SysFreeString(bstrCity);
SysFreeString(bstrName);
SysFreeString(bstrPhone);
SysFreeString(bstrState);
SysFreeString(bstrZip);
DisplayAccountInfoDispatch(pIAccountInfoDispatch,
_TEXT("Each property has been retreived."));
else
DisplayMessage(_TEXT("The AccountInfoAuto object
couldn't be created."));
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Building the AccountInfoAutoDisp Client
In the first part of this chapter, you learned how to access the VTBL-side of a dual interface by
building the AccountInfoAutoVTBL application. The AccountInfoAutoVTBL application
was a simple application that created the AccountInfoAuto object developed in Chapter 5, and
used the VTBL side of the object's AccountInfoDispatch dual interface to set and retrieve the
various properties defined by the object before formatting and displaying their respective values.
(Table 6-1 lists the various properties defined by the AccountInfoAuto object.) In this section
you learn how to access the IDispatch side of a dual interface by building the
AccountInfoAutoDisp application. Like the AccountInfoAutoVTBL application, the
AccountInfoAutoDisp application is a simple application that creates the
AccountInfoAuto object developed in Chapter 5, and uses the object’s IDispatch interface to
set and retrieve the various properties defined by the object before formatting and displaying their
respective values. To better enable you to compare and contrast the differences between VTBL and
IDispatch access of a dual interface, we use the source code from the AccountInfoAutoVTBL
application as our boilerplate, and modify it as necessary to create the AccountInfoAutoDisp
application.
//
//Property names
//
LPOLESTR rgszNames[] = {OLESTR("Number"), OLESTR("Balance"),
OLESTR("Limit"), OLESTR("Address"), OLESTR("City"),
OLESTR("Name"), OLESTR("Phone"), OLESTR("Sex"),
OLESTR("State"), OLESTR("Zip")};
The name of each individual property in the array is then accessed using a for loop, and used by
IDispatch::GetIDsOfNames to retrieve the DISPID for each property, which is then stored in
a global array of DISPIDs. As you look at the following code snippet, notice the use of the
LOCALE_SYSTEM_DEFAULT to signify that the default system locale should be used to interpret
the various property names:
Figure 6-1 Arguments are supplied to the rgvarg array in the reverse order of their positions.
To pass arguments by name, include the DISPID of each named argument in the
rgdispidNamedArgs array element. DISPIDs for property or method parameters can be retrieved
by supplying the names of each parameter after the property or method name in a call to
IDispatch::GetIDsOfNames. However, don’t forget to change
IDispatch::GetIDs0fNames’s third parameter to reflect the number of DISPIDs that you are
expecting to receive in the return DISPIDs array. Update cNamedArgs to reflect the number of
named arguments being passed in rgdispidNamedArgs. While the ordering of named arguments
should be irrelevant, named arguments are typically supplied in reverse order, just like positional
arguments. Automation defines the DISPID_PROPERTYPUT DISPID, which is required when
setting properties.
Previous Table of Contents Next
[an error occurred while processing this directive]
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
As all of the AccountInfoAuto properties require only one parameter — the new value
to assign to the property — the DISPPARAMS structure is initialized once and then used
repeatedly in subsequent calls to IDispatch::Invoke, changing only the VARIANT
arguments supplied in the rgvarg element of the DISPPARAMS structure:
DISPPARAMS dispparams;
DISPID dispidsNamedArgs[1];
VARIANTARG varg[1];
VariantInit(&varg[0]);
dispidsNamedArgs[0] = DISPID_PROPERTYPUT;
.
.
.
//Initialize the dispatch parameters
dispparams.rgvarg = &varg[0];
dispparams.rgdispidNamedArgs = &dispidsNamedArgs[0];
dispparams.cArgs = 1;
dispparams.cNamedArgs = 1;
.
.
.
Notice that although each argument is being sent by position, cNamedArgs is one. This is
to reflect the one DISPID-PROPERTYPUT DISPID that is being supplied as the first
element in the rgdispidNamedArgs array, signaling that we are attempting to set a
property. The only thing left to do before calling IDispatch::Invoke to actually
update each property’s value is to provide the new value for each property. The value of
each property must be supplied as a VARIANT in the rgvarg array. As you look at the
following code snippet, notice how the VARIANT’s vt value is used to reflect the type of
data being supplied to the VARIANT (see Table 5-3 for a list of VARIANT vt values):
//Number
dispparams.rgvarg[0].vt = VT_I4;
dispparams.rgvarg[0].lVal = 333;
pIDispatch->Invoke(dispids[0], IID_NULL,
LOCALE-SYSTEM-DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
For those properties that expect BSTRs, we must use the SysAllocString function to
initialize the various BSTR variables that will contain the actual data. These BSTR variables
are then assigned to the rgvarg VARIANT array like all the other property values. The
resources allocated to these BSTR variables are later freed using SysFreeString:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Retrieving Property Values Using IDispatch
.
.
.
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
VARIANT vRetVal;
//
//AccountInfoAutoDisp.cpp
//
#include <windows.h>
#include <objbase.h>
#include <oleauto.h>
#include <tchar.h>
#include <stdio.h> //for sprintf
#include "AccountInfoAuto_i.h"
//
//Forward declarations
//
void DisplayMessage(LPTSTR lpMessage);
void DisplayAccountInfoDispatch(IDispatch *pIDispatch,
LPTSTR lpszRetrievedMsg);
//
//Global variables
//
const_TCHAR g_lpszApplicationTitle[] =
_TEXT("AccountInfoAutoDisp");
//
//Property names
//
LPOLESTR rgszNames[] = {OLESTR("Number"), OLESTR("Balance"),
OLESTR("Limit"), OLESTR("Address"),
OLESTR("City"), OLESTR("Name"),
OLESTR("Phone"), OLESTR("Sex"),
OLESTR("State"), OLESTR("Zip")};
//
//Dispatch IDs for the properties
//
DISPID dispids[10];
//
//DisplayMessage
void DisplayMessage(LPTSTR lpszMessage)
{
MessageBox(NULL, lpszMessage, g_lpszApplicationTitle,
MB_OK | MB_ICONEXCLAMATION);
}//DisplayMessage
//
//DisplayAccountInfoDispatch
//
void DisplayAccountInfoDispatch(Idispatch *pIDispatch,
LPTSTR lpszRetrievedMsg)
{
long lAccountNumber;
float flAccountBalance;
long lAccountLimit;
BSTR bstrAddress = NULL;
BSTR bstrCity = NULL;
BSTR bstrName = NULL;
BSTR bstrPhone = NULL;
unsigned char bySex;
BSTR bstrState = NULL;
BSTR bstrZip = NULL;
_TCHAR szAccountNumber[255];
_TCHAR szAccountBalance[255];
_TCHAR szAccountLimit[255];
_TCHAR szDisplayText[255];
_TCHAR szConvertedText[255];
_TCHAR charSex;
long lStringLen;
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
VARIANT vRetVal;
DisplayMessage(lpszRetrievedMsg);
DisplayMessage(szDisplayText);
}//DisplayAccountInfoDispatch
//
//WinMain
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HRESULT hr;
BSTR bstrAddress = NULL;
BSTR bstrCity = NULL;
BSTR bstrName = NULL;
BSTR bstrPhone = NULL;
BSTR bstrState = NULL;
BSTR bstrZip = NULL;
IUnknown *pIUnknown = NULL;
IDispatch *pIDispatch = NULL;
int index;
DISPPARAMS dispparams;
DISPID dispidsNamedArgs[1];
VARIANTARG varg[1];
VariantInit(&varg[0]);
dispidsNamedArgs[0] = DISPID_PROPERTYPUT;
//Initialize the COM Library
hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("The COM Library has been
initialized."));
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("The AccountInfoAuto object has
been created."));
//Begin using the object
DisplayAccountInfoDispatch(pIDispatch,
_TEXT("Each property has been retreived."));
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
While working with the IDispatch side of a dual interface is not terribly
complex, it does require more effort on the part of the client. This extra work
stems from the process of obtaining the DISPIDs of the various properties and
methods supported by an Automation object and using them to actually
execute a particular function. Clients can obtain an Automation object’s
supported DISPIDs at compile time (early binding), or at run time (late
binding); both processes are discussed in Chapter 5. Late binding gives an
Automation controller the ability to query the services of an Automation object
at run time. After obtaining the DISPID for a particular property or method, an
Automation controller can execute the function by simply calling the object’s
IDispatch::Invoke function with the function’s DISPID.
Summary
In this chapter you learned:
• That using the VTBL side of a dual interface is a lot like using a
custom COM interface, except that dual interfaces are restricted to
Automation-compatible datatypes.
• That while the IDispatch side of a dual interface allows for
run-time binding, it requires more work (from the Automation
controller’s perspective).
• That obtaining an Automation object’s DISPIDs from its type library
at compile time can significantly increase the performance of executing
a function through IDispatch::Invoke.
• That while Automation objects are restricted to using only the
datatypes supported by VARIANTs, the Automation library provides
intrinsic translation from one VARIANT-supported datatype to another.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Part II
Building Componentized Applications
Chapter 7
Building Object Hierarchies
IN THIS CHAPTER
• How to define and build an object hierarchy, a collection of highly
interoperable COM objects designed to solve a specific task
• The benefits of building an object hierarchy
• How to build COM objects that encapsulate data access to an ODBC
data source
IN THE FIRST part of this book, you learned the fundamentals of COM and
even built several different COM servers. In this part, you will learn how to
develop component-based applications by using COM objects to create two
three-tiered versions of a simple order-entry system. One version of the system
follows the traditional client/server architecture; the other follows a
Web-based architecture.
While the two applications follow different architectural designs, both are
representative of what might be used by a typical mail-order company, where
customers order products over the telephone and pay for their purchases using
a special credit card account. To be effective, the order-entry application must
allow the mail-order company to maintain information for each product being
sold, each customer’s account, and each purchase invoice describing which
customer bought which product(s). All of the information regarding customer
accounts, product inventory, and customer purchase invoices will be stored in
a central data source. Both versions of the application will use the Open
DataBase Connectivity (ODBC) API to provide a universal way to access
different data sources. Although the two applications have different
architectures, they’re both required to perform many of the exact same tasks in
order to be successful. For example, both versions of the order-entry
application must have the ability to add new customer accounts to the
underlying data source. These areas of common functionality are prime targets
for componentization.
In this chapter, we build the OrderEntry object hierarchy, a collection of
highly interoperable COM objects designed to provide basic order-entry
capabilities. By using the OrderEntry object hierarchy as the basis for both
versions of the order-entry application, you gain firsthand experience with the
interoperability and component reuse aspects of COM. By building the
hierarchy itself in C++; the client/server version of the order-entry application
in Microsoft Visual Basic; and the Web-based version of the order-entry
application using HTML, Microsoft Active Server Pages (ASP), and Visual
Basic Scripting Edition (VBScript); you will gain firsthand experience with
the language-independent aspects of COM. Finally, in the last chapter, you
gain firsthand experience with the location-independent aspects of COM, by
using DCOM to access various objects of the hierarchy remotely.
The following sections briefly describe the various entry objects of the
hierarchy. After each object description is a layout of the underlying database
table used to store the object’s information.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The Account Object
Property Datatype
Number long
Balance float
Limit long
Name BSTR
Sex BSTR
Address BSTR
City BSTR
State BSTR
Zip BSTR
Phone BSTR
InService VARIANT_BOOL
Number long
Balance single
Limit long
Name text(40)
Sex byte
Address text(80)
City text(20)
State text(2)
Zip text(5)
Phone text(13)
InService Boolean
The Product object is used to provide the information shown in Table 7-3
for each individual product in the system.
Table 7-3 Properties of the Product Object
Property Datatype
Number long
Description BSTR
Price float
Stock long
InService VARIANT_BOOL
Number long
Description text(40)
Price single
Stock long
InService Boolean
The Invoice object is used to provide the information shown in Table 7-5
for each individual purchase invoice.
Table 7-5 Properties of the Invoice Object
Property Datatype
Number long
EntryDate Date
CustomerAccount long
LineItems ILineItems*
Number long
EntryDate Date/Time
AccountNumber long
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The LineItem Object
The LineItem object is used to provide the information shown in Table 7-7
for each individual item purchased as part of an invoice.
Table 7-7 Properties of the LineItem Object
Property Datatype
Number long
InvoiceNumber long
ProductNumber long
UnitsPurchased long
Number long
InvoiceNumber long
ProductNumber long
UnitsPurchased long
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The Products Object
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The LineItems Object
The basic design of the “entry” objects is simple: Each entry object maintains
a member variable for each property, which is then manipulated using various
Get/Put member functions. The Get functions are used to retrieve the value
of the member variable, while the Put functions are used to change the value
of the member variable, as shown in the code below for the Account object.
See Tables 7-1, 7-3, 7-5, and 7-7 for lists of the properties supported by the
various entry objects.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
AN ODBC PROGRAMMING OVERVIEW
ODBC provides applications developers with a mechanism for offering database support
to their applications in a general, DataBase Management System (DBMS)-independent
manner. Instead of accessing a particular DBMS using its native protocol, applications
developers write their database manipulation routines using the ODBC API, which allows
them to use any number of ODBC-compliant DBMSS. Architecturally, ODBC is
comprised of four components (see Figure 7-2):
• The Application is responsible for calling ODBC API functions to submit SQL
statements, retrieve the results of those statements, and react accordingly.
• The Driver Manager is responsible for loading the appropriate database driver
on behalf of the application.
• The Driver is a vendor-specific DLL used to process ODBC function calls,
submit SQL requests to the data source, and return data to the application.
• The Data Source is a combination of a particular DBMS product running on a
particular operating system that is accessible using a particular network.
return TRUE;
}//Dll Main
Now that you know how to initialize and clean up the ODBC environment for each client
of the OrderEntry object hierarchy, let’s resume our discussion of how to build the
Accounts collection object.
STDMETHODIMP CAccountsFactory::CreateInstance(IUnknown*
pUnknownOuter, REFIID iid, LPVOID *ppv)
{
HRESULT hr;
CAccounts *pCAccounts = NULL;
*ppv = NULL;
//This object doesn't support aggregation
if (NULL != pUnknownOuter)
return CLASS_E_NOAGGREGATION;
//Create the CAccounts object
pCAccounts = new CAccounts();
if (NULL == pCAccounts)
return E_OUTOFMEMORY;
//Initialize the new object
hr = pCAccounts->Initialize();
if (FAILED(hr))
goto cleanUP;.
.
.
.
Previous Table of Contents Next
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The initialization process for the Accounts object is responsible for performing four tasks:
• Loading the type library information for the IAccounts interface using the
LoadTypeInfo function that we created in Chapter 5.
• Allocating a connection handle using SQLAllocConnect and the environment handle
that was previously allocated as part of DllMain, the DLL entry point, and establishing a
connection to the OrderEntry data source using SQLConnect. Each connection handle
maintains information regarding a specific connection to the underlying data source. The
OrderEntry data source used by the OrderEntry object hierarchy should be configured as a
System DSN, using the ODBC Data Source Administrator, which can be found in the
Windows Control Panel. By configuring a data source as a System DSN, it will be
available to all users of the machine, including Windows NT services. If you configure
your data source such that it requires an authorized user account and password, this
additional information can be supplied along with the data source name in a later call to
SQLConnect.
• Allocating statement handles using SQLAllocStmt for each of the five prepared
statements that will be used by the Accounts object. Each statement handle maintains
information regarding a specific SQL statement and associates it with a specific
connection.
• Navigating the current record pointer to the first record of the data source.
Once all of the necessary connection and statement handles have been allocated, the initialization
process continues by creating five prepared SQL statements, one each for:
• Inserting a new account
• Updating an existing account
• Removing an account from service
• Retrieving information for a particular account
• Retrieving the total number of accounts in the data source
Now that we know what the initialization process is responsible for, let’s look at how to
accomplish these objectives. We’ll begin by looking at the definitions of the various SQL
statements that will eventually be used to create prepared statements. As you look at the SQL
statement definitions, notice how the question mark (?) is used as a placeholder for values that
will be supplied later when the statement is actually executed.
SQLTCHAR szSQLInsert[] = _TEXT("INSERT INTO Accounts
(Balance, Limit, Name, Sex, Address, City, State,
Zip, Phone, InService) VALUES (?, ?, ?, ?, ?, ?, ?,
?,?,?)");
Defining the SQL statement only represents half of the work that must be done to create a
prepared statement. The next step is to allocate an ODBC statement handle and initialize it using
the predefined SQL statement:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The SQLBindParameter function accepts the arguments given in Table 7-13.
Table 7-13 Arguments Accepted by SQLBindParameter
Once a C variable has been bound for each parameter, the prepared insert statement can be
executed using SQLExecute:
SDWORD cbBalance = 0;
SDWORD cbLimit = 0;
SDWORD cbName = SQL_NTS;
SDWORD cbSex = SQL_NTS;
SDWORD cbAddress = SQL_NTS;
SDWORD cbCity = SQL_NTS;
SDWORD cbState = SQL_NTS;
SDWORD cbZip = SQL_NTS;
SDWORD cbPhone = SQL_NTS;
SDWORD cbInService = 0;
return NOERROR;
}//Add
Previous Table of Contents Next
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Prepared SQL statements and bound parameters are also used to implement the Update and
Remove methods as well as the Count property. The Update method exists to enable users to
make changes to existing account, product, and invoice entries:
lNumRecs = g_objAccounts.Count
Implementation of the Count property is pretty straightforward and can be seen along with
implementation of the Remove method in Listing 7-2.
Listing 7-2. The Accounts object’s Remove method and Count property
//
//Remove
//
STDMETHODIMP CAccounts::Remove(long lNumber)
{
RETCODE retcode;
SDWORD cbNumber = 0;
return NOERROR;
}//Remove
//
//get_Count
//
STDMETHODIMP CAccounts::get_Count(long *lRetCount)
{
RETCODE retcode;
SDWORD cbCount = 0;
return NOERROR;
}//get_Count
The only other data manipulation property that requires in-depth discussion is the Accounts
object’s Item property. As you may recall, the Accounts object’s Item property can be used in
two different ways. In the first scenario, calling Item with the number of a specific account will
cause Item to return an Account object complete with that account’s information. The following
code shows how a client written in Visual Basic might use the Item property of the Accounts
object in the previously described manner:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Once each parameter has been bound, we can retrieve the column values by calling SQLFetch:
long lNumber;
float flBalance;
long lLimit;
wchar_t wszName[NAME_LEN];
char szName[NAME_LEN];
wchar_t wszSex[SEX_LEN];
char szSex[SEX_LEN];
wchar_t wszAddress[ADDRESS_LEN];
char szAddress[ADDRESS_LEN];
wchar_t wszCity[CITY_LEN];
char szCity[CITY_LEN];
wchar_t wszState[STATE_LEN];
char szState[STATE_LEN];
wchar_t wszZip[ZIP_LEN];
char szZip[ZIP_LEN];
wchar_t wszPhone[PHONE_LEN];
char szPhone[PHONE_LEN];
VARIANT_BOOL vbInService;
SDWORD cbNumber = 0;
SDWORD cbBalance = 0;
SDWORD cbLimit = 0;
SDWORD cbName = SQL_NTS;
SDWORD cbSex = SQL_NTS;
SDWORD cbAddress = SQL_NTS;
SDWORD cbCity = SQL_NTS;
SDWORD cbState = SQL_NTS;
SDWORD cbZip = SQL_NTS;
SDWORD cbPhone = SQL_NTS;
SDWORD cbInService = 0;
//Number
SQLBindParameter(m_hstmtQuery, 1, SQL_PARAM_INPUT,
SQL_C_SLONG, SQL_INTEGER, 0, 0, &lNumber, 0,
&cbNumber);
//Execute the prepared statement
retcode = SQLExecute(m_hstmtQuery);
//Release the bound parameter buffers
SQLFreeStmt(m_hstmtQuery, SQL_RESET_PARAMS);
if (!SQL_SUCCEEDED(retcode))
return E_UNEXPECTED;
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
NAVIGATION PROPERTIES AND METHODS
As I described earlier, the Item property works in conjunction with the five navigation
methods: MoveFirst, MoveLast, MovePrev, MoveNext and Move to reposition the
current record pointer and retrieve customer account information. When an Accounts object
is first initialized, the MoveFirst method is called to position the current record pointer to
the first customer account in the record set. Our exploration of the MoveFirst method
begins in CAccounts::Initialize with the definition of the navigational
prepared statement, which is used by all of the five navigation methods. The
navigational statement generates a record set containing every record from the accounts
database table sorted by their descriptions:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
MoveFirst is the most involved navigation method, because it is responsible for not only
initializing the transfer buffers for each column, but also for repositioning the current record
pointer and retrieving the appropriate data. However, the other four navigation methods use
SQLExtendedFetch to reposition the current record pointer and update the contents of the
bound member variables, in the same way as the MoveFirst method (see Listing 7-4).
Listing 7-4. The BOF and EOF properties and the five navigation methods of the Accounts
object: MoveFirst, MoveLast, MovePrev, MoveNext, and Move
//
//get_BOF
//
STDMETHODIMP CAccounts::get_BOF(short *sRetBOF)
{
*sRetBOF = (m_bBOF) ? VARIANT_TRUE : VARIANT_FALSE;
return NOERROR;
}//get_BOF
//
//get_EOF
//
STDMETHODIMP CAccounts::get_EOF(short *sRetEOF)
{
*sRetEOF = (m_bEOF) ? VARIANT_TRUE : VARIANT_FALSE;
return NOERROR;
}//get_EOF
//
//MoveFirst
//
STDMETHODIMP CAccounts::MoveFirst(void)
{
HRESULT hr = NOERROR;
RETCODE retcode;
UDWORD cRowsFetched;
UWORD rgfRowStatus[1];
SQLTCHAR szSQLNavigationalAccess[] = _TEXT("SELECT * FROM
Accounts ORDER BY Number");
//
//MoveLast
//
STDMETHODIMP CAccounts::MoveLast(void)
{
HRESULT hr = NOERROR;
RETCODE retcode;
UDWORD cRowsFetched;
UWORD rgfRowStatus[1];
//
//MovePrev
//
STDMETHODIMP CAccounts::MovePrev(void)
{
HRESULT hr = NOERROR;
RETCODE retcode;
UDWORD cRowsFetched;
UWORD rgfRowStatus[1];
//
//MoveNext
//
STDMETHODIMP CAccounts::MoveNext(void)
{
HRESULT hr = NOERROR;
RETCODE retcode;
UDWORD cRowsFetched;
UWORD rgfRowStatus[1];
//Get the last record
retcode = SQLExtendedFetch(m_hstmtNavigationalAccess,
SQL_FETCH_NEXT, 1, &cRowsFetched, rgfRowStatus);
if (SQL_SUCCEEDED (retcode))
{
//Operation completed successfully,
//there must be at least one record
m_bBOF = FALSE;
m_bEOF = FALSE;
}
else
{
//Operation failed, no current record
m_bEOF = TRUE;
}
return hr;
}//MoveNext
//
//Move
//
STDMETHODIMP CAccounts::Move(long lRecs)
{
HRESULT hr = NOERROR;
RETCODE retcode;
UDWORD cRowsFetched;
UWORD rgfRowStatus[1];
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Summary
In this chapter, you learned:
• That one of the best ways to ensure interoperability and promote
object reuse between various COM objects is to develop an object
hierarchy.
• That object hierarchies should encapsulate the functionality necessary
for one or more applications to perform their fundamental tasks.
• That by encapsulating functionality common to multiple applications
into a single object hierarchy, corporations can spread a good portion of
their development and maintenance costs across the various applications
that are built on top of the hierarchy.
• How to build COM objects that encapsulate data access to an ODBC
data source.
Now that we have built the OrderEntry object hierarchy, the rest of this book is
dedicated to showing you how to build applications with different architectural
styles. So that you’ll be better able to compare and contrast these different
architectures, we will continue to use the order entry theme. In the next
chapter, we will use the OrderEntry object hierarchy to build an order-entry
application that follows a typical client/server design.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Chapter 8
Building the Client/Server Order-Entry
Application
IN THIS CHAPTER
• The order-entry application’s design requirements
• The detailed architecture of the client/server order-entry application
• How to use the OrderEntry object hierarchy developed in Chapter 7 to
develop a client/server version of an order entry application
• The benefits and limitations of the client/server application
architecture
IN THE LAST chapter you learned about object hierarchies. You learned that
while COM objects are inherently interoperable, one of the best ways to
promote object reuse is to develop a group of interoperable COM objects
designed to solve a specific purpose, as part of a single object hierarchy. You
were then able to design and build the OrderEntry object hierarchy, which is
made up of entry objects and collection objects. Entry objects are used to
convey relatively static information about an individual entry in the data
source, while collection objects are used to manage and represent multiple data
source entries of a particular type. In the case of the OrderEntry object
hierarchy, the Account, Product, Invoice, and LineItem objects are all entry
objects, while the Account, Product, Invoice, and LineItem objects are all
collection objects (see Figure 8-1). The collection objects use ODBC to
interact with an arbitrary backend data source, which in this case happens to be
the OrderEntry.mdb Access database. In this chapter, you use the OrderEntry
object hierarchy as the foundation for a simple order-entry application. As you
develop the order-entry application, you will find that the object hierarchy
encapsulates most of the basic functionality required for the order-entry
application to perform its duties. By encapsulating its basic functionality into a
single object hierarchy, we are free to implement the actual order-entry
application using any number of application architectures. In this chapter, we
create a client/server version of the order-entry application, and in Chapter 9
we create a Web version of the order-entry application. However, both
versions of the application use the exact same object hierarchy.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Developing the Client/Server Application
Development of the client/server version of the order-entry system begins by creating the
global references to the Accounts, Products, and Invoices objects that are used to
interact with the underlying data source. They are created as part of the MDIForm_Load
event:
Before a customer can purchase a product using the order-entry application, he or she must
have an active account. Telephone operators use a window similar to the one in Figure 8-3 to
add new customer accounts. Notice how the purchasing privileges of an account can be
suspended by simply unchecking the “In Service” check box.
Figure 8-3 The New Account window is used to add new customer accounts.
Adding customer account entries to the data source is a very straightforward process. The
New Account window is displayed with blank entry fields, allowing the user to enter
information regarding the new customer account into each field. Once the new account
information has been entered, the user presses the OK button to submit the new information to
the data source. Behind the scenes, when the user presses the OK button, a blank Account
object is created and initialized using the data supplied in the various fields of the New
Account window. Once the information has been transferred from the various fields to the
newly created Account object, the object is added to the data source using the global
Accounts object reference obtained when the application was first started. The source code
for this process can be seen in Listing 8-1, the OK button’s Click event. As you look at the
listing, notice that information for an account number is not supplied. This is because the data
source automatically generates an account number for each new entry to prevent the
possibility of an overlap in user-supplied account numbers.
Listing 8-1. The Click event of the AccountInfoForm’s OK button
The order-entry application has two management functions — Modify Account and
Deactivate Account — that require the user to retrieve a specific customer’s account
information as its first step. The order-entry application provides two different ways to
retrieve a specific customer’s account information, the easiest of which is to simply locate the
account using the account number (see Figure 8-4).
Figure 8-4 The easiest way to retrieve a customer’s account information is to simply search
for the account using the account number.
The LocateAccount function of the SearchForm exists to locate and retrieve a specific
customer account using only the customer’s account number. The LocateAccount
function displays the SearchForm and waits for the user to press either the OK button or
the Cancel button. The LocateAccount function returns TRUE if the user presses the OK
button; otherwise, LocateAccount returns FALSE. Clicking on the OK button causes the
SearchForm to disappear, and the account number that was entered into the txtNumber
entry field is retrieved and used as a parameter in the retrieval of the global Accounts
object’s Item property. The Item property is responsible for actually locating and retrieving
the specified customer’s account information, which is ultimately returned in the
LocateAccount function’s retAccount parameter. However, if the user presses the
Cancel button, or if the desired customer’s account cannot be located, LocateAccount
returns a reference to Nothing in the retAccount parameter:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
The FillListboxWithAccounts subroutine of the ListForm is responsible for displaying
the name of every customer in a listbox. FillListboxWithAccounts uses the MoveFirst
navigation method of the global Accounts object to reposition the current record pointer to the
first customer account in the data source. After clearing the current contents of the listbox,
FillListboxWithAccounts enters a Do…While loop and uses the Item property of the
global Accounts object to retrieve each account entry from the data source until there are no
more, an event signaled by the global Accounts object’s EOF property. As each customer’s
name is added to the contents of the listbox, the customer’s account number is added to the
ItemData array of the listbox. By storing each account number in the ItemData array, detailed
information regarding the currently selected listbox entry can be obtained immediately by simply
retrieving the account number of the selected listbox entry and supplying it to the Item property
of the global Accounts object with code similar to the following:
lNumber = lstListing.ItemData(lstListing.ListIndex)
Set localAccount = g_AccountsCol.item(lNumber)
After each entry is added to the listbox, the current record pointer of the global Accounts object
is repositioned to the next customer account in the data source using the object’s MoveNext
method. Following is the source code for the FillListboxWithAccounts subroutine:
Sub FillListboxWithAccounts()
Dim localObject As Object
g_AccountsCol.MoveFirst
lstListing.Clear
Do While Not g_AccountsCol.EOF
Set localObject = g_AccountsCol.item
lstListing.AddItem localObject.Name
lstListing.ItemData(lstListing.NewIndex) =
localObject.Number
Set localObject = Nothing
g_AccountsCol.MoveNext
Loop
End Sub
Updating Existing Accounts
Telephone operators use the Modify Account window to update information regarding existing
customer accounts, in much the same way that they use the New Account window to add new
customer accounts. Note that instead of displaying the New Account window with blank entry
fields, the entry fields of the window are filled with information about the customer account being
modified (see Figure 8-6).
Figure 8-6 The Modify Account window is used to update information regarding existing
customer accounts.
The updating process begins by retrieving the desired customer’s account information (see
“Retrieving Existing Entries”). Once the account information has been retrieved in the form of an
Account object, it is sent to the AccountInfoForm’s ModifyAccount subroutine, which
formats and displays the account information in the appropriate fields of the Modify Account
window.
Removing a customer’s account from service is probably the easiest management function to
implement. The first step is to locate the desired customer’s account using one of the two methods
described in “Retrieving Existing Entries.” Once the desired account has been located, removing it
from service is simply a matter of calling the Remove method of the global Accounts object
with the desired account number:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Adding and Updating Invoices
Ultimately, the purpose of the order-entry application is to allow telephone operators to complete
online purchase invoices as customers purchase products using their respective accounts.
Telephone operators use the New Invoice window to add new invoices to the data source, and
the Modify Invoice window (see Figure 8-7) to update existing invoices.
Figure 8-8 Each individual line item of an invoice consists of a product and the quantity being
purchased.
Adding invoices to the data source begins with the familiar pattern of displaying a window with
blank entry fields and allowing the user to enter information. However, processing and storing
the data that’s provided in the various entry fields is slightly different for Invoice objects than
for Account or Product objects, due to the Invoice object’s internal LineItems object.
The Invoice object’s internal LineItems object is responsible for maintaining the list of
products to be purchased, along with their respective quantities. Adding an invoice actually
requires two steps. In the first step, information about the invoice itself, such as its date of
creation and the purchasing customer’s account number, is saved to the data source. Once the
invoice’s information has been saved using the global Invoices object, the second step is to
add the individual line items of the invoice. However, to prevent adding redundant line item
entries to the data source, the order-entry application simply deletes all of the invoice’s existing
line items from the data source before adding the current line items. While the order entry
application takes a simplistic approach to preventing redundant line item entries in the data
source, other more sophisticated techniques can be used to minimize the total number of entries
that must be deleted and then re-added. The source code responsible for adding and updating
individual invoices can be seen in Listing 8-2, the Click event for the InvoiceInfoForm’s OK
button.
Listing 8-2. The Click event of the InvoiceInfoForm’s OK button
If f_ADDING Then
Set f_localInvoice = CreateObject("OrderEntry.Invoice")
f_localInvoice.EntryDate = CDate(txtDate.Text)
f_localInvoice.CustomerAccount = CLng(txtCustomerID.Text)
'add to the data source
g_InvoicesCol.Add f_localInvoice
'retrieve the last invoice which is
'the one we just added
Set f_localInvoice = Nothing
g_InvoicesCol.MoveLast
Set f_localInvoice = g_InvoicesCol.item
Else
f_localInvoice.CustomerAccount = CLng(txtCustomerID.Text)
'update the current invoice
g_InvoicesCol.Update f_localInvoice
End If
'set the invoice properties
Set invoiceLineItems = f_localInvoice.LineItems
'delete any existing line item entries
'for this invoice
invoiceLineItems.MoveFirst
Do While Not invoiceLineItems.EOF
Set lineItem = invoiceLineItems.item
invoiceLineItems.Remove lineItem.Number
Set lineItem = Nothing
invoiceLineItems.MoveNext
Loop
Set lineItem = CreateObject("OrderEntry.LineItem")
For item = 1 To lvLineItems.ListItems.Count
'set each line item
lineItem.InvoiceNumber = f_localInvoice.Number
lineItem.ProductNumber = CLng
(lvLineItems.ListItems.item(item).Text)
lineItem.UnitsPurchased = CLng
(lvLineItems.ListItems.item(item).SubItems(3))
invoiceLineItems.Add lineItem
Next item
Set lineItem = Nothing
Set invoiceLineItems = Nothing
Unload Me
End Sub
Summary
In this chapter, you learned that:
• Building an application on top of an object hierarchy helps to reduce the complexity of
the application development process.
• Client/server applications allow you to make effective use of both client-side and
server-side system resources.
• Client/server applications have several limitations in the areas of deployment,
configuration, and support, which ultimately increases their Total Cost of Ownership
(TCO).
In the next chapter, we build a Web version of the order-entry application that addresses some of
the architectural limitations of the client/server design.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Chapter 9
Building the Web Order-Entry
Application
IN THIS CHAPTER
• The architecture of the Web application
• How Active Server Pages (ASP) enhances the Web application
architecture
• How to reuse the OrderEntry object hierarchy developed in Chapter 7
to develop a Web version of the order-entry application developed in
Chapter 8
• The benefits and limitations of the Web application architecture
Figure 9-2 Active Server Pages (ASP) is used to dynamically generate HTML
documents in response to user requests.
In addition to providing scripting support, ASP also allows developers to use
COM components that support the IDispatch interface, which, as you
learned in Chapter 5, is the interface responsible for facilitating Automation.
ASP’s combined support for various scripting languages and COM
components makes it an ideal platform for building Web applications. Web
applications created using ASP are referred to as ASP applications. An ASP
application is essentially a collection of ASP files located under a common
directory. An ASP application is allowed to have one global.asa file, which is
where variables, functions, and subroutines with global scope are declared.
The global.asa file must be located in the application’s root directory.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
While HTTP is the protocol of the Web, it is based on what amounts to stateless connections,
which means that neither the client nor the server is responsible for maintaining information
about the state of their current connection. In other words, each connection between a client
and the server is treated as a brand-new connection. Programming in such a stateless
environment is extremely tedious and time-consuming. Thankfully, ASP maintains state
information for us, and presents us with what appears to be a stated environment. The first
time that a client browser accesses any individual page of an ASP application, ASP creates a
new server-side context called a Session for that particular client. Developers can use the
Session context to save information on a client-by-client basis. Information that has been
stored in a Session may only be accessed from that particular session context. Information
that needs to be accessible from within any Session must be stored in the Application context.
The Application context exists as long as there is at least one client using the ASP application.
A client’s Session is terminated when that client is no longer accessing one of the
application’s ASP files, or after a predetermined period of client nonactivity. The default
time-out period for a Session is initially set to 90 seconds, but can be easily reconfigured by
the ASP administrator. Whenever a new Session is created, ASP fires the
Session_OnStart event. Likewise, whenever a Session is terminated, ASP fires the
Session_OnEnd event. Before the very first client Session is created, ASP fires the
Application_OnStart event, and when the very last client Session is terminated, ASP
fires the Application_OnEnd event. Any code that you write in response to these events
must be contained in the global.asa file.
Web applications present an interesting alternative to traditional client/server applications, the
limitations of which are discussed at the end of Chapter 8. Since the pages that ultimately
make up the clients are stored on the server, the client is forced to go to the server for each
new page. By maintaining a single version of the application on the Web server, clients are
guaranteed to be running the latest version of the application. So as you can see, Web
applications provide an automatic solution to the application deployment problem. Lastly,
more sophisticated Web applications are capable of automatically downloading and installing
additional resources to the client machine as necessary. (We’ll see an example of this in the
next chapter.) Each of these advances ultimately helps to reduce the Total Cost of Ownership
(TCO) for Web applications.
Developing the Web Application
One of the primary design points for the Web version of the order-entry application is that it
should resemble the client/server version as closely as possible with regard to look and feel.
The more the Web version looks and feels like the client/server version, the smaller the
learning curve will be in terms of staff retraining. However, at the same time, you want the
Web version of the application to behave like other Web applications and pages so that you
can leverage any Web experience the user may already have.
The process of adding new customer accounts using the Web version of the application is
almost identical to that of the client/server version, with telephone operators entering new
account information into the various fields of the account information page and submitting
them to the system using the OK button (compare Figures 9-3a and 9-3b).
However, as none of the Web clients have an ODBC connection to the data source, they are
unable to add the new account information to the data source directly. Web clients interact
with the data source by requesting ASP files that encapsulate the interaction with the data
source. For example, the user interface (UI) used by Web clients to add new customer
accounts is actually an HTML form comprised of multiple text-entry fields and a single OK
button. The static NewAccount.htm page generates the UI that is used by Web clients to
receive new customer account information (see Figure 9-3a). Whereas an HTML page can
have multiple forms, each form can only be associated with a single action, which is the
Uniform Resource Locator (URL) of the script that will be used to process the form’s data.
The action associated with the form used by the NewAccount.htm page is the URL of the
SaveAccountLogic.asp file, which happens to be an ASP file. When the user clicks on the
OK button, the browser requests the SaveAccountLogic.asp file from the Web server, which
responds by invoking the ASP engine, which then loads the SaveAccountLogic.asp file and
processes it. This two-phase data-access technique is used whenever the Web client needs to
interact with the data source. As its name suggests, SaveAccountLogic.asp contains the code
logic ultimately responsible for saving customer account information to the data source. The
SaveAccountLogic.asp page begins with a piece of code that you will see quite often
throughout the Web order-entry application. The following code snippet attempts to obtain a
reference to a Session-level Accounts object, which has been stored in order to minimize
the unnecessary overhead of creating and destroying the Accounts object and its ODBC
connection. If for some reason the Session level reference cannot be obtained, the Accounts
object is recreated and then stored in the current Session.
<%
Dim globalReferenceObtained
globalReferenceObtained = FALSE
On Error Resume Next
'Try to obtain the Accounts object that may have been saved
'as part of this session
globalReferenceObtained = Not (Session("g_AccountsCol")
Is Nothing)
'If there is no Accounts object stored in this session
'then create one
If Not globalReferenceObtained Then
Set Session("g_AccountsCol") =
CreateObject("OrderEntry.Accounts")
End If
%>
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Next, the page determines whether it has been called to add a new customer account or to
update an existing customer’s account information. To determine its purpose, the page
retrieves the incoming AccountNumber parameter and attempts to retrieve the account
identified by it. If the attempt is unsuccessful, we assume that the caller is performing an add;
otherwise, we assume that the caller is performing an update. If the caller is performing an
add, we create a blank Account object, call SetProperties to initialize its properties
using the data from the form variables, and use the Session-level Accounts object reference
to add the new account to the data source. If the caller is performing an update, we call
SetProperties to modify the existing account, and use the Session-level Accounts
object reference to propagate the changes to the underlying data source. The source code for
this process can be seen in Listing 9-1.
Listing 9-1. Code from the SaveAccountLogic.asp ASP page that is used to add new account
information to the data source or update existing account information in the data source
<%
Sub SetProperties(account)
account.Balance = Request("txtBalance")
account.Limit = Request("txtLimit")
'
account.Name = Request("txtName")
account.Sex = Request("txtSex")
account.Address = Request("txtAddress")
account.Address = "Home"
account.City = Request("txtCity")
account.State = Request("txtState")
account.Zip = Request("txtZip")
account.Phone = Request("txtPhone")
account.InService = ("ON" =
UCASE(Request("chkInService")))
End Sub
Dim localAccount
Dim lNumber
Before an existing customer account can be updated or removed, it must first be identified. As
you saw, the client/server version of the order-entry application has functions that are
responsible for not only identifying accounts, but also for locating and retrieving them. Once
the account is located and retrieved, the client/server version of the order-entry application
can pass the retrieved account information to another function for either updating or
removing. However, the nature of Web application architectures, specifically their inability to
maintain references to COM objects located on different machines over the HTTP protocol,
prevents the Web version of the order-entry application from functioning in a similar manner.
When it comes to identifying existing customer accounts, the Web version of the order-entry
application is capable of displaying UIs similar to those used by the client/server version for
identifying, locating, and retrieving customer account information (see Figures 9-4a and 9-4b
and 9-5a and 9-5b.)
Figure 9-4a The Web version of the order-entry application provides a UI for identifying an
account using the account number; compare it to Figure 9-4b.
Figure 9-5a The Web version of the order-entry application provides a UI for selecting an
account from a list using the account name; compare it to Figure 9-5b.
Session("g_AccountsCol").MoveFirst
Do While Not Session("g_AccountsCol").EOF
Set localObject = Session("g_AccountsCol").item
%>
<option value = <% = localObject.Number%><% =
localObject.Name%></option>
<% Set localObject = Nothing
Session("g_AccountsCol").MoveNext
Loop
%>
</select>
To select an account from the list, the user highlights the desired account in the listbox and
clicks on the Details button. When the user clicks on the Details button, the account number
of the selected account is sent to the ModifyAccount.asp file, which then locates, retrieves,
and displays the account identified by the incoming AccountNumber parameter for
updating.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Updating Existing Accounts
Before an account can be updated, it must be located and retrieved first. As you saw, whereas the
client/server version of the order-entry application provides two different mechanisms for locating
and retrieving existing customer accounts, the Web version of the order-entry application provides
two different mechanisms for identifying customer accounts. The Web order-entry application
doesn’t provide a single mechanism for actually locating and retrieving existing customer
accounts. This is a subtle but important difference between the implementations of the two
versions. For example, the SearchForm.LocateAccount function of the client/server order-entry
application actually returns an Account object in one of its parameters. The returned Account
object can then be updated or removed from service. While the Web order-entry application
provides an HTML form-based UI similar to that produced by calling the
SearchForm.LocateAccount function of the client/server order-entry application, the form
only returns an account number. The ASP file that is the action of the form is ultimately
responsible for locating and retrieving the customer account information based on an account
number parameter. Once an account is identified using one of the two account identification
techniques described in “Identifying Existing Accounts,” the Web order-entry application uses
two additional ASP files to update an existing account: ModifyAccount.asp, which is used to
locate, retrieve, and display the desired customer’s account information (see Figure 9-6) and
SaveAccountLogic.asp, which actually saves customer account information to the data source.
(We examined the SaveAccountLogic.asp file in “Adding New Accounts.”)
Figure 9-6 The ModifyAccount.asp file is responsible for locating, retrieving, and displaying an
existing customer’s account information.
ModifyAccount.asp begins with the process that was used in the SaveAccountLogic.asp file to
obtain a reference to a Session-level Accounts object, after which ModifyAccount.asp uses an
incoming AccountNumber parameter along with the Session-level Accounts object to locate
and retrieve the desired customer account information. If the account cannot be located, the
ModifyAccount.asp will generate an HTML page similar to that in Figure 9-7.
Figure 9-7 If ModifyAccount.asp cannot locate the specified customer account, it will notify the
user with an HTML page.
If the account is located, its information is retrieved and used to fill the individual entry fields of
the HTML form-based UI (see Listing 9-2).
Listing 9-2. Code from the ModifyAccount.asp file that is used to locate and retrieve customer
account information before displaying it in an HTML form-based UI for the user to edit
<%
Dim localAccount
Dim lNumber
<form action="SaveAccountLogic.asp">
<input type="hidden" name="AccountNumber" value="<% =
lNumber%>">
<table border="0" width="100%">
<tr>
<td valign="top"><p align="left">Account #:
<strong><% = localAccount.Number%></strong></p>
</td>
<td valign="top"><p align="right"><input
type="submit" name="cmdOK" value="OK"></p>
</td>
</tr>
</table>
<div align="left"><table border="0" width="100%">
<tr>
<td valign="top">
<%
If localAccount.InService = True Then
%>
<input type="checkbox" name="chkInService"
Checked>In Service
<%
Else
%>
<input type="checkbox" name="chkInService">In Service
<%
End If
%>
</td>
</tr>
</table>
</div><p align="left"><strong>Credit Information</strong></p>
<div align="left"><table border="0">
<tr>
<td align="right">Balance:</td>
<td><input type="text" size="10" name="txtBalance"
value="<% = localAccount.Balance%>"></td>
</tr>
<tr>
<td align="right">Limit:</td>
<td><input type="text" size="10" name="txtLimit"
value="<% = localAccount.Limit%>"></td>
</tr>
</table>
</div><p align="left"><strong>Billing Information</strong></p>
<div align="left"><table border="0">
<tr>
<td align="right">Name: </td>
<td><input type="text" size="40" name="txtName"
value="<% = localAccount.Name%>"></td>
</tr>
<tr>
<td align="right">Sex:</td>
<td><input type="text" size="1" name="txtSex"
value="<% = localAccount.Sex%>"></td>
</tr>
<tr>
<td align="right">Address :</td>
<td><p align="left"><textarea name="txtAddress"
rows="2" cols="40"><% =
localAccount.Address%></textarea></p>
</td>
</tr>
<tr>
<td align="right">City:</td>
<td><input type="text" size="20" name="txtCity"
value="<% = localAccount.City%>"></td>
</tr>
<tr>
<td align="right">State:</td>
<td><input type="text" size="2" name="txtState"
value="<% = localAccount.State%>"></td>
</tr>
<tr>
<td align="right">Zip:</td>
<td><input type="text" size="5" name="txtZip"
value="<% = localAccount.Zip%>"></td>
</tr>
<tr>
<td align="right">Phone:</td>
<td><input type="text" size="13" name="txtPhone"
value="<% = localAccount.Phone%>"></td>
</tr>
</table>
</div>
</form>
<%
End If
'Release the object's reference
Set localAccount = Nothing
%>
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Removing Existing Accounts
Before an account can be removed from service, it must first be identified, using either of the two
supported account identification methods previously described. However, once an existing
customer account has been identified, the DeactivateAccountLogic.asp file is used to actually
remove the account from service. The DeactivateAccountLogic.asp file begins with the same
logic as the SaveAccountLogic.asp and ModifyAccount.asp files, which exist to obtain a
reference to a Session-level Accounts object. The Accounts object is then used to locate and
retrieve the existing customer account identified by the incoming AccountNumber parameter. If
the account cannot be located, the DeactivateAccountLogic.asp will generate an HTML page
similar to that in Figure 9-6. If the account is located, its information is retrieved and used to
remove the account from service (see Listing 9-3).
Listing 9-3. Code from the DeactivateAccountLogic.asp file that is used to locate, retrieve, and
remove an existing customer account from service
<%
Dim localAccount
Dim lNumber
Summary
In this chapter, you learned that:
• The infrastructure required to run a Web application is very well-defined and consists of a
Web server, HTTP, HTML, and a Web browser.
• By encapsulating an application’s basic functionality inside an object hierarchy, you have
the freedom and flexibility to develop an application using any of today’s popular
application architectures.
• Web applications can be used to solve many of the basic deployment, configuration, and
support problems associated with client/server applications.
• Web applications introduce their own set of problems, including the need to learn a
slightly different programming model that often only allows access to server-side system
resources.
In the final chapter, we create a slightly different version of the order-entry application based on
the Web application architecture developed in this chapter. The new version of the order-entry
application will use DCOM to overcome the limitations of the Web version created in this chapter.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Chapter 10
Using DCOM
IN THIS CHAPTER
• How DCOM extends the COM programming model beyond the boundaries
of a single machine
• The two forms of security employed by DCOM: activation security and call
security
• The various levels of authentication and impersonation supported by DCOM
• How to use DCOMCNFG to provide both default system-wide and
application-specific DCOM configuration information
• The various registry settings used by DCOM
• The additional APIs added to COM on behalf of DCOM
• How DCOM can be used to improve both the traditional client/server
application architecture and the Web application architecture
DCOM CAN BE defined simply as the extension of the COM programming model
beyond the boundaries of one physical machine. DCOM allows COM clients to
manipulate COM objects located on physically separate machines through what
amounts to a remote procedure call. In fact, DCOM is based on MS-RPC,
Microsoft’s implementation of the Open Software Foundation’s (OSF) Distributed
Computing Environment (DCE) Remote Procedure Call (RPC) system. By building
on top of RPC, DCOM is shielded from the underlying network protocol, and is thus
capable of running a variety of connected and connectionless protocols, such as TCP,
UDP, SPX, IPX, Netbios, and NetBEUI, just to name a few. DCOM supports all
combinations of connectivity between in-process and out-of-process clients and
remote servers. It is able to support remote in-process servers by loading them into a
special surrogate process designed specifically for this purpose. The ability to
instantiate or connect to a remote COM object on a different machine raises some
very interesting issues with regard to security, as you’ll see in the next section.
DCOM Security
In the pre-DCOM days, security was not a major concern for most COM developers.
All objects were created locally and inherited the user’s security context and access
privileges. However, with the advent of DCOM, users are able to create objects on
remote machines and gain access to the remote machine’s resources. To prevent
applications from using remote resources in an irresponsible or malicious manner,
DCOM employs two forms of security: activation security and call security.
Activation security effectively regulates who is authorized to launch a particular
COM server, and it is totally independent of call security, which regulates who is
authorized to access the various interface functions of a particular COM object. Since
these two methods of security work independently of each other, it is possible for a
client to have activation authorization and be able to start a particular object’s server,
but not have call authorization to actually access the various interface functions
provided by the object itself.
Before a user can be granted or denied authorization to perform a specific task, he or
she must be authenticated. Authentication is the process of verifying that a user is
who they claim to be. Table 10-1 illustrates the various levels of authentication
supported by DCOM. It should be noted that, at the time this book is going to print,
DCOM on Windows 95 only supports the RPC_C_AUTHN_LEVEL_NONE and
RPC_C_AUTHN_LEVEL_CONNECT levels of authentication.
Table 10-1 Authentication Levels Supported by DCOM
No authentication is
RPC_C_AUTHN_LEVEL_NONE 1
performed.
RPC_C_AUTHN_LEVEL_CONNECT 2 Used by connection-oriented
protocols to perform
authentication whenever a
client establishes a connection
with the server. Connectionless
protocols use
RPC_C_AUTHN_LEVEL_PKT
instead.
RPC_C_AUTHN_LEVEL_CALL 3 Used by connection-oriented
protocols to perform
authentication whenever the
server receives an RPC call.
Connectionless protocols use
RPC_C_AUTHN_LEVEL_PKT
instead.
RPC_C_AUTHN_LEVEL_PKT 4 Authentication of all data is
performed on a per-packet
basis.
RPC_C_AUTHN_LEVEL_PKT_INTEGRITY 5 Same as above, with added
validation that no data has
been modified.
All of the above, plus
RPC_C_AUTHN_LEVEL_PKT_PRIVACY 6
encryption.
Once a user has been validated, DCOM allows you to decide the impersonation level
or security context of each call to the object, which ultimately determines the
resources accessible by the object. For example, if the call is executed under the
caller’s security context, then the object will impersonate the caller and only have
access to those system resources accessible by the caller. Table 10-2 illustrates the
various levels of security impersonation supported by DCOM.
Table 10-2 Impersonation Levels Supported by DCOM
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Ultimately, DCOM gains access to the security services of the underlying
operating system via the Security Support Provider Interface (SSPI), which
allows DCOM to use a variety of different security systems, such as Windows
NT’s default Windows NT LAN Manager Security Support Provider
(NTLMSSP) or DCE RPC’s Kerberos security system.
Microsoft has defined and added several new APIs and interfaces specifically
for the programmatic control and configuration of DCOM. (See Appendix A
for a complete list.) However, this does nothing to accommodate existing
COM applications. To accommodate existing COM applications, Microsoft
developed DCOMCNFG.
Using DCOMCNFG
DCOMCNFG ships as part of DCOM and provides a point-and-click way to
configure it. DCOMCNFG is ultimately a way for all existing COM clients
and servers to be accessed via DCOM without changing a single line of code!
Besides providing a way to configure DCOM for individual COM objects,
DCOMCNFG also enables you to provide a system-wide default DCOM
configuration (see Figure 10-1).
Figure 10-1 DCOMCNFG not only enables you to configure DCOM for
individual COM objects, but also provides a system-wide default DCOM
configuration.
Providing System-Wide Configuration Information
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Providing Object-Specific Configuration Information
DCOM’s system-wide default security is only applied if an object fails to provide security
information of its own. However, you can also use DCOMCNFG to configure object-specific
security as well as other valuable object-specific information. On DCOMCNFG’s
Applications tab, you will notice the names of the various COM servers that have registered
themselves as being available for remote invocation (see Figure 10-1). COM servers expose
themselves for remote invocation by adding the following two entries to the system registry:
HKEY_CLASSES_ROOT
APPID
{APPID_GUID}
RemoteServerName = servername
For the preceding code:
• APPID_GUID indicates the COM server responsible for creating a particular COM
object.
• servername is either the DNS or UNC name of the machine where the COM server
should be launched.
Following is the other registry setting required of COM servers that wish to expose
themselves for remote invocation:
HKEY_CLASSES_ROOT
CLSID
{CLSID_GUID}
AppId = {APPID_GUID}
In this case:
• CLSID_GUID is the COM object’s CLSID.
• APPID_GUID is the APPID of the remote process responsible for creating the object.
In-process servers that wish to expose themselves for remote invocation must also add the
DLLSurrogate value under their appropriate HKEY_CLASSES_ROOT\
APPID\{APPID_GUID} key:
HKEY_CLASSES_ROOT
APPID
{APPID_GUID}
DLLSurrogate = surrogatePath
RemoteServerName = servername
In this example, surrogatePath is the name of a custom surrogate, or you can use NULL to
request the default (DllHost.exe) surrogate.
To configure the security and other application-specific information for a specific COM
server, highlight the server in the Applications tab of DCOMCNFG and click on the
Properties button. DCOMCNFG will respond by displaying the current settings for the
application (see Figure 10-4).
Figure 10-4 COM server-specific DCOM settings can be configured by highlighting the
server in the Applications tab of DCOMCNFG and clicking on the Properties button.
As Figure 10-5a, Figure 10-5b, and Figure 10-5c illustrate, DCOMCNFG has
application-specific tabs that allow you to configure the execution location, launch, access,
and configuration security, as well as the impersonation identity, for a particular COM server.
Figures 10-5a - 10-5c DCOMCNFG allows you to configure a COM server’s execution
location, launch, access, and configuration security, as well as impersonation identity.
While DCOMCNFG provides a way for existing applications to use DCOM without writing a
single line of code, these applications may achieve less than optimal performance, as they
simply were not designed to perform in a distributed manner. Due to latencies introduced by
the network, distributed applications must take special precautions to minimize the total
number of trips back and forth across the network required to execute a remote procedure and
return a result, otherwise known as network round-trips (see Figure 10-6).
Figure 10-6 Each remote procedure call executed via DCOM requires one network
round-trip - one leg to the server that actually executes the call and one leg back to the client
with the results of the call.
To understand the negative impact of too many network round-trips, consider the following
code snippet, typical of a legacy VB application that retrieves each property from an Account
object in order to display them to the user:
txtNumber.Text = CStr(localAccount.Number)
txtBalance.Text = CStr(localAccount.Balance)
txtLimit.Text = CStr(localAccount.Limit)
'
txtName.Text = localAccount.Name
txtSex.Text = localAccount.Sex
txtAddress.Text = localAccount.Address
txtCity.Text = localAccount.City
txtState.Text = localAccount.State
txtZip.Text = localAccount.Zip
txtPhone.Text = localAccount.Phone
chkInService.Value = Abs(localAccount.InService)
If localAccount is a remote object being accessed via DCOM, performance will be less
than optimal to say the least, as the above code requires eleven trips back and forth across the
network, one for each property. Ideally, the Account object referenced in localAccount
would support a single method that could be used to retrieve all of the object’s properties at
the same time (see Figure 10-7):
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Another area where performance can be easily improved is in the retrieval of many different
interface pointers. For example, imagine that you need to retrieve several different interface
pointers from a particular remote object. Each time you call QueryInterface, you are
charged one network round-trip. Among the special modifications that DCOM has added to the
underlying COM system services is the ability to query for multiple interfaces with a single
call, using the IMultiQI interface. IMultiQI is a special interface that has been added to
the standard COM proxy, which means that you never have to actually implement it! All you
have to do to obtain multiple interfaces is call QueryInterface to obtain an IMultiQI
interface pointer, then call IMultiQI::QueryMultipleInterfaces with the
appropriate arguments. How ’bout that! The function prototype for
QueryMultipleInterfaces is
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Listing 10-1 shows the implementation of the Account object’s new GetProperties and
SetProperties methods, which are used to minimize the amount of network round-trips
necessary to get or set multiple properties of a remote Account object. As you look at the
implementation, notice how both methods delegate to the other Get/Set member functions.
Listing 10-1. Implementation of the Account object’s GetProperties and SetProperties methods.
//
//SetProperties
//
STDMETHODIMP CAccount::SetProperties(long lNumber, float fBalance,
long llimit, BSTR bstrName, BSTR bstrSex, BSTR bstr Address,
BSTR bstrCity, BSTR bstrState, BSTR bstrZip, BSTR bstrPhone,
VARIANT_BOOL vbInService)
{
HRESULT hr = NOERROR;
hr = put_Number(lNumber);
if (FAILED(hr))
return hr:
hr = put_Balance(fBalance):
if (FAILED(hr))
return hr:
hr = put_Limit(lLimit):
if (FAILED(hr))
return hr;
hr = put_Name(bstrName);
if (FAILED(hr))
return hr;
hr = put_Sex(bstrSex);
if (FAILED(hr))
return hr;
hr = put_Address(bstrAddress);
if (FAILED(hr))
return hr;
hr = put_City(bstrCity);
if (FAILED(hr))
return hr;
hr = put_State(bstrState);
if (FAILED(hr))
return hr;
hr = put_Zip(bstrZip);
if (FAILED(hr))
return hr;
hr = put_Phone(bstrPhone);
if (FAILED(hr))
return hr;
hr = put_InService(vbInService);
return hr;
}//SetProperties
//
//GetProperties
//
STDMETHODIMP CAccount::GetProperties(long *lRetNumber,
float *fRetBalance, long *lRetLimit, BSTR *bstrRetName,
BSTR *bstrRetSex, BSTR *bstrRetAddress, BSTR *bstrRetCity,
BSTR *bstrRetState, BSTR *bstrRetZip, BSTR *bstrRetPhone,
VARIANT_BOOL *vbRetInService)
{
HRESULT hr = NOERROR;
hr = get_Number(lRetNumber);
if (FAILED(hr))
return hr;
hr = get_Balance(fRetBalance);
if (FAILED(hr))
return hr;
hr get_Limit(lRetLimit);
if (FAILED(hr))
return hr;
hr get_Name(bstrRetName);
if (FAILED(hr))
return hr;
hr = get_Sex(bstrRetSex);
if (FAILED(hr))
return hr;
hr = get_Address(bstrRetAddress):
if (FAILED(hr))
return hr;
hr = get_City(bstrRetCity);
if (FAILED(hr))
return hr;
hr = get_State(bstrRetState);
if (FAILED(hr))
return hr;
hr = get_Zip(bstrRetZip);
if (FAILED(hr))
return hr;
hr = get_Phone(bstrRetPhone);
if (FAILED(hr))
return hr;
hr = get_InService(vbRetInService);
return hr;
}//GetProperties
When the order-entry application is deployed, we add the appropriate DCOM settings to the
system registry such that the execution location of each collection object points to the appropriate
remote server machine. The remote server machine is responsible for having the new server
portion of the OrderEntry object hierarchy, as well as ODBC, properly installed and configured.
Currently, whenever the client/server order-entry application retrieves an entry object using the
Item property of a collection object, the application receives an interface pointer to an entry object.
However, now that the entry objects reside on a different machine than the collection objects, at
best the collection objects can only return a remote reference to the client, which would force the
client to make unnecessary network round-trips whenever it needed to access the object. The other
solution is for the client to create a local blank object, then pass it as a remote reference to the
Item property. The Item property could then use the SetProperties method of the remote
object to define all of the object’s properties in a single network round-trip, and the client would
maintain a local reference to the object. Because the Item property no longer returns an interface
pointer, there is no need for it to be a property, so we’ll convert it to a method. Following is the
new IDL definition for the Item method:
.
.//Manipulate retrieved object
.
the client will use code like this:
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Listing 10-2 shows the new implementation of the Accounts object’s Item method. Notice how
the remote object’s SetProperties method is used to minimize the total number of network
round-trips.
Listing 10-2. The new implementation of the Accounts object’s Item method.
//
//Item
//
STDMETHODIMP CAccounts::Item(IAccount *pIAccount, VARIANT Key)
{
HRESULT hr = NOERROR;
RETCODE retcode;
long lNumber;
float flBalance;
long lLimit;
wchar_t wszName[NAME_LEN];
char szName[NAME_LEN];
wchar_t wszSex[SEX_LEN];
char szSex[SEX_LEN];
wchar_t wszAddress[ADDRESS_LEN];
char szAddress[ADDRESS_LEN];
wchar_t wszCity[CITY_LEN];
char szCity[CITY_LEN];
wchar_t wszState[STATE_LEN];
char szState[STATE_LEN];
wchar_t wszZip[ZIP_LEN];
char szZip[ZIP_LEN];
wchar_t wszPhone[PHONE_LEN];
char szPhone[PHONE_LEN];
VARIANT-BOOL vbInService;
SDWORD cbNumber = 0;
SDWORD cbBalance = 0;
SDWORD cbLimit = 0;
SDWORD cbName SQL_NTS;
SDWORD cbSex = SQL_NTS;
SDWORD cbAddress = SQL_NTS;
return hr;
}//Item
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
Ideally, we would like to develop the Web version of the order-entry application using the
COM programming model, in which the application’s user interface is created from various
visual objects that execute code in response to various events supported by the individual
visual objects. Microsoft Internet Explorer (IE) 3.0 provides us with exactly this level of
support, and even allows us to manipulate the various objects with client-side scripts.
Client-side scripts that are written to run within the browser can be included as part of the
HTML file itself, enclosed within <%...%> tags; similar to the way in which server-side
scripts are included as part of an ASP file. When the browser loads an HTML file, it processes
the file line-by-line from the top to the bottom, locating and executing the various lines of
script enclosed within the <%...%> tags. In addition, IE 3.0 supports many different
client-side scripting languages, including VBScript, JScript, Perl, and Rexx. IE 3.0 is included
on the companion CD-ROM if you don’t already have IE 3.0 or greater installed on your
system.
We will use VBScript because of its close resemblance to VB, which we used to create the
client/server version of the order-entry application. By combining IE 3.0’s ability to execute
client-side scripts and access client-side system resources using the COM programming
model with DCOM’s ability to remotely access server-side resources in a secure manner, we
get the best of both worlds. We get the benefits of the weblication model - ease of installation,
configuration, and support - plus the benefits of the client/server model: access to both
client-side and server-side system resources! Let’s look at how we can apply this new hybrid
application architecture to the Web version of our order-entry application.
Currently, the Web version of the order-entry application uses ASP extensively as a way to
access server-side system resources. However, this means that the client must issue requests
for new HTML pages in order to communicate with ASP. For example, the user interface
generated by NewAccount.htm relies on SaveAccountLogic.asp to provide the processing
logic necessary to actually add a new customer account to the underlying data source (see
Figure 10-8).
Figure 10-8 Currently, the user interface presented by NewAccount.htm relies on
SaveAccountLogic.asp to actually add a new customer account to the underlying data source.
But now we can use our new hybrid application architecture to embed client-side VBScript
directly within NewAccount.htm. The new hybrid architecture allows the user interface
presented by NewAccount.htm to manipulate the Account and Accounts OrderEntry
objects directly, in the exact same manner as the client/server version of the order-entry
application, to add a new customer account to the data source all by itself (see Figure 10-9).
Figure 10-9 The combination of client-side scripting and DCOM allows the user interface
presented by NewAccount.htm to manipulate the Account and Accounts objects directly to
add a new customer account to the underlying data source.
The VBScript code used by NewAccount.htm to add a new customer account to the data
source can be seen in Listing 10-3, and is remarkably similar to the VBScript code used by
the AccountInfoForm form in the client/server version, which can be seen in Listing
10-4.
Listing 10-3. VBScript code from the NewAccount.htm page responsible for actually adding
new customer accounts.
<script language="VBScript">
<!-
Sub cmdOK_onClick()
Dim localAccountsCol
Dim localAccount
Set localAccountsCol =
CreateObject("DCOMOrderEntryServer.Accounts")
Set localAccount =
CreateObject ("DCOMOrderEntryClient.Account")
localAccount.Balance = CLng(AddForm.txtBalance.Value)
localAccount.Limit = CLng(AddForm.txtLimit.Value)
'
localAccount.Name = Trim(AddForm.txtName.Value)
localAccount.Sex = Trim(AddForm.txtSex.Value)
localAccount.Address = Trim(AddForm.txtAddress.Value)
localAccount.City = Trim(AddForm.txtCity.Value)
localAccount.State = Trim(AddForm.txtState.Value)
localAccount.Zip = Trim(AddForm.txtZip.Value)
localAccount.Phone = Trim(AddForm.txtPhone.Value)
localAccount.InService = (AddForm.chkInService.Checked = 1)
'add to the data source
localAccountsCol.Add localAccount
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
-----------
While the hybrid architecture allows us to access server-side resources using DCOM, there are
places where ASP is still the best solution. For example, consider how the ModifyAccount.asp
file is used to retrieve information about a specific customer account and display it on an
HTML page. ASP provides a great facility for the entire page, complete with customer account
information, to be generated on the server and shipped to the client for display. However, once
the account information has been edited via the ModifyAccount.asp user interface, the
Account and Accounts OrderEntry objects are manipulated using client-side VBScript to
save the edited contents to the data source, using the code in Listing 10-5.
Listing 10-5. VBScript code from the ModifyAccount.asp page responsible for actually saving
modified customer account information to the data source.
<script language="VBScript">
<-!
Sub cmdOK_onClick()
Dim localAccountsCol
Dim localAccount
localAccount.Number = CLng(EditForm.AccountNumber.value)
localAccount.Balance = CLng(EditForm.txtBalance.Value)
localAccount.Limit = CLng(EditForm.txtLimit.Value)
'
localAccount.Name = Trim(EditForm.txtName.Value)
localAccount.Sex = Trim(EditForm.txtSex.Value)
localAccount.Address = Trim(FditForm.txtAddress.Value)
localAccount.City = Trim(EditForm.txtCity.Value)
localAccount.State = Trim(EditForm.txtState.Value)
localAccount.Zip = Trim(EditForm.txtZip.Value)
localAccount.Phone = Trim(EditForm.txtPhone.Value)
localAccount.InService = (EditForm.chkInService.Checked = 1)
'update the data source
localAccountsCol.Update localAccount
Summary
In this chapter, you learned:
• That DCOM extends the COM programming model beyond the physical machine
boundaries by using MS-RPC, Microsoft’s implementation of the OSF DCE RPC
system.
• That DCOM employs two independently operating forms of security: activation
security and call security.
• That activation security regulates who is authorized to launch a particular COM server.
• That call security regulates who is authorized to access the various interface functions
defined by a particular COM object.
• That DCOM offers several levels of user authentication and impersonation to suit a
wide variety of needs.
• How to use DCOMCNFG to provide both default system-wide and
application-specific DCOM configuration information.
• That DCOM relies on the HKEY_CLASSES_ROOT\APPID\ registry key and the
APPID named value to locate and launch remote COM servers.
• That DCOM can be used programmatically via the additional APIs that have been
added to COM on DCOM’s behalf.
• How DCOM can be used to improve the architectures of both the traditional
client/server application and the Web application.
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Table of Contents
-----------
Appendix A
Frequently Used Interfaces and APIs
THE FOLLOWING IS a list of frequently used interfaces and APIs. For further
information, consult Microsoft Developer Network (MSDN) or the on-line
documentation provided by your development environment.
Interfaces
IAdviseSink IBindCtx
ICatInformation ICatRegister
IClassActivator IClassFactory
IClassFactory2 IConnectionPoint
IConnectionPointContainer IDataAdviseHolder
IDataObject IEnumXXXX
IEnumConnectionPoints IEnumConnections
IEnumFORMATETC IEnumMoniker
IEnumSTATDATA IEnumSTATPROPSETSTG
IEnumSTATSTG IEnumString
IEnumUnknown IErrorLog
IExternalConnection ILockBytes
IMalloc IMallocSpy
IMarshal IMessageFilter
IMoniker IOleItemContainer
IOXIDResolver IParseDisplayName
IPersist IPersistFile
IPersistMemory IPersistMoniker
IPersistPropertyBag IPersistStorage
IPersistStream IPersistStreamInit
IPropertyBag IPropertySetStorage
IPropertyStorage IProvideClassInfo
IProvideClassInfo2 IRootStorage
IROTData IRunnableObject
IRunningObjectTable IStdMarshalInfo
IStorage IStream
IUnknown
DCOM
IClientSecurity IMultiQI
IServerSecurity
Automation
ICreateErrorInfo ICreateTypeInfo
ICreateTypeInfo2 ICreateTypeLib
ICreateTypeLib2 IErrorInfo
IDispatch IProvideClassInfo
IProvideClassInfo2 ISupportErrorInfo
ITypeComp ITypeInfo
ITypeInfo2 ITypeLib
ITypeLib2
APIs
General
BindMoniker CLSIDFromProgID
CLSIDFromString CoAddRefServerProcess
CoCreateFreeThreadedMarshaler CoCreateGuid
CoCreateInstance CoDisconnectObject
CoDosDateTimeToFileTime CoFileTimeNow
CoFileTimeToDosDateTime CoFreeAllLibraries
CoFreeLibrary CoFreeUnusedLibraries
CoGetClassObject CoGetCurrentProcess
CoGetInstanceFromFile CoGetInstanceFormIStorage
CoGetInterfaceAndReleaseStream CoGetMalloc
CoGetMarshalSizeMax CoGetPSClsid
CoGetStandardMarshal CoGetTreatAsClass
CoInitialize CoInitializeEx
CoIsHandlerConnected CoLoadLibrary
CoLockObjectExternal CoMarshalInteface
CoMarshalInterThreadInterfaceInStream
CoRegisterClassObject CoRegisterMallocSpy
CoRegisterMessageFilter CoRegisterPSClsid
CoReleaseMarshalData CoReleaseServerProcess
CoResumeClassObjects CoRevokeClassObject
CoRevokeMallocSpy CoSuspendClassObjects
CoTaskMemAlloc CoTaskMemFree
CoTaskMemRealloc CoTreatAsClass
CoUninitialize CoUnmarshalInterface
CreateAntiMoniker CreateBindCtx
CreateClassMoniker CreateDataAdviseHolder
CreateGenericComposite CreateILockBytesOnHGlobal
CreateItemMoniker CreatePointerMoniker
CreateStreamOnHGlobal DllCanUnloadNow
DllGetClassObject DllRegisterServer
DllUnregisterServer FreePropVariantArray
GetClassFile GetConvertStg
GetHGlobalFromILockBytes GetHGlobalFromStream
GetRunningObjectTable IIDFromString
IsEqualGUID IsEqualCLSID
IsEqualIID MkParseDisplayName
MonikerCommonPrefixWith MonikerRelativePathTo
ProgIDFromCLSID PropStgNameToFmtId
PropVariantClear PropVariantCopy
ReadClassStg ReadClassStm
ReadFmtUserTypeStg ReleaseStgMedium
SetConvertStg StgCreateDocfile
StgCreateDocfileOnILockBytes StgCreatePropSetStg
StgCreatePropStg StgIsStorageFile
StgIsStorageILockBytes StgOpenPropStg
StgOpenStorage StgOpenStorageOnILockBytes
StgSetTimes StringFromCLSID
StringFromGUID2 StringFromIID
WriteClassStg WriteClassStm
WriteFmtUserTypeStg
DCOM
CoCopyProxy CoCreateInstanceEx
CoGetCallContext CoImpersonateClient
CoInitializeSecurity CoQueryAuthenticationServices
CoQueryClientBlanket CoQueryProxyBlanket
CoRevertToSelf CoSetProxyBlanket
Automation
GENERAL
CreateErrorInfo CreateStdDispatch
DispGetIDsOfNames DispGetParam
DispInvoke GetActiveObject
GetAltMonthNames GetErrorInfo
RegisterActiveObject RevokeActiveObject
SetErrorInfo
TYPE LIBRARY
CreateDispTypeInfo CreateTypeLib
LHashValOfName LHashValOfNameSys
LoadRegTypeLib LoadTypeLib
LoadTypeLibEx RegisterTypeLib
UnRegisterTypeLib
BSTR
BstrFromVector SysAllocString
SysAllocStringByteLen SysAllocStringLen
SysFreeString SysReAllocString
SysReAllocStringLen SysStringByteLen
SysStringLen VectorFromBstr
SAFEARRAY
SafeArrayAccessData SafeArrayAllocData
SafeArrayAllocDescriptor SafeArrayCopy
SafeArrayCopyData SafeArrayCreate
SafeArrayCreateVector SafeArrayDestroy
SafeArrayDestroyData SafeArrayDestroyDescriptor
SafeArrayGetDim SafeArrayGetElement
SafeArrayGetElemsize SafeArrayGetLBound
SafeArrayGetUBound SafeArrayLock
SafeArrayPtrOfIndex SafeArrayPutElement
SafeArrayRedim SafeArrayUnaccessData
SafeArrayUnlock
VARIANT
DosDateTimeToVariantTime SystemTimeToVariantTime
VarDateFromUdate VariantChangeType
VariantChangeTypeEx VariantClear
VariantCopy VariantCopyInd
VariantInit VariantTimeToDosDateTime
VariantTimeToSystemTime VarNumFromParseNum
VarParseNumFromStr VarUdateFromDate
National Language
CompareString LCMapString
GetLocaleInfo GetStringType
GetSystemDefaultLangID GetSystemDefaultLCID
GetUserDefaultLangID GetUserDefaultLCID
Registry
RegCloseKey RegConnectRegistry
RegCreateKey RegCreateKeyEx
RegDeleteKey RegDeleteValue
RegEnumKey RegEnumKeyEx
RegEnumValue RegFlushKey
RegGetKeySecurity RegLoadKey
RegNotifyChangeKeyValue RegOpenKey
RegOpenKeyEx RegQueryInfoKey
RegQueryMultipleValues RegQueryValue
RegQueryValueEx RegReplaceKey
RegRestoreKey RegSaveKey
RegSetKeySecurity RegSetValue
RegSetValueEx RegUnLoadKey
Table of Contents
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Table of Contents
-----------
Appendix B
Resources for COM Developers
BESIDES THE COM Specification that is included on the companion
CD-ROM and the Microsoft Win32 Software Development Kit (SDK)
documentation that is typically included as part of your compiler’s
documentation, the following is a list of essential resources containing
additional COM programming information.
Books
Kraig Brockschmidt, Inside OLE (Microsoft Press); ISBN
1-55615-618-9
OLE 2 Programmer’s Reference, Volume 1 (Microsoft Press); ISBN
1-55615-628-6
OLE 2 Programmer’s Reference, Volume 2 (Microsoft Press); ISBN
1-55615-629-4
OLE Automation Programmer’s Reference (Microsoft Press); ISBN
1-55615-851-3
Table of Contents
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Table of Contents
-----------
Appendix C
What’s on the CD-ROM
THE COMPANION CD-ROM contains:
• Sample source code from the examples presented throughout the book
• The COM Specification
• DCOM for Windows 95
Table of Contents
[an error occurred while processing this directive]
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Table of Contents
-----------
Appendixes
Quick Reference
ODL Language Features in MIDL
THE MICROSOFT INTERFACE Definition Language (MIDL) compiler and the MkTypLib utility are both
capable of compiling scripts that are written in the Object Description Language (ODL). Since Microsoft
has expanded the Interface Definition Language (IDL) to contain the complete ODL syntax, you should
use the MIDL compiler in preference to MkTypLib, as MkTypLib is being phased out and will no longer
be supported.
For more information about the MIDL compiler, refer to the MIDL Programmer’s Guide and Reference in
the Win32 Software Development Kit (SDK).
1 typelib mylib1.tlb
2 typelib mylib2.tlb
There can be multiple type library resources in a DLL. Application developers should use the resource
compiler to add the .tlb file to their own DLL. A DLL with one or more type library resources typically
has the file extension .olb (object library).
• A resource in an .exe file. The file can contain multiple type libraries.
• A stand-alone binary file. The .tlb (type library) file output by the MkTypLib utility is a binary
file.
Object browsers, compilers, and similar tools access type libraries through the interfaces ITypeLib,
ITypeLib2, ITypeInfo, ITypeInfo2 and ITypeComp. Type library tools (such as MkTypLib)
can be created using the interfaces ICreateTypeLib, ICreateTypeLib2, ICreateTypeInfo,
and ICreateTypeInfo2.
library a
{
interface [xyz]];
struct bar;
...
};
If an element defined outside of the block is referenced in the block, its definition is put into the generated
type library.
Anything outside of the library block is an .idl file, and the MIDL compiler processes it as usual.
Typically, this means generating proxy stubs for it.
Invoking MkTypLib
To invoke MkTypLib, enter the following command line:
Although MkTypLib offers minimal error reporting, error messages include accurate line number and
column number information that can be used with text editors to locate the source of errors.
type arrname[size];
To describe a SAFEARRAY, use the following syntax:
"commandName"
"This string contains a \"quote\"."
"Here's a pathname: c:\\bin\\binp"
A string can be up to 255 characters long.
FILE NAMES
A file name is a string that represents either a full or partial path. Automation expects to find files in
directories that are referenced by the type library registration entries, so partial path names are typically
used.
FORWARD DECLARATIONS
Forward declarations permit forward references to types. Forward references have the following form:
typedef struct mydata;
interface aninterface;
dispinterface fordispatch;
coclass pococlass;
GLOBALLY UNIQUE IDENTIFIER (GUID)
A universally unique identifier (UUID) is a globally unique identifier (GUID). This number is created by
running the Guidgen.exe command line program. Guidgen.exe never produces the same number twice, no
matter how many times it is run or how many different machines it runs on. Every entity that needs to be
uniquely identified (such as an interface) has a GUID.
IDENTIFIERS
Identifiers can be up to 255 characters long, and must conform to C-style syntax. MkTypLib is case
sensitive, but it generates type libraries that are case insensitive. It is therefore possible to define a
user-defined type whose name differs from that of a built-in type only by case. User-defined type names
(and member names) that differ only in case refer to the same type or member. Except for property
accessor functions, it is invalid for two members of a type to have the same name, regardless of case.
STRING DEFINITIONS
Strings can be declared using the LPSTR data type, which indicates a zero-terminated string, and with the
BSTR data type, which indicates a length-prefixed string. In 32-bit type libraries, Unicode strings can be
defined with the LPWSTR data type.
Attributes List
The following is a list of the Object Description Language (ODL) attributes, statements, and directives
that are now part of the Microsoft Interface Definition Language (MIDL).
appobject aggregatable
bindable control
custom default
defaultbind defaultcollelem
defaultvalue defaultvtbl
displaybind dllname
dual entry
helpcontext helpfile
helpstring helpstringcontext
helpstringdll hidden
id immediatebind
in lcid
licensed nonbrowsable
noncreateable nonextensible
odl oleautomation
optional out
propget propput
propputref public
readonly replaceable
requestedit restricted
retval source
string uidefault
usesgetlasterror uuid
vararg version
Statements List
coclass dispinterface
enum interface
library module
struct typedef
union
Directives List
importlib
Attribute Descriptions
The following sections describe the ODL attributes and the types of objects that they apply to, along with
the equivalent flags set in the object’s type information.
appobject
Description
Identifies the Application object.
Allowed on
Coclass.
Comments
Indicates that the members of the class can be accessed without qualification when accessing this
type library.
Flags
TYPEFLAG_FAPPOBJECT
aggregatable
Description
Indicates that the class supports aggregation.
Allowed on
Coclass.
Comments
Indicates that the members of the class can be aggregated.
Flags
TYPEFLAG-FAGGREGATABLE
Example
[uuid(1e196b20-1f3c-1069-996b-00dd010fe676), aggregatable]
coclass Form
{
[default] interface IForm;
[default, source] interface IFormEvents;
}
bindable
Description
Indicates that the property supports data binding.
Allowed on
Property.
Comments
Refers to the property as a whole, so it must be specified wherever the property is defined. The
attribute should be specified on both the property get description and the property set description.
Flags
FUNCFLAG_FBINDABLE
VARFLAG_FBINDABLE
control
Description
Indicates that the item represents a control from which a container site will derive additional type
libraries or coclasses.
Allowed on
Type library, coclass.
Comments
This attribute allows type libraries that describe controls to be marked so that they are not displayed
in type browsers intended for nonvisual objects.
Flags
TYPEFLAG_FCONTROL
LIBFLAG_FCONTROL
custom(guid, value)
Description
Indicates a custom attribute (one not defined by Automation). This feature enables the independent
definition and use of attributes.
Parameters
<guid> The standard GUID form.
<value> A value that can be put into a variant. See also the const directive.
Allowed on
Library, typeinfo, typlib, variable, function, parameter.
Not allowed on
A member of a coclass (IMPLTYPE).
Representation
Can be retrieved using:
ITypeLib2::GetCustData
ITypeInfo2::GetCustData
ITypeInfo2::GetAllCustData
ITypeInfo2::GetFuncCustData
ITypeInfo2::GetAllFuncCustData
ITypeInfo2::GetVarCustData
ITypeInfo2::GetAllVarCustData
ITypeInfo2::GetParamCustData
ITypeInfo2::GetAllParamCustData
ITypeInfo2::GetImplTypeCustData
ITypeInfo2::GetAllImplTypeCustData
Example
The following example shows how to add a string-valued attribute that gives the ProgID for a class:
[custom(GUID_PROGID, "DAO.Dynaset")]
coclass Dynaset
{
[default] interface Dynaset;
[default, source] interface IDynasetEvents;
}
default
Description
Indicates that the interface or dispinterface represents the default programmability interface.
Intended for use by macro languages.
Allowed on
Coclass member.
Comments
A coclass can have two default members at most. One represents the source interface or
dispinterface, and the other represents the sink interface or dispinterface. If the default attribute
is not specified for any member of the coclass or cotype, the first source and sink members that do
not have the restricted attribute will be treated as the defaults.
Flags
IMPLTYPEFLAG_FDEFAULT
defaultbind
Description
Indicates the single, bindable property that best represents the object.
Allowed on
Property.
Comments
Properties that have the defaultbind attribute must also have the bindable attribute. The
defaultbind attribute cannot be specified on more than one property in a dispinterface.
This attribute is used by containers that have a user model that involves binding to an object rather
than binding to a property of an object. An object can support data binding and not have this
attribute.
Flags
FUNCFLAG_FDEFAULTBIND
VARFLAG_FDEFAULTBIND
defaultcollelem
Description
Allows for optimization of code.
Allowed on
Property, members in dispinterface and interface.
Comments
In Visual Basic for Applications (VBA5.0), foo!bar is normally syntactic shorthand for
foo.defaultprop (“bar”). Because such a call is significantly slower than accessing a data
member of foo directly, an optimization has been added in which the compiler looks for a member
named “bar” on the type of foo. If such a member is found and flagged as an accessor function for
an element of the default collection, a call is generated to that member function. To allow vendors
to produce object servers that will be optimized in this way, the member flag should be
documented.
Because this optimization searches the type of item that precedes the ‘!’, it will optimize calls of the
form MyForm!bar only if MyForm has a member named “bar,” and it will optimize
MyForm.Controls!bar only if the return type of Controls has a member named “bar.” Even
though MyForm!bar and MyForm.Controls!bar both would normally generate the same
calls to the object server, optimizing these two forms requires that the object server add the bar
method in both places.
Use of [defaultcollelem] must be consistent for a property. For example, if it is present on a
Get, it must also be present on a Put.
Flags
FUNCFLAG_FDEFAULTCOLLELEM
VARFLAG_FDEFAULTCOLLELEM
Example
A form has a button on it named Button1. User code can access the button using property syntax
or ! syntax, as shown below.
Sub Test()
Dim f As Form1
Dim b1 As Button
Dim b2 As Button
Set f = Form1
To use the property syntax and the ! syntax properly, see the form in the type information below.
[
odl, dual, uuid(1e196b20-1f3c-1096-996b-00dd010ef676),
helpstring("This is IForm"), restricted
]
interface IForm1: Iform
{
[propget, defaultcollelem]
HRESULT Button1([out, retval] Button *Value);
}
defaultvalue(vallue)
Description
Enables specification of a default value for a typed optional parameter.
Allowed on
Parameter.
Comments
The expression value resolves to a constant that can be described in a variant. The ODL already
allows some expression forms, as when a constant is declared in a module. The same expressions
are supported without modification.
The following example shows some legal parameter descriptions:
interface IFoo
{
void Ex1([defaultvalue(44)] LONG i);
void Ex2([defaultvalue(44)] SHORT i);
void Ex3([defaultvalue("Hello")] BSTR i);
}
interface QueryDef
{
An object can have both a default source and a default VTBL source interface with the same
interface identifier (IID or GUID). In this case, a sink should advise using IID_IDISPATCH to
receive dispatch events and use the specific interface identifier to receive VTBL events.
Allowed on
A member of a coclass.
Comments
For normal (non-source) interfaces, an object can support a single interface that satisfies consumers
who want to use IDispatch access as well as VTBL access (a dual interface). Because of the way
source interfaces work, it is not possible to use dual interface for source interfaces. The object with
the source interface is in control of whether calls are made through IDispatch or through the
VTBL. The sink does not provide any information about how it wants to receive the events. The
only action that object-sourcing events can take would be to use the least common denominator, the
IDispatch interface. This effectively reduces a dual interface to a dispatch interface with regard
to sourcing events.
HRESULT Click();
HRESULT Resize();
}
[uuid(1e196b20-1f3c-1069-996b-00dd010fe676)]
coclass Form
{
Bits Value
0-15 Offset. Any value is permissible.
16-21 The nesting level of this type information in the inheritance
hierarchy. For example:
interface mydisp : IDispatch
The nesting level of IUnknown is 0, IDispatch is 1, and
MyDisp is 2.
22-25 Reserved. Must be zero.
26-28 Dispatch identifier (DISPID) value.
True if this is the member identifier for a FuncDesc; otherwise
29
False.
30-31 Must be 01.
[
uuid(1e196b20-1fc3-1069-996b-00dd010ef671),
helpstring("This is Dynaset"),
noncreatable
]
coclass Dynaset
{
[default] interface IDynaset;
[default, source] interface IDynasetEvents;
}
Flags
TYPEFLAG_FCANCREATE
nonextensible
Description
Indicates that the IDispatch implementation includes only the properties and methods listed in
the interface description.
Allowed on
Dispinterface, interface.
Comments
The interface must have the dual attribute.
By default, Automation assumes that interfaces can add members at run time, meaning that it
assumes the interfaces are extensible.
Flags
TYPEFLAG_FNONEXTENSIBLE
odl
Description
Identifies an interface as an Object Description Language (ODL) interface.
Allowed on
Interface (required).
Comments
This attribute must appear on all interfaces.
oleautomation
Description
The oleautomation attribute indicates that an interface is compatible with Automation.
Allowed on
Interface.
Comments
Not allowed on dispinterfaces.
The parameters and return types specified for its members must be compatible with Automation. A
parameter is compatible with Automation if its type is compatible with an Automation type, a
pointer to an Automation type, or a SAFEARRAY of an Automation type. A return type is
compatible with Automation if its type is an HRESULT or is void. Methods in Automation must
return either HRESULT or void. A member is compatible with Automation if its return type and
all of its parameters are compatible with Automation. An interface is compatible with Automation if
it derives from IDispatch or IUnknown, if it has the oleautomation attribute, or if all of its
VTBL entries are compatible with Automation. For 32-bit systems, the calling convention for all
methods in the interface must be STDCALL. For 16-bit systems, all methods must have the CDECL
calling convention. Every dispinterface is compatible with Automation.
Flags
TYPEFLAG_FOLEAUTOMATION
optional
Description
Specifies an optional parameter.
Allowed on
Parameter.
Comments
Valid only if the parameter is of type VARIANT or VARIANT*. All subsequent parameters of the
function must also be optional.
out
Description
Specifies an output parameter.
Allowed on
Parameter.
Comments
The parameter must be a pointer to memory that will receive a result.
propget
Description
Specifies a property-accessor function.
Allowed on
Function, method in interface, dispinterface.
Comments
The property must have the same name as the function. At most, one of propget, propput, and
propputref can be specified for a function.
Flags
INVOKE_PROPERTYGET
propput
Description
Specifies a property-setting function.
Allowed on
Function, method in interface, dispinterface.
Comments
The property must have the same name as the function. Only one propget, propput, and
propputref can be specified.
Flags
INVOKE_PROPERTYPUT
propputref
Description
Specifies a property-setting function that uses a reference instead of a value.
Allowed on
Function, method in interface, dispinterface.
Comments
The property must have the same name as the function. Only one propget, propput, and
propputref can be specified.
Flags
INVOKE_PROPERTYPUTREF
public
Description
Includes an alias declared with the typedef keyword in the type library.
Allowed on
Alias declared with typedef.
Comments
By default, an alias that is declared with typedef, and has no other attributes, is treated as a
#define and is not included in the type library. Using the public attribute ensures that the alias
becomes part of the type library.
readonly
Description
Prohibits assignment to a variable.
Allowed on
Variable.
Flags
VARFLAG_FREADONLY
replaceable
Description
Tags an interface as having default behaviors.
Allowed on
Methods and properties of dispinterfaces and interfaces.
Comments
The object supports IConnectionPointWithDefault.
Flags
TYPEFLAG_FREPLACEABLE
FUNCFLAG_FREPLACEABLE
VARFLAG_FREPLACEABLE
requestedit
Description
Indicates that the property supports the OnRequestEdit notification.
Allowed on
Property.
Comments
The property supports the OnRequestEdit notification, raised by a property before it is edited.
An object can support data binding and not have this attribute.
Flags
FUNCFLAG_FREQUESTEDIT
VARFLAG_FREQUESTEDIT
restricted
Description
Prevents the item from being used by a macro programmer.
Allowed on
Type library, type information, coclass member, or member of a module or interface.
Comments
This attribute is allowed on a member of a coclass, independent of whether the member is a
dispinterface or interface, and independent of whether the member is a sink or source. A member of
a coclass cannot have both the restricted and default attributes.
Flags
IMPLTYPEFLAG_FRESTRICTED
FUNCFLAG_FRESTRICTED
TYPEFLAG_FRESTRICTED
VARFLAG_FRESTRICTED
Example
[
odl, dual, uuid(1e196b20-1f3c-1069-996b-00dd010ef676),
helpstring("This is IForm"), restricted
]
interface IForm: IDispatch
{
[propget]
HRESULT Backcolor([out, retval] long *Value);
[propput]
HRESULT Backcolor([in] long Value);
}
[
odl, dual, uuid(1e196b20-1f3c-1069-996b-00dd010ef767),
helpstring("This is IFormEVents"), restricted
]
interface IFormEvents: IDispatch
{
HRESULT Click();
}
[uuid(1e196b20-1f3c-1069-996b-00dd010fe676), helpstring("This is
Form")]
coclass Form
{
[default] interface IForm;
[default, source] interface IFormEvents;
}
retval
Description
Designates the parameter that receives the return value of the member.
Allowed on
Parameters of interface members that describe methods or get properties.
Comments
This attribute can be used only on the last parameter of the member. The parameter must have the
out attribute and must be a pointer type.
Parameters with this attribute are not displayed in user-oriented browsers.
Flags
IDLFLAG_FRETVAL
source
Description
Indicates that a member is a source of events.
Allowed on
Member of a coclass, property, or method.
Comments
For a member of a coclass, this attribute indicates that the member is called rather than
implemented.
On a property or method, this attribute indicates that the member returns an object or VARIANT
that is a source of events. The object implements the interface
IConnectionPointContainer.
Flags
IMPLTYPEFLAG_FSOURCE
VARFLAG_SOURCE
FUNCFLAG_SOURCE
string
Description
Specifies a string.
Allowed on
Structure, member, parameter, property.
Comments
Included only for compatibility with the Interface Definition Language (IDL). Use LPSTR for a
zero-terminated string.
uidefault
Description
Indicates that the type information member is the default member for display in the user interface.
Allowed on
A member of an interface or dispinterface.
Comments
This attribute is used to mark an event as the default (the first one created) or a property as the
default (the one to select first in the properties browser). For example, Visual Basic uses this
attribute in the following ways:
• When an object is double-clicked at design time, Visual Basic jumps to the event in the
default source interface that is marked as [uidefault]. If there is no such member, then
Visual Basic displays the first one listed in the default source interface.
• When an object is selected at design time, by default, the Properties window in Visual
Basic displays the property in the default interface that is marked as [uidefault]. If there
is no such member, then Visual Basic displays the first one listed in the default interface.
Flags
FUNCFLAG_FUIDEFAULT
VARFLAG_FUIDEFAULT
Example
[propget, uidefault]
HRESULT Name([out, retval] BSTR *Value);
[propput, uidefault]
HRESULT Name([in] BSTR Value);
}
[dllname("MyOwn.dll")]
module MyModule
{
[entry("One"), usesgetlasterror, bindable, requestedit, propputref,
defaultbind]
void Func1 ([in]IUnknown * iParam1, [out] long * Param2) ;
Statement Descriptions
The following sections describe the statements and directives that make up the Object Description
Language (ODL).
coclass
This statement describes the globally unique ID (GUID) and the supported interfaces for a Component
Object Model (COM).
Syntax
[attributes]coclass classname
{
[attributes2] [interface | dispinterface] interfacename;
};
Syntax Elements
attributes
The uuid attribute is required on a coclass. This is the same uuid that is registered as a CLSID in
the system registration database. The helpstring, helpcontext, licensed, version,
control, hidden, and appobject attributes are accepted, but not required, before a coclass
definition. For more information about the attributes accepted before a coclass definition, see
“Attribute Descriptions” earlier in this appendix. The appobject attribute makes the functions
and properties of the coclass globally available in the type library.
classname
Name by which the common object is known in the type library.
attributes2
Optional attributes for the interface or dispinterface. The source, default, and restricted
attributes are accepted on an interface or dispinterface in a coclass.
interfacename
Either an interface declared with the interface keyword, or a dispinterface declared with the
dispinterface keyword.
Comments
COM defines a class as an implementation that allows QueryInterface between a set of
interfaces.
Example
[
uuid(BFB73347-822A-1068-8849-00DD011087E8),
version(1.0), helpstring("A class"), helpcontext(2481),
appobject
]
coclass myapp
{
[source] interface IMydocfuncs;
dispinterface DMydocfuncs;
};
[uuid 00000000-0000-0000-0000-123456789019]
coclass foo
{
[restricted] interface bar;
interface bar;
}
dispinterface
This statement defines a set of properties and methods on which IDispatch::Invoke can be called.
A dispinterface can be defined by explicitly listing the set of supported methods and properties (Syntax 1)
or by listing a single interface (Syntax 2).
Syntax 1
The type can be any declared or built-in type, or a pointer to any type. Attributes on parameters are
in, out, optional, and string.
If optional is specified, it must only be specified on the right-most parameters, and the types of
those parameters must be VARIANT.
Comments
Method functions are specified exactly as described in the “module ” statement except that the
entry attribute is not allowed.
Properties can be declared either in the properties or methods lists. Declaring properties in the
properties list does not indicate the type of access the property supports (get, put, or putref).
Specify the readonly attribute for properties that do not support put or putref. If the property
functions are declared in the methods list, functions for one property will all have the same ID.
Using Syntax 1, the properties: and methods: tags are required. The id attribute is also required on
each member. For example
properties:
[id(0)] int Value; // Default property.
methods:
[id(1)] void Show();
Unlike interface members, dispinterface members cannot use the retval attribute to return a value
in addition to an HRESULT error code. The lcid attribute is also invalid for dispinterfaces
because IDispatch::Invoke passes a locale ID (LCID). However, it is possible to declare an
interface again that uses these attributes.
Using Syntax 2, interfaces that support IDispatch and are declared earlier in an ODL script can
be redeclared as IDispatch interfaces as follows:
dispinterface helloPro
{
interface hello;
};
This example declares all of the members of the Hello sample and all of the members that it inherits
to support IDispatch. In this case, if Hello was declared earlier with lcid and retval
members that returned HRESULTs, MkTypLib would remove each lcid parameter and
HRESULT return type, and instead mark the return type as that of the retval parameter.
The properties and methods of a dispinterface are not part of the VTBL of the dispinterface.
Consequently, CreateStdDispatch and DispInvoke cannot be used to implement
IDispatch::Invoke. The dispinterface is used when an application needs to expose existing
non-VTBL functions through Automation. These applications can implement
IDispatch::Invoke by examining the dispidmember parameter and directly calling the
corresponding function.
Example
[
uuid(BFB73347-822A-1068-8849-00DD011087E8), version(1.0),
helpstring("Useful help string."), helpcontext(2480)
]
dispinterface MyDispatchObject
{
properties:
[id(1)] int x; // An integer property named x.
[id(2)] BSTR y; // A string property named y.
methods:
[id(3)] void show; // No arguments, no result.
[id(11)] int computeit(int inarg, double *outarg);
};
[uuid 00000000-0000-0000-0000-123456789012]
dispinterface MyObject
{
properties:
methods:
[id(1), propget, bindable, defaultbind, displaybind]
long x();
typedef
[
uuid(DEADFOOD-CODE-BIFF-F001-A100FF001ED),
helpstring("Farm Animals are friendly"),
helpcontext(234)
]
enum
{
[helpstring("Moo")] cows = 1,
pigs = 2
} ANIMALS;
interface
This statement defines an interface, which is a set of function definitions. An interface can inherit from
any base interface.
Syntax
The type can be any previously declared type, built-in type, a pointer to any type, or a pointer to a
built-in type. Attributes on parameters are in, out, optional, and string.
If optional is used, it must be specified only on the right-most parameters, and the types of those
parameters must be VARIANT.
Comments
Because the functions described by the interface statement are in the VTBL, DispInvoke
and CreateStdDispatch can be used to provide an implementation of
IDispatch::Invoke. For this reason, interface is more commonly used than
dispinterface to describe the properties and methods of an object.
Functions in interfaces are the same as described in the “module” statement except that the entry
attribute is not allowed.
Members of interfaces that need to raise exceptions should return an HRESULT and specify a
retval parameter for the actual return value. The retval parameter is always the last parameter
in the list.
Examples
The following example defines an interface named Hello with two member functions, Helloproc
and Shutdown:
[uuid(BFB73347-822A-1068-8849-00DD011087E8),
version(1.0)]
interface hello : IUnknown
{
void HelloProc([in, string] unsigned char * pszString);
void Shutdown(void);
};
The next example defines a dual interface named IMyInt, which has a pair of accessor functions
for the MyMessage property, and a method that returns a string.
[dual]
interface IMyInt : Idispatch
{
// A property that is a string.
[propget]
HRESULT MyMessage([in, lcid] LCID lcid, [out, retval]
BSTR *pbstrRetVal);
[propput]
HRESULT MyMessage([in] BSTR rhs, [in, lcid] DWORD
lcid);
The members of this interface return error information and function return values through the
HRESULT values and retval parameters, respectively. Tools that access the members can return
the HRESULT to their users, or can simply expose the retval parameter as the return value, and
handle the HRESULT transparently. A dual interface must derive from IDispatch.
library
This statement describes a type library. This description contains all of the information in a MkTypLib
input file (ODL).
Syntax
[
uuid(F37C8060-4AD5-101B-B826-00DD01103DE1), // LIBID_Hello.
helpstring("Hello 2.0 Type Library"),
lcid(0x0409),
version(2.0)
]
library Hello
{
importlib("stdole.tlb");
[
uuid(F37C8062-4AD5-101B-B826-00DD01103DE1), // IID_Ihello.
helpstring("Application object for the Hello application."),
oleautomation,
dual
]
interface IHello : Idispatch
{
[propget, helpstring("Returns the application of the
object.")]
HRESULT Application([in, lcid] long localeID,
[out, retval] IHello** retval);
}
}
module
This statement defines a group of functions, typically a set of DLL entry points.
Syntax
The type can be any previously declared type or built-in type, a pointer to any type, or a pointer to a
built-in type. Attributes on parameters are in, out, and optional.
If optional is specified, it must only be specified on the right-most parameters, and the types of
those parameters must be VARIANT.
Comments
The header file (.h) output for modules is a series of function prototypes. The module keyword
and surrounding brackets are stripped from the header file output, but a comment (\\ module
modulename) is inserted before the prototypes. The keyword extern is inserted before the
declarations.
Example
[
uuid(DOOBEDOO-CEDE-B1FF-F001-A100FF001ED),
helpstring("This is not GDI.EXE"), helpcontext(190),
dllname("MATH.DLL")
]
module somemodule
{
[helpstring("Color for the frame")]
unsigned long const COLOR_FRAME = 0xH80000006;
[helpstring("Not a rectangle but a square"), entry(1)]
pascal double square([in] double x);
};
struct
This statement defines a C-style structure.
Syntax
typedef [attributes]
struct [tag]
{
memberlist
} structname;
Syntax Elements
attributes
The attributes helpstring, helpcontext, uuid, hidden, and version are accepted
before a struct statement. The attributes helpstring, helpcontext, and string are
accepted on a structure member. For more information about the attributes accepted before a
structure definition, see “Attribute Descriptions” earlier in this appendix. Attributes (including the
brackets) can be omitted. If uuid is omitted, the structure is not specified uniquely in the system.
Tag
An optional tag, as with a C struct.
memberlist
List of structure members defined with C syntax.
structname
Name by which the structure is known in the type library.
Comments
The struct keyword must be preceded with a typedef. The structure description must precede
other references to the structure in the library. Members of a struct can be of any built-in type, or
any type defined lexically as a typedef before the struct. For a description of how strings and
arrays can be entered, see the sections “String Definitions” and “Array Definitions” earlier in this
appendix.
Example
typedef
[
uuid(BFB7334B-822A-1068-8849-00DD011087E8),
helpstring("A task"), helpcontext(1019)
]
struct
{
DATE startdate;
DATE enddate;
BSTR ownername;
SAFEARRAY (int) subtasks;
int A_C_array[10];
} TASKS;
typedef
This statement creates an alias for a type.
Syntax
This example creates a type description for an alias type with the name DWORD.
typedef enum
{
TYPE_FUNCTION = 0,
TYPE_PROPERTY = 1,
TYPE_CONSTANT = 2,
TYPE_PARAMETER = 3
} OBJTYPE;
The second example creates a type description for an enumeration named OBJTYPE, which has
four enumerator values.
union
This statement defines a C-style union.
Syntax
[
uuid(BFB7334C-822A-1068-8849-00DD011087E8),
helpstring("A task"), helpcontext(1019)
]
typedef union
{
COLOR polycolor;
int cVertices;
boolean filled;
SAFEARRAY (int) subtasks;
} UNIONSHOP;
Directives Description
importlib
This directive makes types that have already been compiled into another type library available to the
library currently being created. All importlib directives must precede the other type descriptions in the
library.
Syntax
importlib(filename);
Syntax Element
filename
The location of the type library file when MkTypLib is executed.
Comments
The importlib directive makes any type defined in the imported library accessible from within
the library being compiled. Ambiguity is resolved as the current library is searched for the type. If
the type cannot be found, MkTypLib searches the imported library that is lexically first, and then
the next, and so on. To import a type name in code, the name should be entered as
libname.typename, where libname is the library name as it appeared in the library statement
when the library was compiled.
The imported type library should be distributed with the library being compiled.
Example
The following example imports the libraries Stdole.tlb and Mydisp.tlb:
library BrowseHelper
{
importlib("stdole.tlb");
importlib("mydisp.tlb");
// Additional text omitted.
}
Table of Contents
[an error occurred while processing this directive]
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Table of Contents
-----------
Index
A
Account List listbox, 238, 257, 258
Account object
implementing
GetProperties and SetProperties method, 280-283
Item method, 283-286
object properties of, 191-192
AccountInfo COM object
interfaces and properties of, 114
object properties of, 114
AccountInfo.idl file, 115-116
AccountInfoAuto object
building with AccountInfoAutoVTBL application, 153-154
object properties of, 139, 154
properties of, 139, 154
releasing, 162-163
AccountInfoAutoDisp.cpp file, 175-184
AccountInfoAutoVTBL application, 153-170
initializing the COM library, 155
manipulating the COM object, 156-162
obtaining an initial interface, 155-156
releasing the COM object, 162-163
uninitializing the COM library, 163-170
AccountInfoForm file, 290-291
AccountInfoAutoDisp client
retrieving property values using IDispatch function, 175-185
setting property values using IDispatch interface pointer, 171-174
accounts
adding, 234-237
deactivating with Remove method, 213
removing, 241
retrieving, 237-239
updating, 239-241
Accounts object
functionality of, 195-197
initialization of, 205-208
Item property source code for, 218-222
source code
for Add method, 210-213
for BOF and EOF properties and navigation methods for,
225-229
for Remove method and Count property, 214
activation security, 268
Active Server Pages (ASP), 249-250
ActiveX, 4
Add method for Accounts object, 210-213
AddRef function, 8-10
aggregation, 111-123
containment vs., 111
delegating
inner objects to outer objects, 113
unknown member variable, 118
exposing interfaces of inner object, 112
interface pointers
releasing with nondelegating unknown, 118-122
storing in controlling unknown, 117
interfaces and properties of AccountInfo object, 114
restrictions of, 122-123
APIs (Application Programming Interfaces)
with BSTRS, 134
HRESULT data type, 14-15, 16-17
manipulating system registry with WIN32, 18-19
with SAFEARRAYs, 137-138
with variants, 133
applications
AccountInfoAutoVTBL, 153-170
client/server, 231-245
improving with DCOMCNFG, 279-286
VBScript for improving Web order-entry, 286-289
Web order-entry, 247-265
Applications tab (Distributed COM Configuration Properties dialog
box), 270, 274
architecture
for client/server applications, 233-234
limitations of, 244
order-entry systems, 235
combining DCOM with existing, 292
of Web applications
limitations of, 264
understanding, 248-250
arguments
for SQLBindCol function, 215-216
for SQLBindParameter function, 208-209
ASP (Active Server Pages), 249-250, 286-287
authentication levels supported by DCOM, 268-269
Automation
about, 125
intrinsic IDL datatypes supported by, 129-130
process of, 126-128
Automation controllers, 153-185
building the AccountInfoAutoDisp client, 170-185
retrieving property values using IDispatch function,
175-185
setting property values using IDispatch interface pointer,
171-174
building the AccountInfoAutoVTBL application, 153-170
initializing the COM library, 155
manipulating the COM object, 156-162
obtaining an initial interface, 155-156
releasing the COM object, 162-163
uninitializing the COM library, 163-170
Automation objects, 125-151
character sets, 135-136
dual interfaces, 128-130
exposing a type library, 143-144
implementing IDispatch interface, 145-149
isolating Automation specifics, 138-143
registering, 149-151
understanding
automation, 125
BSTRs, 133-134
IDispatch interface, 126-128
SAFEARRAYs, 136-138
variants, 130-133
B
binding. See VTBL binding
BSTRs (binary strings), 133-134
C
call security, 268
character sets, 135-136
class factories
exposing
for in-process servers, 37-39
for out-of-process servers, 74-77
implementing
for in-process servers, 34-35
for out-of-process servers, 72-73
registering, 74
click event for InvoiceInfoForm's OK button, 243-244
client/server applications, 231-245
about the order-entry application, 232-233
developing, 234-244
adding accounts, 234-237
adding and updating invoices, 241-244
removing accounts, 241
retrieving accounts, 237-239
updating accounts, 239-241
improving with DCOMCNFG, 279-286
limitations of, 244
object hierarchies and, 231-232
understanding architecture for, 233-234
See also Web order-entry applications
CLSIDs (Class Identifiers)
allocating for out-of-process servers, 66
COM objects and, 5-6
mapping to COM server filenames, 18
collection objects, 202-229
about ODBC programming, 203-205
data access and manipulation properties and methods, 205-222
navigation properties and methods, 222-229
for OrderEntry object hierarchy, 190
columns
of Accounts database table, 192
of Invoice database table, 194
of LineItem database table, 195
of Products database table, 193
COM (Component Object Model), 3-23
as programming model, 3-14
COM objects, 5-6
COM servers, 11-14
DCOM as extension of, 267
interfaces, 6-7
managing COM objects, 8-10
navigating interfaces, 7-8
object versioning and evolution, 10-11
objectives, 3-5
system services
APIs, 14-17
system registry, 17-20
VTBL design and transparent LPC and RPC mechanism,
20-23
COM client
Automation controllers and, 125
initializing the COM library, 55
manipulating COM objects, 56
obtaining an initial interface, 55-56
releasing COM objects, 56
testing in-process servers with, 54-60
uninitializing the COM library, 57-60
COM library
composition of, 14
initializing for AccountInfoAutoVTBL application, 155
uninitializing for the AccountInfoAutoVTBL application,
163-170
COM objects, 5-6, 97-123
Automation objects and, 125
CLSIDs and GUIDs, 5
defining interfaces for, 27-31, 66-70
encapsulating server packaging code, 40-42
instantiation process of, 37-39
interfaces and, 6-7
managing, 8-10
manipulating with AccountInfo Dispatch interface, 156-162
object hierarchies, 189-230
object properties for UserInfo, 64
object versioning and evolution, 10-11
registering
Automation objects, 149-151
class information for, 35-37
releasing in AccountInfoAutoVTBL application, 162-163
See also reusing COM objects
COM servers, 11-14
configuring security for, 274
defined, 4
out-of-process servers as, 63-64
See also in-process servers
Component Object Model. See COM
containment, 97-111
aggregation vs., 111
creating inner objects, 100-101
implementing object properties, 101-102
releasing objects, 102
controlling unknown member variable, 117
Count property
deactivating accounts with, 213
source code for, 214
creating inner objects, 100-101
D
data manipulation methods, Add method, 207-208
datatypes
Automation support for intrinsic IDL, 129-130
SAFEARRAY, 136-138
SQLBindParameter function support for C, 209-210
SQLBindParameter function support for SQL, 210
supported by IDL, 29-30
DBCS (double-byte character sets), 135
DCOM (Distributed Component Object Model), 267-292
as extension of COM programming model, 267
security, 268-270
using DCOMCNFG, 270-279
improving the client/server order-entry application,
279-286
improving the Web order-entry application, 286-292
providing object-specific configuration information,
273-279
providing system-wide configuration information, 270-272
DCOM impersonation levels, 269
DCOMCNFG, 270-279
application-specific tabs for, 274, 275, 286
improving applications
for client/server order-entry, 279-286
for Web order-entry, 286-292
providing configuration information
object-specific, 273-279
system-wide, 270-272
user interface for, 270
DCOM authentication levels, 268-269
DeactivateAccountLogic.asp file, 263-264
Default Properties tab (Distributed COM Configuration Properties
dialog box), 272
Default Security tab (Distributed COM Configuration Properties dialog
box), 272
defining
dual interfaces, 139-142
object hierarchies, 190-201
delegating
inner objects to outer objects, 113
unknown member variable, 118
developing
client/server applications, 234-244
Web order-entry applications, 251-264
DISPARAMS structure, 172
DISPID (dispatch identifier), 171
DisplayAccountInfoDispatch function, 158-152
Distributed COM Configuration Properties
dialog box, 270
Distributed Component Object Model. See DCOM
DllMain.cpp file, 43, 44-48
DLLs, 11, 12
double-byte character sets (DBCS), 135
dual interfaces, 128-130, 139-142
custom interfaces and, 170
handling with AccountInfoAutoDisp client, 170-185
setting IAccountInfoDispatch, 157
See also interfaces
E
early binding, 127
encapsulating server packaging code, 40-42
entry objects
building, 201-202
for OrderEntry object hierarchy, 190
EXEMain.cpp file, 80-85
EXEs, 12, 13
exposing
class factories
for in-process servers, 37-39
for out-of-process servers, 74-77
interfaces of inner object, 112
in aggregation, 112
a type library, 143-144
F
facility codes, 15-16
functionality
of Accounts object, 195-197
of Invoices object, 198-200
of LineItems object, 200-201
of Products object, 197-198
G
global.asa file, 250
GUIDs (Globally Unique Identifiers)
allocating for in-process servers, 26-27
COM objects and, 5
H
HKEY_CLASSES_ROOT\APPID\ registry key, 273
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registry key,
272
HRESULT data type
internal structure of, 14-15
macros, 17
return value constants for, 16-17
HTML (HyperText Markup Language)
generating user messages with HTML pages, 260
in Web applications, 248-249
HTTP (HyperText Transfer Protocol), 248
I
IAccountInfoDispatch interface, 142-143
IDispatch::Invoke function
defined context constants for, 174
reading property values with, 175-185
updating property values before calling, 73
IDispatch interface
about, 126-128
ASP applications, 249-250
implementing, 145-149
member functions, 126
IDL (Interface Definition Language)
about, 27-31
of IAccountInfoDispatch interface, 139-142
intrinsic data types
supported by, 29-30
supported by Automation, 129-130
syntax for, 27
IID (interface identifier), 6-7
impersonation levels, 269
implementation locator service, 20
implementing
a class factory for out-of-process servers, 72-73
IDispatch interface, 145-149
interface methods for out-of-process servers, 70-72
object properties, 101-102
inner objects, 97
creating, 100-101
delegating to outer objects, 113
effects of aggregation on, 111-114
exposing interfaces of, 112
in-process servers, 25-61
about Interface Definition Language (IDL), 27-31
allocating GUIDs for, 26-27
building UserInfo, 25-26
exposing the class factory, 37-39
implementing
a class factory, 34-35
interface functions, 32-34
registering class information, 35-37
server unloading, 39-54
testing with COM client, 54-60
initializing the COM library, 55
manipulating COM objects, 56
obtaining an initial interface, 55-56
releasing COM objects, 56
uninitializing the COM library, 57-60
See also COM server
Interface Definition Language. See IDL
interface identifier (IID), 6-7
interface navigation, 7-8
interface pointers
releasing with nondelegating unknown, 118-122
setting property values with IDispatch, 171-174
storing in controlling unknown, 117
interfaces
of AccountInfo COM object, 114
of AccountInfo object, 114
for AccountInfoAutoVTBL application, 155-156
COM objects and, 6-7
custom and dual, 170
defined, 4
dual, 128-130, 139-142
IDispatch, 126-128
implementing functions of, 32-34
for objects on out-of-process servers, 66-70
QueryInterface function and navigation, 7-8
user interfaces for Web clients, 251, 256, 257
as VTBL, 20-21
See also dual interfaces
internal structure
of HRESULT data type, 14-15
of variants, 130-132
interoperability, 4
InvoiceInfoForm click event for OK button, 243-244
invoices, 241-244
Invoices object
functionality of, 198-200
object properties of, 193-194
isolating Automation specifics, 138-143
Item method
retrieving information with, 217-218
source code for, 218-222
L
late binding, 127
limitations
of aggregation, 122-123
of client/server application architecture, 244
of Web application architecture, 264
Line Item Information dialog box, 242
LineItems object
functionality of, 200-201
object properties of, 194-195
ListAccounts.asp file, 255
LoadTypeInfo function
functions of, 145-146
listing for, 146-147
local objects, 12
local servers, 12
LPC (local procedure calls), 21-22
M
macros for HRESULT, 17
manipulating
Account and Account objects with NewAccount.htm file, 289
collection objects, 205-222
COM objects with in-process servers, 56
system registry with WIN32 APIs, 18-19
marshaling process
for out-of-process servers, 77-96
proxies and, 21
MemberInfo COM object, 98
Memberinfo.cpp file, 104-111
Memberinfo.h file, 102-104
Memberinfo.idl file, 98-102
menus for order-entry application, 233
methods
data access and manipulation properties and, 205-222
implementing interfaces for out-of-process servers, 70-72
navigation properties and, 222-229
See also methods listed individually
Microsoft Remote Procedure Call (MS-RPC) system, 267
Modify Account dialog box
retrieving account information with, 237, 257
updating accounts with, 239-241
Modify Invoice dialog box, 241-242
ModifyAccount.asp file
source code for, 261
user interface for, 259
user messages with, 260
ModifyAccount.asp page, 291
Move navigation method, 223-229
MoveFirst navigation method, 222, 224-229
MoveLast navigation method, 223-229
MoveNext navigation method, 223-229
MovePrev navigation method, 223-229
MS-RPC (Microsoft Remote Procedure Call) system, 267
N
navigation
interface, 7-8
properties and methods for Accounts object, 222-229
network round trips, 274, 276-277
New Account window, 236, 253
NewAccount.htm file
manipulating Account and Account objects with, 289
SaveAccountLogic.asp file and, 288
NewAccount.htm page, 289-290
nondelegating unknown member variable, 118-122
O
object hierarchies, 189-230
building collection objects, 202-229
about ODBC programming, 203-205
data access and manipulation properties and methods,
205-222
navigation properties and methods, 222-229
building entry objects, 201-202
client/server applications and, 231-232
defining, 190-201
functionality
of Accounts object, 195-197
of Invoices object, 198-200
of LineItems object, 200-201
of Products object, 197-198
properties
of Account object, 191-192
of Invoice object, 193-194
of LineItem object, 194-195
of Product object, 192-193
object outline for UserInfoHandler COM object, 65
object properties
of Account object, 191-192
of AccountInfo COM object, 114
of AccountInfoAuto, 139, 154
for collection objects, 205-222
implementing, 101-102
of Invoice object, 193-194
of LineItem object, 194-195
for MemberInfo COM object, 98
of Product object, 192-193
for UserInfo COM object, 64
object versioning and evolution, 10-11
object-specific configuration information, 273-279
obtaining DISPID, 171
ODBC (Open Database Connectivity) API calls, 203-205
OLE, 4
order-entry applications, 234-244
client/server
about, 232-233
adding accounts, 234-237
adding and updating invoices, 241-244
menus for, 233
removing accounts, 241
retrieving accounts, 237-239
updating accounts, 239-241
developing Web, 251-264
adding accounts, 251-255
identifying existing accounts, 255-258
removing accounts, 263-264
updating accounts, 258-263
improving with DCOMCNFG
for client-server, 279-286
for Web applications, 286-292
See also Web order-entry applications
OrderEntry object hierarchy, 190
outer objects, 97
delegating to inner object, 113
exposing interfaces of inner object, 112
out-of-process servers, 63-96
allocating CLSIDs, 66
defined, 63-64
defining object's interfaces, 66-70
exposing the class factory, 74-77
implementing
a class factory, 72-73
interface methods, 70-72
marshaling process for, 77-96
registering class information, 73-74
unloading, 77
UserInfoHandler server, 64-65
overview
of Automation, 125
of BSTRs, 133-134
of character sets, 135-136
of client/server application architecture, 233-234
of IDispatch interface, 126-128
of ODBC programming, 203-205
of SAFEARRAYs, 136-138
variants, 130-133
P
Products object
functionality of, 197-198
object properties of, 192-193
property values
retrieving using IDispatch function, 175-185
setting with IDispatch interface pointer, 171-174
See also object properties
proxy
creating for out-of-process servers, 77-80
as object substitute, 21
Q
QueryInterface function, 7-8
R
reference counting, 8
registering
Automation objects, 149-151
class factories, 74
class information, 35-37
class information for out-of-process servers, 73-74
See also system registry
Release function, 8-10
releasing
AccountInfoAuto object, 162-163
interface pointers with nondelegating unknown, 118-122
objects in containment, 102
remote objects, 12
remote servers, 12
Remove method
deactivating accounts with, 213
source code for, 214
return value constants, for HRESULTS, 16-17
reusing COM objects, 97-123
aggregation, 111-123
delegating inner objects to outer objects, 113
delegating unknown member variable, 118
exposing interfaces of inner object, 112
interfaces and properties of AccountInfo object, 114
releasing interface pointers with
nondelegating unknown, 118-122
restrictions of, 122-123
storing interface pointers in controlling
unknown, 117
containment, 97-111
creating inner objects, 100-101
implementing object properties, 101-102
releasing objects, 102
rgvarg array, arguments supplied to, 172
RPCs (remote procedure calls)
network round-trips and, 276, 277
VTBL design and, 20-22
S
SAFEARRAY datatype, 136-138
SaveAccountLogic.asp ASP page
NewAccount.htm and, 288
source code for, 254-255
security
configuring object-specific, 273-279
DCOM authentication levels, 268-269
single byte character sets (SBCS), 135
SQLBindCol function, 215-216
SQLBindParameter function
arguments accepted by, 208-209
C datatypes supported by, 209-210
SQL datatypes supported by, 210
stateless connections, 250
stub
creating for out-of-process servers, 77-80
using a, 21
syntax
for binary strings, 134
for IDL (Interface Definition Language), 27
system registry, 17-20
adding class information to, 35-37
as hierarchy of keys, 17-18
HKEY_CLASSES_ROOT\APPID\ registry key, 273
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole
registry key, 272
manipulating with WIN32 APIs, 18-19
registering Automation objects, 149-151
registering class information for out-of-process servers, 73-74
system-wide configuration information, 270-272
T
TCO (Total Cost of Ownership), 244
type libraries
defined, 27
exposing, 143-144
U
uninitializing
the COM library for the AccountInfoAutoVTBL application,
163-170
UserInfoClient.cpp file, 57-60
unloading
in-process servers, 39-54
out-of-process servers, 77
Update method, for Account object, 213
user interface
for DCOMCNFG, 270
for modifying Web accounts, 259
for Web clients, 251, 256, 257
UserInfo COM object, object properties for, 64
UserInfo.cpp file, 43, 49-54
UserInfo.def file, 43
UserInfo.h file, 43, 48-49
UserInfo in-process server, 25-26
UserInfoClient application, 54-60
UserInfoClient.cpp file, 57-60
UserInfoHandler.cpp file, 87-95
UserInfoHandler.h file, 85-86
UserInfoHandler.idl file, 66-70
UserInfoHandler object, 65
UserInfoHandler server, 64-96
V
variants
internal structure of, 130-132
interpreting vt values, 132
understanding, 130-133
WIN32 API functions for, 133
VBScript
improving Web order-entry application and, 286-289
source code for AccountInfoForm, 290-291
source code for ModifyAccount.asp page, 291
source code for NewAccount.htm page, 289-290
Visual Basic, 130-133
vt variant, 131-132
VTBL binding
Automation and, 127-128
defined, 125
VTBL (virtual function table) interfaces, 20-21, 128-130
W
Web browsers
user interface for, 256, 257
in Web applications, 248
Web order-entry applications, 247-265
developing, 251-264
adding accounts, 251-255
identifying existing accounts, 255-258
removing accounts, 263-264
updating accounts, 258-263
improving with DCOMCNFG, 286-292
Web applications
limitation of architecture, 264
understanding architecture of, 248-250
See also order-entry applications
weblications. See Web order-entry applications
WIN32 API functions
for SAFEARRAYs, 137-138
for variants, 133
for working with BSTRS, 134
Table of Contents
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is
prohibited. Read EarthWeb's privacy statement.