You are on page 1of 392

To access the contents, click the chapter and section titles.

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Preface

----------- About the Author

Part 1—Introduction to COM


Chapter 1—A COM Overview
COM: The Programming Model
COM Objects
Interfaces
COM Servers
COM: The System Services
COM’s APIs
COM’s Implementation Locator Service
COM’s Transparent LPC and RPC Mechanism
Summary

Chapter 2—Building In-Process Servers


The UserInfo Server
Allocating GUIDs
Defining Each Object’s Interfaces
Implementing Interface Functions
Implementing a Class Factory
Registering Class Information
Exposing the Class Factory
Server Unloading
The UserInfoClient Application
Initializing the COM Library
Obtaining an Initial Interface
Manipulating a COM Object
Releasing the COM Object
Uninitializing the COM Library
Summary

Chapter 3—Building Out-of-Process Servers


The UserInfoHandler Server
Allocating CLSIDs
Defining an Object’s Interfaces
Implementing Interface Methods
Implementing a Class Factory
Registering Class Information
Exposing the Class Factory
Server Unloading
Marshaling
Summary

Chapter 4—Reusing COM Objects


Understanding Containment
Understanding Aggregation
Summary

Chapter 5—Building Automation Objects


An Introduction to Automation
Understanding IDispatch
Understanding Dual Interfaces
Understanding Variants
Understanding BSTRs
Understanding SAFEARRAYs
Building an Automation Object
Isolating Automation Specifics
Exposing a Type Library
Implementing IDispatch
Registering an Automation Object
Summary
Chapter 6—Building Automation Controllers
Building the AccountInfoAutoVTBL Application
Initializing the COM Library
Obtaining an Initial Interface
Manipulating the COM Object
Releasing the COM Object
Uninitializing the COM Library
Building the AccountInfoAutoDisp Client
Setting Property Values Using IDispatch
Retrieving Property Values Using IDispatch
Summary

Part II—Building Componentized Applications


Chapter 7—Building Object Hierarchies
Defining an Object Hierarchy
The Account Object
The Product Object
The Invoice Object
The LineItem Object
The Accounts Object
The Products Object
The Invoices Object
The LineItems Object
Building an Object Hierarchy
Building the Entry Objects
Building the Collection Objects
Summary

Chapter 8—Building the Client/Server


Order-Entry Application
Understanding the Order-Entry Application
Understanding the Client/Server Application
Architecture
Developing the Client/Server Application
Adding New Accounts
Retrieving Existing Accounts
Updating Existing Accounts
Removing Existing Accounts
Adding and Updating Invoices
Limitations of the Client/Server Application
Architecture
Summary

Chapter 9—Building the Web Order-Entry


Application
Understanding the Web Application Architecture
Developing the Web Application
Adding New Accounts
Identifying Existing Accounts
Updating Existing Accounts
Removing Existing Accounts
Limitations of the Web Application Architecture
Summary

Chapter 10—Using DCOM


DCOM Security
Using DCOMCNFG
Providing System-Wide Configuration
Information
Providing Object-Specific Configuration
Information
Improving the Client/Server Order-Entry Application
Improving the Web Order-Entry Application
Summary

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.

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

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.

What You Need Before You Begin


As a bare minimum, you should have one computer that is equipped with an
operating system that supports DCOM, a C++ compiler that supports the
development of COM objects, Microsoft Internet Explorer 3.0.2, and Visual
Basic. Even though Windows NT 4.0 Workstation and Server have built-in
support for DCOM, you should install the latest service pack from the
Microsoft web site () DCOM support for Windows 95 is freely downloadable
from the Microsoft web site as well. Information regarding DCOM support for
other non-Windows operating systems can be found on the Microsoft web site,
the Active Group web site (http://www.activex.org), or on the web sites of
Microsoft partners like Software AG (http://www.sagus.com).

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.

What’s in This Book


For corporate application developers, building COM objects is just half the
story; the other half is actually using those COM objects to develop their final
application. DCOM: Microsoft® Distributed Component Object Model takes
exactly this approach. DCOM: Microsoft® Distributed Component Object
Model is divided into two parts. The first part begins with a conceptual
overview of what COM is and how COM works, by describing the various
pieces of COM and how they interact, and goes on to illustrate the
fundamentals of COM development. This is the “how to build COM objects”
part. The second part describes how to encapsulate application functionality
into a single tightly integrated group of COM objects called an object
hierarchy and ultimately describes how to use the object hierarchy to build
both client/server and web-based enterprise applications.

Part I: COM Fundamentals


In Part I, Chapters 1–6, you learn about the various pieces of the COM
infrastructure, everything from the System Registry to the Service Control
Manager (SCM). You learn how to build COM interfaces, COM objects, COM
servers, and COM clients. You also learn how to reuse existing COM objects
through aggregation and containment, COM’s mechanisms for implementation
inheritance. Finally, you learn how to implement COM objects that support
Automation and how to create Automation controllers that use them.

Part II: Building Enterprise Applications Using DCOM


In Part II, Chapters 7–10, you will increase your knowledge and understanding
of COM by creating a tightly integrated group of COM objects called an object
hierarchy. You then learn how to use this object hierarchy to develop an order
entry application using both the client/server and web-based application
architectures. Finally, you learn how DCOM can be used to improve both of
these application architectures.

How to Reach Me
If you have any questions, comments, or suggestions, you can reach me
through my CompuServe account at 76352.343@compuserve.com.

Table of Contents

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Table of Contents

-----------

About the Author


Frank E. Redmond III is a Software Design Engineer in Microsoft’s Developer
Relations Group (DRG), where he assists strategic Independent Software
Vendors (ISVs) by providing them with technical expertise on a wide range of
Microsoft COM-based technologies. Frank has authored several articles for
various trade journals including Microsoft Interactive Developer and Dr.
Dobb’s Journal, and frequently speaks at various corporations, trade shows,
and conferences all over the world.

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

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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

ALTHOUGH MICROSOFT’S COMPONENT Object Model (COM) has been


referred to as many things, it is essentially only two: a programming model
and a set of related system services. In this chapter, I describe COM, the
programming model, and how it relates to COM, the system services. By the
end of this chapter, you should have a clear understanding of COM
fundamentals and the advantages of adopting COM as the foundation for
developing your next generation of software applications.

COM: The Programming Model


The COM programming model is a client/server, object-based programming
model designed to promote software interoperability. The primary goal of
COM is to provide a means for client objects to make use of server objects,
despite the fact that the two may have been developed by different companies,
using different programming languages, at different times. In order to achieve
this level of interoperability, COM defines a binary standard, which specifies
how an object is laid out in memory at run time. By defining how an object is
laid out in memory, COM allows any language that is capable of reproducing
the required memory layout to create a COM object. We look at the memory
layout of a COM object later in this chapter.

A Word About Interoperability


There are many reasons why two or more applications may need to
interoperate, such as to exchange data or to programmatically control one
another. However, COM doesn’t define the underlying purpose for
applications to communicate. COM exists to provide a single standardized
way for two or more applications to interoperate regardless of the purpose
for their interaction. OLE and ActiveX are examples of two industry
specifications that define specific purposes for applications to interact. OLE
is a specification that describes how COM objects can be used to create and
manipulate compound documents. ActiveX is a specification that describes
how COM objects can be used on the Internet. In addition, COM has been
used as the foundation for many industry-specific initiatives in such vertical
market segments as retailing, banking, and insurance. For a complete list of
the industry-specific initiatives that rely on COM as their foundation, check
Microsoft’s Web site at www. microsoft.com.

While COM’s primary objective is to provide basic interoperability between


object clients and servers at a binary level, COM also has several other
objectives:
• Providing a solution to versioning and evolution problems
• Providing a system view of objects
• Providing a singular programming model
• Providing support for distributed capabilities
Before we investigate how COM accomplishes each of these objectives, let’s
finish our discussion of COM’s basic interoperability.
In COM, programming model, COM clients connect to one or more COM
objects, which are themselves contained in COM servers. Here, a client is any
piece of software that makes use of the services provided by a COM object.
Each COM object exposes its services through one or more interfaces, which
are essentially groupings of semantically related functions. The compiled
implementation of each COM object is contained within a binary module
(EXE or DLL) called a COM server. A single COM server is capable of
containing the compiled implementations of several different COM objects.
The COM programming model defines what a COM server must do to expose
COM objects, what a COM object must do to expose its services, and what a
COM client must do to use a COM object’s services. Part 1 of this book
focuses on these topics exclusively, and provides the background information
necessary to understand the material covered in Part 2. As a corporate
applications developer, I assume that ultimately you want to know how COM
will help you build better applications. Rather than tell you how, Part 2 of this
book shows you how. I illustrate how COM can be used to create traditional
client/server applications, as well as next-generation Web-based applications. I
also show you how COM’s distributed aspect (DCOM) can be used to enhance
these two popular application architectures.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
COM Objects

A COM object, like any other object, is a run-time instantiation of a particular


defining class. However, unlike most other objects, which are identified by a
human-readable name, COM objects are identified by a unique Class Identifier
(CLSID). CLSIDs are part of a special group of identifiers called Globally Unique
Identifiers, or GUIDs. GUIDs are 128-bit values that are statistically guaranteed
to be unique across time and space. The following illustrates the internal structure
of a GUID:

typedef struct GUID


{
DWORD Data1; //32-bits
WORD Data2; //16-bits
WORD Data3; //16-bits
BYTE Data4[8]; //64-bits
}GUID;
The internal structure members of a GUID are typically not accessed directly,
except for debugging purposes or when GUIDs are being transmitted between
machines with different byte orders.
To understand why it’s imperative that COM use CLSIDs to uniquely identify
object classes, consider the following scenario. Imagine that you’ve just
developed a COM object and identified it using a traditional human-readable
name, such as “MyObject.” You then ship your object in binary form to thousands
of anxious developers who quickly install your component. If one of these
developers already has an object named “MyObject” installed on his or her
system, there is no way to resolve the naming conflict because both objects are in
binary form. Therefore, to prevent this type of naming conflict, COM uses
CLSIDs to uniquely identify each individual object class. Instead of having a
central authority that is responsible for issuing GUIDs, COM provides the
CoCreateGuid API, which is used by various GUID generation tools, such as
GUIDGEN.EXE and UUIDGEN.EXE, which both ship as part of Microsoft
Visual C++. Internally, CoCreateGuid calls the RPC function UuidCreate
to generate a 128-bit, globally unique identifier, which can be used as a CLSID.
While CLSIDs are great for uniquely identifying object classes, they are not very
developer-friendly. To make dealing with CLSIDs more developer-friendly and
more like traditional object-based development, you can assign them to traditional
human-readable names for use throughout your applications:

//{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

A COM object is defined in terms of the individual interfaces that it supports.


Conceptually, an interface is simply a group of semantically related functions.
Figure 1-1 shows an example object, the UserInfoHandler COM object, with
three interfaces: ICopyInfo, IReverseInfo, and ISwapInfo (the “I”
stands for interface, of course). Each interface contains four functions:
ICopyInfo contains CopyName, CopyAge, CopySex, and CopyAll;
IReverseInfo contains ReverseName, ReverseAge, ReverseSex, and
ReverseAll; and ISwapInfo contains SwapName, SwapAge, SwapSex,
and SwapAll.

Figure 1-1 The ICopyInfo, IReverseInfo, and ISwapInfo interfaces of the


UserInfoHandler COM object
Each interface is identified by a unique identifier called an interface identifier
(IID), similar to the way in which each COM object is identified by a unique
CLSID. Like CLSIDs, IIDs are also GUIDs, which means that they are created
like any other GUID using the COM API CoCreateGuid or some GUID
generation tool such as GUIDGEN.EXE or UUIDGEN.EXE. Again, the IID
information is typically included as part of the same header file that includes the
object’s CLSID, which is redistributed — along with the object itself — for
consumption by other developers. When developing COM objects and interfaces,
you will need to assign each IID a human-readable name, just as you would if you
were working with CLSIDs:

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

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//pIReverseInfo is a pointer to the IReverseInfo interface


hr = pIReverseInfo->QueryInterface(IID_ISwapInfo, &pISwapInfo);
if (SUCCEEDED(hr))
{
//pISwapInfo points to the ISwapInfo interface
}
else
{
//Error - pISwapInfo contains a NULL value
}
Because every interface must implement QueryInterface, you are guaranteed the ability
to navigate from one interface on an object to any other interface on that same object.
However, if the object doesn’t implement a particular interface, you will never be able to
obtain a pointer to it, and QueryInterface will always fail when asked to retrieve that
particular interface.
Three things essentially define an interface:
• The number of supported functions
• The function prototypes of each supported function
• The order in which the function prototypes are listed
Changing any of these things effectively changes the interface, and because interfaces are the
only way to manipulate a COM object, once an interface is exposed for client usage, it must
never change. In other words, interfaces are immutable. The logic behind this is simple.
Suppose, that as a client of the UserInfoHandler COM object, my program relies on the
CopyName and CopyAge functions of the ICopyInfo interface. If the definition of the
ICopyInfo interface or any of its functions is altered or removed, my application will cease
working properly. Therefore, to preserve client compatibility, COM stipulates that an
interface must never change.
Interface navigation is not the only critical function that must be supported by every interface.
Every COM interface must also support the AddRef and Release functions (see “Lifetime
Management” below). Together, QueryInterface, AddRef, and Release define
COM’s most fundamental interface, IUnknown. Because each interface must support these
three fundamental functions, every interface must inherit from IUnknown.

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:

class CSomeObject : IUnknown


{
private:
ULONG m_cRef; //Reference counting variable
.
.//other member variables
.
};
When a COM object is first created, its internal counter variable is set to zero. Whenever the
COM object issues an interface pointer — as a result of a QueryInterface call, for
example — it is the COM object’s responsibility to call AddRef on that interface:

HRESULT CSomeObject::QueryInterface(REFIID iid,


LPVOID *ppv)
{
*ppv = NULL;
if (IID_Iunknown == iid)
*ppv = (LPVOID)(IUnknown *)this;
else if (…)
.
.//check for other supported interfaces
.
else
return E_NOINTERFACE; //interface not supported
//AddRef through the returned interface to accommodate
//per interface ref counting
((IUnknown *)*ppv)->AddRef(); return NOERROR;
}
AddRef serves to increment the value of the internal counter variable by one:

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:

pIX->Release(); //decrement reference count


The Release method serves to decrement the object’s internal counter variable by one.
When the internal counter variable reaches zero, it is the responsibility of the COM object to
destroy itself:

ULONG CSomeObject::Release(void)
{
m_cRef-;
if (0 == m-cRef)
{
delete this;
.
. //other object destruction code
.
return 0;
}
return m_cRef;
}

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Figure 1-2 By adding a new IDictionary2 interface, version 2 of the


ReferenceMaterials COM object is able to provide increased
functionality to version 2 clients and still provide support for legacy version 1
clients.
While an interface defines the function prototypes for each of its supported
functions, the implementation of each function is left totally to the developer’s
discretion. Each function must be implemented, but the implementation
specifics are fully encapsulated within the COM object. This, plus the fact that
interfaces are immutable, allows COM objects to change implementation
specifics without breaking existing clients. Consider the following example.
Suppose that version 1 of the ReferenceMaterials COM object from the
previous example maintains its list of known words in memory. Based on
customer feedback, you later decide that the memory requirements are too
demanding, and you therefore decide to maintain the list of known words on
disk in version 2. Even though you’ve changed the implementation of the
CheckWord function, you haven’t changed the interface, and existing client
applications are none the wiser! By separating definition from implementation,
COM allows object developers to expose proprietary implementations in an
open and standard way, which is important for protecting intellectual property
and sensitive corporate information.

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

Figure 1-5 When an in-process server is used remotely, COM automatically


creates a surrogate process and loads the in-process server into its address
space.
However, the benefit of resource ownership is not without its drawbacks —
one of which, as you may have guessed, is reduced speed. Whenever a client
accesses code or resources located within the out-of-process server, the
operating system is forced to perform a context switch, and you must pay a
performance penalty. On the other hand, accessing code or resources located
within an in-process server is extremely fast. However, in-process servers are
incapable of owning their own resources. Clearly, there are benefits and
drawbacks to both in-process and out-of-process servers. Ultimately, the type
of COM server you create will depend on the overall architecture of your
application.

COM: The System Services


In order for an operating system to support the COM programming model, it
must include a set of COM system services, commonly referred to as the COM
Library. The COM Library is composed of three essential items:
• A set of APIs necessary for accessing COM’s services
• The implementation locator service used to locate and start COM
servers
• Transparent Local Procedure Calls (LPCs) and Remote Procedure
Calls (RPCs) when objects are running in local or remote servers
Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Field Name Bit Positions Description

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.

Table 1-2 Currently Defined Facility Codes

Facility Name Value Description

FACILITY_NULL 0 Used for broadly applicable error codes


FACILITY_RPC 1 Used to report errors that result from an
underlying Remote Procedure Call (RPC)
FACILITY_DISPATCH 2 Used to report IDispatch-interface-related
status codes
FACILITY_STORAGE 3 Used to report status codes that relate to
persistent storage
FACILITY_ITF 4 Used to report an error from an interface
member function
FACILITY_WIN32 7 Used to map an error code from a Win32
API function onto an HRESULT
FACILITY_WINDOWS 8 Used to report error codes from
Microsoft-defined interfaces
Used to report error codes that relate to
FACILITY_SSPI 9
security
Used to report OLE-Control-related error
FACILITY_CONTROL 10
codes
FACILITY_CERT 11 Used to report error codes that relate to
public key certificates and Authenticode
FACILITY_INTERNET 12 Used to report error codes that relate to
Internet APIs
FACILITY_MSMQ 14 Used to report error codes that relate to
Microsoft Message Que
FACILITY_SETUPAPI 15 Used to report error codes that relate to
the Win32 setup APIs

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

S_OK Function completed and the result is TRUE


S_FALSE Function completed and the result is FALSE
NOERROR Function completed with no return value
E_UNEXPECTED An unexpected error has occurred
E_INVALIDARG One of the user-supplied arguments is invalid
E_OUTOFMEMORY Sufficient memory could not be allocated
E_NOINTERFACE The requested interface is not supported

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

SUCCEEDED(HRESULT status) If the severity field of the HRESULT is


0, returns TRUE; otherwise, returns
FALSE
FAILED(HRESULT status) If the severity field of the HRESULT is
1, returns TRUE; otherwise, returns
FALSE
Returns the error code field of the
HRESULT_CODE(HRESULT hr)
HRESULT
HRESULT_FACILITY(HRESULT Returns the facility field of the
hr) HRESULT
HRESULT_SEVERITY(HRESULT Returns the severity field of the
hr) HRESULT
HRESULT MAKE_HRESULT Creates a new HRESULT given a
(SEVERITY sev, FACILITY fac, severity, a facility, and a status code
CODE code)

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.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
COM’s Implementation Locator Service

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. The system registry is composed of a hierarchy
of keys. Each key may have an associated value and may also define other
subordinate keys, or subkeys (see Figure 1-6).

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
COM’s Transparent LPC and RPC Mechanism

As a developer, you are free to implement a COM object in either an


in-process server or out-of-process server. For that matter, Distributed COM
(DCOM) allows you to choose between local or remote servers. However,
because of process and machine boundaries, there are fundamental differences
between accessing code in a DLL, accessing code in a separate EXE, and
accessing code on an entirely different machine. Rather than burden the client
application developer with the headache of three different programming
models depending on where a particular COM object is located, COM
provides a single programming model for accessing COM objects regardless of
where they are located. COM’s single programming model provides location
transparency to the client: the client has no idea where a particular COM
object is actually running. This is not to say that the client has no control over
where a particular COM object runs, for as I mentioned earlier, by using
CoCreateInstance, a client can specify the context in which a particular
COM object should run.
The secret to COM’s singular programming model lies in the interface. As you
already know, conceptually, an interface is a group of semantically related
functions. Architecturally, an interface is a pointer to a virtual function table,
known as a VTBL. This VTBL contains pointers to functions that provide the
actual implementation defined by the interface. As Figure 1-7 shows, when
you obtain an interface pointer, you are actually receiving a pointer to a pointer
that is pointing to a VTBL of function pointers! (Try saying that very fast three
times!)
Figure 1-7 An interface is actually a pointer to a VTBL of function pointers.
While very subtle in its design, this level of indirection is all that is required
for COM to transparently provide you with a single location-independent
programming model. When a server is in-process, the pointers in the VTBL
point directly to methods that are also located in the same process space. Since
the method implementations are in the same process space as the client, very
little overhead is associated with invoking the method through multiple-pointer
indirection. However, pointers are only able to access information within a
single process space. So when a server is out-of-process, client interface
pointers are not allowed to access information in the server’s process space. To
solve this problem, COM relies on a special piece of in-process software called
a proxy. When you receive an interface pointer to an out-of-process object, you
are actually receiving a pointer to a proxy. The proxy exists to take the place of
the object and to forward any client requests to another special piece of
software called a stub. Since the proxy is in-process, the client’s interface
pointer can access it. To the client, the proxy is the object. The proxy is also
responsible for packaging any parameters that are needed to invoke a
particular method, a process known as marshaling.
Like the proxy, the stub is also in-process; however, the stub is located in the
server’s process space. The stub receives requests from the proxy and
unmarshals any parameters before actually invoking the method of the
interface. To the object, the stub is the client. The object (the COM server)
passes any return data to the stub, which forwards it to the proxy, which passes
it to the client. All of this takes place behind the scenes, and the client and
server are none the wiser.
When the client is accessing a local server, and the proxy and stub are located
on the same machine, they communicate via Local Procedure Calls (LPCs).
An LPC is a form of interprocess communication specifically designed for one
process to invoke the methods of a different process. LPCs work fine as long
as the proxy and stub are located on the same computer. When the proxy and
stub are located on different computers, they communicate via Remote
Procedure Calls (RPCs). Like LPCs, RPCs are also a form of interprocess
communication; however, RPCs are designed to allow a process on one
machine to invoke the methods of a process located on a different machine.
This distributed aspect of COM is the basis of DCOM. COM’s use of LPCs
and RPCs is diagrammed in Figure 1-8.

Figure 1-8 Clients of local out-of-process objects actually communicate with


an in-process proxy, which communicates with a stub loaded into the address
space of the object. The proxy then communicates with the stub via Local
Procedure Calls (LPCs). Clients of remote objects, either in-process or
out-of-process, communicate with an in-process proxy, which communicates
with a remote stub via Remote Procedure Calls (RPCs).

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.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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.

The UserInfo Server


While COM servers are capable of supporting multiple COM objects, our
example UserInfo in-process server only supports one, the UserInfo
COM object. The UserInfo COM object is used to maintain information
about an individual person. Each piece of information that is maintained is
exposed as a property of the UserInfo COM object (see Table 2-1).
Table 2-1 UserInfo Object Properties

Property Name Data Type

Age short
Name LPSTR
Sex unsigned char

While the UserInfo server is admittedly simple, its purpose is to


demonstrate the fundamental elements that every COM server is responsible
for implementing. The UserInfo server also demonstrates what a typical
in-process server must do to fulfill these responsibilities. Every COM server is
responsible for the following:
• 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
• Registering class information for each supported object
• Exposing a class factory for each supported object
• Unloading (destroying) itself when appropriate

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:

C:>uuidgen -n3 -oguids.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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Data Type Description

boolean Data item with either a TRUE or FALSE value


char 8-bit, signed data item
double 64-bit IEEE floating-point number
int System-dependent signed integer
float 32-bit IEEE floating-point number
long 32-bit signed integer
short 16-bit signed integer
wchar_t Unicode character accepted only for 32-bit type libraries
BSTR Length-prefixed string
CURRENCY 8-byte, fixed-point number
DATE 64-bit, floating-point fractional number of days since
December 30, 1899
DECIMAL 98-bit, unsigned binary integer scaled by a power of 10.
Provides size and scale for a number (as in coordinates).
SCODE Built-in error type that corresponds to VT_ERROR. An
SCODE (used on 16-bit systems only) does not contain
the additional error information provided by an
HRESULT.
VARIANT One of the variant data types described in Chapter 5
IDispatch* Pointer to an IDispatch interface
IUnknown* Pointer to an IUnknown interface
SAFEARRAY(TypeName) TypeName is any of the above types. An array of these
types
TypeName* TypeName is any of the above types. A pointer to a type
void Allowed only as a function return type or in a parameter
list to indicate no arguments
HRESULT Return type used for reporting error information in
interfaces as described in Chapter 1
LPWSTR Unicode string accepted only for 32-bit type libraries
LPSTR Zero-terminated string

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.

const IID IID_IUserInfo = {0xacceeb02,0x86c7,0x11d0,


{0x94,0xab,0x00,0x80,0xc7,0x4c,0x7e,0x95}};
const CLSID CLSID_UserInfo = {0xacceeb01,0x86c7,0x11d0,
{0x94,0xab,0x00,0x80,0xc7,0x4c,0x7e,0x95}};
const IID LIBID_UserInfo = {0xacceeb00,0x86c7,0x11d0,
{0x94,0xab,0x00,0x80,0xc7,0x4c,0x7e,0x95}};

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Implementing Interface Functions

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

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
Now all we have to do is provide an implementation for each CUserInfo member function.
The first COM-related function that we will investigate is QueryInterface.
QueryInterface, as you recall, is used for interface navigation. If a client calls
QueryInterface with the IID of a supported interface, the object should respond by
returning a pointer to that particular interface. In the case of the UserInfo COM object, if a
client calls QueryInterface with the IID for either IUnknown or IUserInfo,
UserInfo should return a pointer to that interface. In the
CUserInfo::QueryInterface function listed below, notice how the CUserInfo
object casts itself into either a IUserInfo pointer or an IUnknown pointer, depending on
the requested IID. If the requested interface is supported, QueryInterface calls AddRef
to increase the reference count in accordance with COM’s reference counting rules. If all goes
well, QueryInterface reports NOERROR to the client. If a client requests an unsupported
interface, which in this case would be any interface other than IUnknown or IUserInfo,
QueryInterface returns E_NOINTERFACE to notify the client that the requested
interface is not supported. NOERROR and E_NOINTERFACE may look familiar, as they were
introduced in the last chapter in Table 1-3: Commonly Used HRESULT Return Value
Constants.

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

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Implementing a Class Factory

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

bOK = SetRegKeyValue(szCLSIDKey, NULL,


_TEXT("DCOM Enterprise Apps - UserInfo Object."));
if (bOK)
bOK = SetRegKeyValue(szCLSIDKey,
_TEXT("InProcServer32"), szModulePath);
When a client invokes the DllUnregisterServer function, the server must remove
its information from the system registry. The following code snippet shows how
DllUnregisterServer uses the Win32 API function RegDeleteKey to remove
information from the registry:

//Delete sub-keys first


lErrorCode = RegDeleteKey(HKEY_CLASSES_ROOT,
szInprocServer32Key);
//Delete the entry under CLSID.
if (ERROR_SUCCESS == lErrorCode)
lErrorCode = RegDeleteKey(HKEY_CLASSES_ROOT,
szCLSIDKey);

Exposing the Class Factory

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.

Figure 2-1 The COM object instantiation process

The DllGetClassObject function begins by validating that the object identified by


the CLSID parameter can be created by the server; if it can’t, DllGetClassObject
returns CLASS_E_CLASSNOTAVAILABLE. However, if the server is capable of
creating the object, DllGetClassObject creates an instance of the class factory
responsible for creating the desired object. Once the class factory is instantiated,
DllGetClassObject calls QueryInterface, passing it the client-specified IID,
which in most cases is IClassFactory. The call to QueryInterface also serves
to increment the class factory object’s reference count from zero to one (see Listing 2-5).
Listing 2-5. The DllGetClassObject function

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

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
Because an in-process server cannot unload itself, the operating system must ask the server if
it can be unloaded by calling the DllCanUnloadNow function. This means that every
in-process server is responsible for implementing and exposing the DllCanUnloadNow
function. This is a relatively simple function that returns S-OK if there are no existing
instances of objects and no outstanding locks; otherwise, the function returns S_FALSE.
Encapsulating Server Packaging Specifics
There will often be times when a particular COM object needs to be exposed by both an
in-process server and an out-of-process server. I have tried to encapsulate the
implementation differences between in-process servers and out-of-process servers as much
as possible in order to shield the individual COM objects from these server packaging
specifics. As you will see in Chapter 3, the biggest differences between in-process and
out-of-process servers are in the areas of registering class information and server
unloading. Both of these differences occur as a result of the in-process server’s ability to
directly export functions for client consumption. Since out-of-process servers maintain
an address space apart from the client, they do not have the ability to simply export
functions for use by other processes. However, all COM servers are responsible for
providing mechanisms for registering class information and server unloading.
As you know, DLL servers are responsible for exporting the two functions
DLLRegisterServer and DLLUnregisterServer for the explicit purpose of
registering and unregistering class information. (Source code for the UserInfo object’s
implementation of DLLRegisterServer and DLLUnregisterServer can be seen
in Listing 2.6.) EXE servers, on the other hand, register their class information in response
to the /REGSERVER or -REGSERVER command-line arguments and unregister their class
information in response to the /UNREGSERVER or -UNREGSERVER command-line
arguments. In the spirit of the DLLRegisterServer and DLLUnregisterServer
functions used by DLL servers to manage registration information, I created the
RegisterServer and UnregisterServer functions, which are used by the various
EXE servers created throughout this book to manage their registration information. As part
of their WinMain entry point, each EXE server searches for the defined registration
command-line arguments, and calls either RegisterServer or UnregisterServer
as appropriate (see Listing 3-4). (Implementations of the RegisterServer and
UnregisterServer functions can be seen in Listing 3-5.)
When it comes to server unloading, DLL servers again have the luxury of simply exporting
the DllCanUnloadNow function, which is called automatically by the COM system
services to determine whether or not a particular COM server can be unloaded from
memory. EXE servers, on the other hand, are responsible for unloading themselves. In both
cases, a server is only unloaded when there are no outstanding objects and no class factory
locks. (Class factory locks are explained in Chapter 3.) In both cases, I have created the
ServerCanUnloadNow function, which is used to determine when it is appropriate to
unload a server from memory:

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.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
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];

//Obtain the path to server's executable file for


//later use
GetModuleFileName(g_hModule, szModulePath,
sizeof(szModulePath) / sizeof(_TCHAR));
//Convert the CLSID to the format
//{00000000-0000-0000-0000-000000000000}
StringFromGUID2(CLSID_UserInfo, wszGUID,
sizeof(wszGUID) / sizeof(wchar_t));
#ifdef _UNICODE
//UNICODE
_tcscpy(szCLSID, wszGUID);
#else
//SBCS and MBCS
//Convert from the wide character set to the
//multibyte character set
WideCharToMultiByte(CP_ACP, 0, wszGUID, -1, szCLSID,
sizeof(szCLSID) / sizeof(_TCHAR), NULL, NULL);
#endif
//HKEY_CLASSES_ROOT\CLSID\{00000000-0000-0000-0000-
//000000000000}
_tcscpy(szCLSIDKey, _TEXT("CLSID\\"));
_tcscat(szCLSIDKey, szCLSID);
bOK = SetRegKeyValue(szCLSIDKey, NULL,
_TEXT("DCOM Enterprise Apps - UserInfo Object."));
if (bOK)
bOK = SetRegKeyValue(szCLSIDKey,
_TEXT("InProcServer32"), szModulePath);
if (bOK)
return NOERROR;
else
return SELFREG_E_CLASS;
}//DllRegisterServer

//
//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];

//Convert the CLSID to the format


//{00000000-0000-0000-0000-000000000000}
StringFromGUID2(CLSID_UserInfo, wszGUID,
sizeof(wszGUID) / sizeof(wchar_t));
#ifdef _UNICODE
//UNICODE
_tcscpy(szCLSID, wszGUID);
#else
//SBCS and MBCS
//Convert from the wide character set to the
//multibyte character set
WideCharToMultiByte(CP_ACP, 0, wszGUID, -1, szCLSID,
sizeof(szCLSID) / sizeof(_TCHAR), NULL, NULL);
#endif
//HKEY_CLASSES_ROOT\CLSID\{00000000-0000-0000-0000-
//000000000000}
_tcscpy(szCLSIDKey, _TEXT("CLSID\\"));
_tcscat(szCLSIDKey, szCLSID);
_tcscpy(szInprocServer32Key, szCLSIDKey);
_tcscat(szInprocServer32Key,
_TEXT("\\InProcServer32"));
//Delete sub-keys first
lErrorCode = RegDeleteKey(HKEY_CLASSES_ROOT,
szInprocServer32Key);
//Delete the entry under CLSID.
if (ERROR-SUCCESS == lErrorCode)
lErrorCode = RegDeleteKey(HKEY_CLASSES_ROOT,
szCLSIDKey);
if (ERROR_SUCCESS == lErrorCode)
return NOERROR;
else
return SELFREG_E_CLASS;
}//DllUnregisterServer

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//Deallocate any previously allocated storage


if (m_lpszName)
delete[] m_lpszName;
m_lpszName = NULL;
//Allocate enough storage for the string
lStringLen = strlen(lpszName);
if (IStringLen > 0)
{
m_lpszName = new char[lStringLen + 1];
//Copy the string
strcpy(m_lpszName, lpszName);
}
return NOERROR;
}//put_Name

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

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

The UserInfoClient Application


To test our COM server, we will build the UserInfoClient application. The
UserInfoClient application will create and use the UserInfo COM object that we’ve just
developed. Once the UserInfo object is created, we will use QueryInterface to change to
the IUserInfo interface, which we will then use to set each of the UserInfo object’s
properties. After setting each property, we will retrieve, format, and display their values via the
Win32 API function MessageBox, which simply creates and displays a small window
containing a message. Before we build the UserInfoClient application, note the
responsibilities of a COM client:
• 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

Initializing the COM Library


The first item of business for COM clients is initializing the COM Library by calling the COM
API function CoInitialize. The CoInitialize function is provided by COM to
initialize the COM Library, and it must be called before any other COM Library calls, except the
CoGetMalloc function and memory allocation calls. Usage of CoInitialize is pretty
straightforward.

//Initialize the COM Library


hr = CoInitialize(NULL);
if ( SUCCEEDED(hr) )
{
...
}

Obtaining an Initial Interface

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:

hr = CoCreateInstance(CLSID_UserInfo, NULL, CLSCTX_INPROC_SERVER,


IID_IUnknown, (LPVOID *)&pIUnknown);
if ( SUCCEEDED(hr) )
{
...
}
While we are only interested in the CLSCTX_INPROC_SERVER class context, COM defines
the additional class execution contexts in Table 2-3:
Table 2-3 Defined Class Execution Contexts (CLSCTX)

Execution Context Meaning

CLSCTX_INPROC_SERVER In-Process server


CLSCTX_INPROC_HANDLER In-Process proxy
CLSCTX_LOCAL_SERVER Local server
CLSCTX_REMOTE_SERVER Remote server
CLSM_SERVER CLSCTX_INPROC_SERVER,
CLSCTX_LOCAL_SERVER, or
CLSCTX_REMOTE_SERVER
CLSCTX_ALL CLSCTX_INPROC_HANDLER or CLSCTX_SERVER

Manipulating a COM Object

If the call to CoCreateInstance is successful, it will return a pointer to the IUnknown


interface in the pIUnknown parameter. After obtaining an initial interface pointer, we can begin
to manipulate the object. As all of the action seems to take place in the IUserInfo interface,
let’s go there! A simple QueryInterface call requesting the IID_IUserInfo pointer is all
it takes to obtain a pointer to the IUserInfo interface. Once we have obtained an IUserInfo
interface pointer, we can set any of the available properties. The UserInfoClient
application sets the Age, Name, and Sex properties. UserInfoClient then retrieves each
property value back, formats their values, and displays them.

Releasing the COM 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:

//Release the IUserInfo interface


pIUserInfo->Release();
//Release the IUnknown interface
pIUnknown->Release();

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//Shut down the COM Library


CoUninitialize();
The source code for the UserInfoClient can be found in Listing 2-9.
Listing 2-9. UserInfoClient.cpp

//
//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;

//Retrieve each property


pIUserInfo->get_Name(&lpszName);
pIUserInfo->get_Age(&nAge);
pIUserInfo->get_Sex(&bySex);

DisplayMessage(lpszRetrievedMsg);

//Format the Name


strcpy(szDisplayText, "Name: ");
if (lpszName)
strcat(szDisplayText, lpszName);
//Add a carriage return
lStringLen = strlen(szDisplayText);
szDisplayText[lStringLen] = '\r';
//Null terminate the string
szDisplayText[lStringLen + 1] = '\0';

//Format the Age


ltoa(nAge, szAge, 10);
strcat(szDisplayText, "Age: ");
strcat(szDisplayText, szAge);
//Add a carriage return
lStringLen = strlen(szDisplayText);
szDisplayText[]StringLen] = '\r';
//Null terminate the string
szDisplayText[]StringLen + 1] = '\0';

//Format the Sex


strcat(szDisplayText, "Sex: ");
lStringLen = strlen(szDisplayText);
szDisplayText[lStringLen] = bySex;
//Null terminate the string
szDisplayText[]StringLen + 1] = '\0';

#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;

//Initialize the COM Library


hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("The COM Library has been
initialized."));

//Ask the COM Library to instantiate the UserInfo object


//and return us an initial pointer to IUnknown
hr = CoCreateInstance(CLSID_UserInfo, NULL,
CLSCTX_INPROC_SERVER, IID_IUnknown,
(LPVOID *)&pIUnknown);

if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("The UserInfo object has
been created."));
//Begin using the object

//QueryInterface for the IUserInfo interface


hr = pIUnknown->QueryInterface(IID-IUserInfo,
(LPVOID *)&pIUserInfo);
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("Changed to the
IUserInfo interface."));

//Set each property


pIUserInfo->put_Name("Frank E. Redmond III");
pIUserInfo->put_Age(24);
pIUserInfo->put_Sex('M');

DisplayMessage(_TEXT("Each UserInfo property


has been set."));
DisplayUserInfo(pIUserInfo, _TEXT("Each UserInfo
property has been retreived."));

//Release the IUserInfo interface


pIUserInfo->Release();
DisplayMessage(_TEXT("Released the IUserInfo
interface."));
}
else
DisplayMessage(_TEXT("Couldn't change to the
IUserInfo interface."));

//Release the IUnknown interface


pIUnknown->Release();
DisplayMessage(_TEXT("Released the IUnknown
interface."));
}
else
DisplayMessage(_TEXT("The UserInfo object couldn't
be created."));

//Shut down the COM Library


CoUninitialize();
DisplayMessage(_TEXT("Shut down the COM Library."));
}
else
DisplayMessage(_TEXT("The COM Library initialization
failed."));

DisplayMessage(_TEXT("Terminating the Application."));


//Terminate the application
return FALSE;
}//WinMain
That’s all there is to it! While the UserInfoClient is a simple application, it exemplifies
the fundamental steps required by all COM clients.

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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.

AS YOU NOW know, an out-of-process server is a COM server implemented


as an EXE. By being packaged within an EXE, a COM object actually exists in
its own process space apart from its client. Maintaining a separate process
space can have significant advantages:
• Process isolation. This is the ability to isolate the effects of one
process from another related process. Consider a case in which a COM
client relies on a COM object that’s implemented as part of an
in-process server. If the COM object crashes for some unknown reason,
the entire client application will also crash, because the in-process server
and the client share the same address space. However, if the COM
object in question were created as part of an out-of-process server, then
the client application would be afforded process isolation, and thus
would be shielded from the effects of the crashing COM object.
• Better security. On secure systems such as Windows NT, the
resources of each process can be guarded against unauthorized usage by
rogue processes. The same level of security is not possible with an
in-process server, because the client and the server share the same
process space.
• The ability to maintain shared global resources. Since EXEs
maintain ownership of their own resources, an out-of-process server is
easily capable of allocating global resources and sharing them among its
various clients. The same is not true for DLLs, because an independent
copy of the DLL, and thus an independent copy of the DLL’s resources,
is loaded into each client’s address space.
However, maintaining an address space apart from the client also means that
each method invocation requires marshaling, which is definitely slower than
in-process execution. Separate address spaces also means that out-of-process
servers must use different techniques to provide functionality that would
otherwise be exported by in-process servers. Learning the functional and
architectural differences between in-process servers and out-of-process servers
is the focus of this chapter.

The UserInfoHandler Server


The UserInfoHandler server is an out-of-process server that supports
both the UserInfo object from the last chapter and the
UserInfoHandler object. The UserInfo object is implemented using
the same code introduced in Chapter 2, except for the DLL-specific sections
contained in DllMain.cpp. The UserInfo COM object maintains information
about an individual user and exposes this information through the properties
given in Table 3-1.
Table 3-1 UserInfo Object Properties

Property Name Data Type

Age short
Name LPSTR
Sex unsigned char

The UserInfoHandler object is used to perform operations on UserInfo


objects. UserInfoHandler has three interfaces: ICopyInfo,
IReverseInfo, and ISwapInfo. The ICopyInfo interface is used to
copy information from one UserInfo object to another. The
IReverseInfo interface is used to reverse a UserInfo object’s
information. The ISwapInfo interface is used to exchange information
between two UserInfo objects. Each interface has four functions, one for
each UserInfo property and one that affects all of the properties. The
UserInfoHandler is outlined in Table 3-2:
Table 3-2 UserInfoHandler Object Outline
Interface Name Function

ICopyInfo CopyName(IUserInfo *pDest, IUserInfo *pSrc)


CopyAge(IUserInfo *pDest, IUserInfo *pSrc)
CopySex(IUserInfo *pDest, IUserInfo *pSrc)
CopyAll(IUserInfo *pDest, IUserInfo *pSrc)
IReverseInfo ReverseName(IUserInfo *pIUserInfo)
ReverseAge(IUserInfo *pIUserInfo)
ReverseSex(IUserInfo *pIUserInfo)
ReverseAll(IUserInfo *pIUserInfo)
SwapName(IUserInfo *pIUserInfo, IUserInfo
ISwapInfo
*pIUserInfo)
SwapAge(IUserInfo *pIUserInfo, IUserInfo
*pIUserInfo)
SwapSex(IUserInfo *pIUserInfo, IUserInfo
*pIUserInfo)
SwapAge(IUserInfo *pIUserInfo, IUserInfo
*pIUserInfo)

Like in-process servers, out-of-process servers are also responsible for:


• Allocating GUIDs for each supported object and interface
• Defining the interfaces supported by each object
• Implementing the functions defined by each interface
• Implementing a class factory capable of creating each supported
object
• Registering class information for each supported object
• Exposing a class factory for each supported object
• Unloading (destroying) itself when appropriate

Allocating CLSIDs

Using either GUIDGEN or UUIDGEN, you need to generate enough GUIDs


for the UserInfoHandler CLSID, the type library, and each of the three
supported interfaces. If you are using GUIDGEN, this will require several
steps. However, UUIDGEN can be used to accomplish this in one single step:

C:>uuidgen -n5 -oguids.txt

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

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
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;
}
}

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

const IID IID_IUserInfo = {0xacceeb02,0x86c7,0x11d0,


{0x94,0xab,0x00,0x80,0xc7,0x4c,0x7e,0x95}};
const IID IID_ICopyInfo = {0xb04faa82,0x8bef,0x11d0,
{0x94,0xab,0x00,0xa0,0x24,0xa8,0x5a,0x21}};
const IID IID_IReverseInfo = {0xb04faa83,0x8bef,0x11d0,
{0x94,0xab,0x00,0xa0,0x24,0xa8,0x5a,0x21}};
const IID IID_ISwapInfo = {0xb04faa84,0x8bef,0x11d0,
{0x94,0xab,0x00,0xa0,0x24,0xa8,0x5a,0x21}};
const IID LIBID_UserInfoHandler = {0xb04faa80,0x8bef,0x11d0,
{0x94,0xab,0x00,0xa0,0x24,0xa8,0x5a,0x21}};
const CLSID CLSID_UserInfo = {0xacceeb01,0x86c7,0x11d0,
{0x94,0xab,0x00,0x80,0xc7,0x4c,0x7e,0x95}};
const CLSID CLSID_UserInfoHandler = {0xb04faa81,0x8bef,0x11d0,
{0x94,0xab,0x00,0xa0,0x24,0xa8,0x5a,0x21}};
The UserInfoHandler_i.h file contains the C and C++ definitions for the IUserInfo,
ICopyInfo, IReverseInfo, and ISwapInfo interfaces. We examine the
UserInfoHandler_p.c and dlldata.c files later on in this chapter.

Implementing Interface Methods

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

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
Next we have to provide the implementation for each CUserInfoHandler member
function. Because the UserInfoHandler object supports four interfaces, its
QueryInterface implementation must be capable of returning each one. The following
listing is of CUserInfoHandler::QueryInterface. Notice that because the
CUserInfoHandler C++ class doesn’t directly inherit from IUnknown, it must cast itself
into an ICopyInfo interface pointer before casting to an IUnknown interface pointer. A
C++ class can be cast to any interface that it inherits from, regardless of whether the
inheritance is direct, like the ICopyInfo, IReverseInfo, and ISwapInfo interfaces;
or indirect, like the IUnknown interface:

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:

STDMETHODIMP CUserInfoHandler::CopyAge(IUserInfo *lpDest,


IUserInfo *lpSrc)
{
HRESULT hr;
short nTmpAge;

//Retrieve information from the source


hr = lpSrc->get_Age(&nTmpAge);
if (SUCCEEDED(hr))
{
//Apply it to the destination
hr = lpDest->put_Age(nTmpAge);
}
return hr;
}//CopyAge

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Registering Class Information

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

Exposing the Class Factory

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

The Importance of Quick Registration


To understand why it is important to register class factories as soon as possible, imagine
that you’re a client of the UserInfoHandler server, and you call
CoCreateInstance to instantiate a UserInfoHandler object. COM responds by
calling CoGetClassObject to actually load the UserInfoHandler server. Once
CoGetClassObject has loaded the server, COM then begins searching through a
system-wide table of registered class factories, based on the CLSID parameter specified in
the call to CoCreateInstance. If COM finds the entry, life is good! However, if COM
doesn’t find the object’s class factory entry in the system-wide table of registered class
factories, the call to CoGetClassObject will be suspended. But COM won’t wait
forever, and if the UserInfoHandler server doesn’t register its class factory within a
certain amount of time, the call to CoGetClassObject will time-out with the
CO_E_APPDIDNTREG error code. Thus, for an out-of-process server, it is important to
register the object’s class factories as soon after application start-up as possible in order to
prevent calls to CoGetClassObject and ultimately CoCreateInstance from
timing out.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

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

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Figure 3-1 The COM marshaling process

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
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];

//Obtain the path to server's executable file for


//later use
GetModuleFileName(g_hModule, szModulePath,
sizeof(szModulePath) / sizeof(_TCHAR));
//Convert the CLSID to the format
//{00000000-0000-0000-0000-000000000000}
StringFromGUID2(clsid, wszGUID, sizeof(wszGUID) /
sizeof(wchar_t));
#ifdef _UNICODE
//UNICODE
_tcscpy(szCLSID, wszGUID);
#else
//SBCS and MBCS
//Convert from the wide character set to the
//multibyte character set
WideCharToMultiByte(CP_ACP, 0, wszGUID, -1, szCLSID,
sizeof(szCLSID) / sizeof(_TCHAR), NULL, NULL);
#endif
//HKEY_CLASSES_ROOT\CLSID\
//{00000000-0000-0000-0000-000000000000}
_tcscpy(szCLSIDKey, _TEXT("CLSID\\"));
_tcscat(szCLSIDKey, szCLSID);
bOK = SetRegKeyValue(szCLSIDKey, NULL, lpszDescription);
if (bOK)
bOK = SetRegKeyValue(szCLSIDKey, _TEXT("LocalServer32"),
szModulePath);

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];

//Convert the CLSID to the format


//{00000000-0000-0000-0000-000000000000}
StringFromGUID2(clsid, wszGUID, GUID_SIZE);
#ifdef _UNICODE
//UNICODE
_tcscpy(szCLSID, wszGUID);
#else
//SBCS and MBCS
//Convert from the wide character set to the
//multibyte character set
WideCharToMultiByte(CP_ACP, 0, wszGUID, -1, szCLSID,
sizeof(szCLSID) / sizeof(_TCHAR), NULL, NULL);
#endif
//HKEY_CLASSES_ROOT\CLSID\
//{00000000-0000-0000-0000-000000000000}
_tcscpy(szCLSIDKey, _TEXT("CLSID\\"));
_tcscat(szCLSIDKey, szCLSID);
_tcscpy(szLocalServer32Key, szCLSIDKey);
_tcscat(szLocalServer32Key, _TEXT("\\LocalServer32"));
//Delete sub-keys first
lErrorCode = RegDeleteKey(HKEY_CLASSES_ROOT,
szLocalServer32Key);
//Delete the entry under CLSID.
if (ERROR_SUCCESS == lErrorCode)
lErrorCode = RegDeleteKey(HKEY_CLASSES_ROOT,
szCLSIDKey);
if (ERROR_SUCCESS == lErrorCode)
return TRUE;
else
return FALSE;
}//UnregisterServer

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//Retrieve information from the source


hr = lpSrc->get_Age(&nTmpAge);
if (SUCCEEDED(hr))
{
//Apply it to the destination
hr = lpDest->put_Age(nTmpAge);
}
return hr;
}//CopyAge

//
//CopyName
//
STDMETHODIMP CUserInfoHandler::CopyName(IUserInfo *lpDest,
IUserInfo *lpSrc)
{
HRESULT hr;
LPSTR lpszTmpName = NULL;

//Retrieve information from the source


hr = lpSrc->get_Name(&lpszTmpName);
if (SUCCEEDED(hr))
{
//Apply it to the destination
hr = lpDest->put_Name(lpszTmpName);
}
return hr;
}//CopyName

//
//CopySex
//
STDMETHODIMP CUserInfoHandler::CopySex(IUserInfo *lpDest,
IUserInfo *lpSrc)
{
HRESULT hr;
BYTE byTmpSex;

//Retrieve information from the source


hr = lpSrc->get_Sex(&byTmpSex);
if (SUCCEEDED(hr))
{
//Apply it to the destination
hr = lpDest->put_Sex(byTmpSex);
}
return hr;
}//CopySex

//
//CopyAll
//
STDMETHODIMP CUserInfoHandler::CopyAll(IUserInfo *lpDest,
IUserInfo *lpSrc)
{
HRESULT hr;

//Copy each property


//Copy Age
hr = CopyAge(lpDest, lpSrc);
if (SUCCEEDED(hr))
{
//Copy Name
hr = CopyName(lpDest, lpSrc);
if (SUCCEEDED(hr))
{
//Copy Sex
hr = CopySex(lpDest, lpSrc);
}
}
return hr;
}//CopyAll

//
//ReverseAge
//
STDMETHODIMP CUserInfoHandler::ReverseAge(IUserInfo *lpIUserInfo)
{
HRESULT hr;
short nTmpAge;
short nReversedAge;
char szAgeString[10];
char szReversedAgeString[10];

//Retrieve the age


hr = lpIUserInfo->get_Age(&nTmpAge);
if (SUCCEEDED(hr))
{
//Convert the number to a string
ltoa(nTmpAge, szAgeString, 10);
//Reverse the string
strcpy(szReversedAgeString, _strrev(szAgeString));
//Convert the string to a number
nReversedAge = atoi(szReversedAgeString);
//Set the age property to the reversed value
hr = lpIUserInfo->put_Age(nReversedAge);
}
return hr;
}//ReverseAge

//
//ReverseName
//
STDMETHODIMP CUserInfoHandler::ReverseName(IUserInfo
*lpIUserInfo)
{
HRESULT hr;
LPSTR lpszTmpName = NULL;
LPSTR lpszReversedName = NULL;

//Retrieve the name


hr = lpIUserInfo->get_Name(&lpszTmpName);
if (SUCCEEDED(hr))
{
//Reverse the string
lpszReversedName = _strrev(lpszTmpName);
//Set the name property to the reversed value
hr = lpIUserInfo->put_Name(lpszReversedName);
}
return hr;
}//ReverseName

//
//ReverseSex
//
STDMETHODIMP CUserInfoHandler::ReverseSex(IUserInfo
*lpIUserInfo)
{
HRESULT hr;
BYTE byTmpSex;

//Retrieve the sex


hr = lpIUserInfo->get_Sex(&byTmpSex);
if (SUCCEEDED(hr))
{
//Reverse the sex
if (('M' == byTmpSex)||('m' == byTmpSex))
hr = lpIUserInfo->put_Sex('F');
else
hr = lpIUserInfo->put_Sex('M');
}
return hr;
}//ReverseSex

//
//ReverseAll
//
STDMETHODIMP CUserInfoHandler::ReverseAll(IUserInfo
*lpIUserInfo)
{
HRESULT hr;

//Reverse each property


//Reverse Age
hr = ReverseAge(lpIUserInfo);
if (SUCCEEDED(hr))
{
//Reverse Name
hr = ReverseName(lpIUserInfo);
if (SUCCEEDED(hr))
{
//Reverse Sex
hr = ReverseSex(lpIUserInfo);
}
}
return hr;
}//ReverseAll

//
//SwapAge
//
STDMETHODIMP CUserInfoHandler::SwapAge(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2)
{
HRESULT hr;
short nTmpAge1;
short nTmpAge2;

//Retrieve the age from the first UserInfo


hr = lpIUserInfo1->get_Age(&nTmpAge1);
if (SUCCEEDED(hr))
{
//Retrieve the age from the second UserInfo
hr = lpIUserInfo2->get_Age(&nTmpAge2);
if (SUCCEEDED(hr))
{
//Set the age of the first UserInfo
hr = lpIUserInfo1->put_Age(nTmpAge2);
if (SUCCEEDED(hr))
{
//Set the age of the second UserInfo
hr = lpIUserInfo2->put_Age(nTmpAge1);
}
}
}
return hr;
}//SwapAge

//
//SwapName
//
STDMETHODIMP CUserInfoHandler::SwapName(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2)
{
HRESULT hr;
LPSTR lpszTmpName1 = NULL;
LPSTR lpszTmpName2 = NULL;
LPSTR lpszCopiedName1 = NULL;
LPSTR lpszCopiedName2 = NULL;
long lStringLen;

//Retrieve the name from the first UserInfo


hr = lpIUserInfo1->get_Name(&lpszTmpName1);
if (SUCCEEDED(hr))
{
lStringLen = strlen(lpszTmpName1);
if (lStringLen > 0)
{
lpszCopiedName1 = new char[lStringLen + 1];
//Copy the string value
strcpy(lpszCopiedName1, lpszTmpName1);
}
//Retrieve the name from the second UserInfo
hr = lpIUserInfo2->get_Name(&lpszTmpName2);
if (SUCCEEDED(hr))
{
lStringLen = strlen(lpszTmpName2);
if (lStringLen > 0)
{
lpszCopiedName2 = new char[lStringLen + 1];
//Copy the string value
strcpy(lpszCopiedName2, lpszTmpName2);
}
//Set the name of the first UserInfo
hr = lpIUserInfo1->put_Name(lpszCopiedName2);
if (SUCCEEDED(hr))
{
//Set the name of the second UserInfo
hr = lpIUserInfo2->put_Name(lpszCopiedName1);
}
}
}
//Clean up
if (lpszCopiedName1)
delete[ ] lpszCopiedName1;
if (lpszCopiedName2)
delete[ ] lpszCopiedName2;
return hr;
}//SwapName

//
//SwapSex
//
STDMETHODIMP CUserInfoHandler::SwapSex(IUserInfo *lpIUserInfo1,
IUserInfo *lpIUserInfo2)
{
HRESULT hr;
BYTE byTmpSex1;
BYTE byTmpSex2;

//Retrieve the sex from the first UserInfo


hr = lpIUserInfo1->get_Sex(&byTmpSex1);
if (SUCCEEDED(hr))
{
//Retrieve the sex from the second UserInfo
hr = lpIUserInfo2->get_Sex(&byTmpSex2);
if (SUCCEEDED(hr))
{
//Set the sex of the first UserInfo
hr = lpIUserInfo1->put_Sex(byTmpSex2);
if (SUCCEEDED(hr))
{
//Set the sex of the second UserInfo
hr = lpIUserInfo2->put_Sex(byTmpSex1);
}
}
}
return hr;
}//SwapSex

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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

Property Name Data Type Implemented In

Name LPSTR IUserInfo


Sex unsigned char IUserInfo
Address LPSTR IMemberInfo
City LPSTR IMemberInfo
State LPSTR IMemberInfo
Zip LPSTR IMemberInfo
Phone LPSTR IMemberInfo

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.")]

HRESULT Zip([in] LPSTR lpszZip);


}

//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;
}
}

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

STDMETHODIMP CMemberInfo::get_Name(LPSTR *lpszRetName)


{
return m_pIUserInfo->get_Name(lpszRetName);
}//get_Name

STDMETHODIMP CMemberInfo::put_Name(LPSTR lpszName)


{
return m_pIUserInfo->put_Name(lpszName);
}//put_Name
.
.
.
STDMETHODIMP CMemberInfo::get_Sex(BYTE *byRetSex)
{
return m_pIUserInfo->get_Sex(byRetSex);
}//get_Sex

STDMETHODIMP CMemberInfo::put_Sex(BYTE bySex)


{
return m_pIUserInfo->put_Sex(bySex);
}//put_Sex
That’s all there is to containment! When the MemberInfo object is finished using
the UserInfo object, it destroys it by calling Release as part of its 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
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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//Deallocate any previously allocated storage


if (m_lpszAddress)
delete[] m_lpszAddress;
m_lpszAddress = NULL;
//Allocate enough storage for the string
lStringLen = strlen(lpszAddress);
if (lStringLen > 0)
{
m_lpszAddress = new char[lStringLen + 1];
//Copy the string
strcpy(m_lpszAddress, lpszAddress);
}
return NOERROR;
}//put_Address

//
//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;

//Deallocate any previously allocated storage


if (m_lpszCity)
delete[] m_lpszCity;
m_lpszCity = NULL;
//Allocate enough storage for the string
lStringLen = strlen(lpszCity);
if (lStringLen > 0)
{
m_lpszCity = new char[lStringLen + 1];
//Copy the string
strcpy(m_lpszCity, lpszCity);
}
return NOERROR;
}//put_City

//
//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;

//Deallocate any previously allocated storage


if (m_lpszPhone)
delete[] m_lpszPhone;
m_lpszPhone = NULL;
//Allocate enough storage for the string
lStringLen = strlen(lpszPhone);
if (lStringLen > 0)
{
m_lpszPhone = new char[lStringLen + 1];
//Copy the string
strcpy(m_lpszPhone, lpszPhone);
}
return NOERROR;
}//put_Phone
//
//get_Sex
//
STDMETHODIMP CMemberInfo::get_Sex(BYTE *byRetSex)
{
return m_pIUserInfo->get_Sex(byRetSex);
}//get_Sex

//
//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;

//Deallocate any previously allocated storage


if (m_lpszState)
delete[] m_lpszState;
m_lpszState = NULL;
//Allocate enough storage for the string
lStringLen = strlen(lpszState);
if (lStringLen > 0)
{
m_lpszState = new char[lStringLen + 1];
//Copy the string
strcpy(m_lpszState, lpszState);
}
return NOERROR;
}//put_State

//
//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;

//Deallocate any previously allocated storage


if (m_lpszZip)
delete[] m_lpszZip;
m_lpszZip = NULL;
//Allocate enough storage for the string
lStringLen = strlen(lpszZip);
if (lStringLen > 0)
{
m_lpszZip = new char[lStringLen + 1];
//Copy the string
strcpy(m_lpszZip, lpszZip);
}
return NOERROR;
}//put_Zip

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

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Property Data Type Implemented In

AccountNumber long IAccountInfo


AccountBalance float IAccountInfo
AccountLimit long IAccountInfo
Name LPSTR IMemberInfo
Sex unsigned char IMemberInfo
Address LPSTR IMemberInfo
City LPSTR IMemberInfo
State LPSTR IMemberInfo
Zip LPSTR IMemberInfo
Phone LPSTR IMemberInfo

As you may have noticed, the AccountInfo object’s IMemberInfo


interface is exactly the same as the IMemberInfo interface defined earlier
by the MemberInfo object. That’s because we are going to use the
MemberInfo object as the inner object. The AccountInfo object will
implement the IAccountInfo interface itself, but will aggregate the
MemberInfo object in order to expose the IMemberInfo interface. I think
it’s worth mentioning that containment and aggregation aren’t mutually
exclusive. Interfaces created through containment can be aggregated later and,
likewise, aggregate interfaces can be contained later. For example, this
chapter’s AccountInfo object exposes the IMemberInfo interface
directly through aggregation, and it just so happens that the IMemberInfo
interface was created through containment of Chapter 2’s IUserInfo
interface.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

STDMETHODIMP CMemberInfo::QueryInterface(REFIID iid, LPVOID *ppv)


{
//Delegate to the controlling IUnknown
return m_pControllingIUnknown->QueryInterface(iid, ppv);
}//QueryInterface
This makes sense when you consider that the inner object has no knowledge of what interfaces are
supported by the outer object. The outer AccountInfo object’s QueryInterface is then
slightly modified to take into account the interfaces of the inner MemberInfo object:

STDMETHODIMP CAccountInfo::QueryInterface(REFIID iid, LPVOID *ppv)


{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)this;
else if (IID_IAccountInfo == iid)
*ppv = (LPVOID)(IAccountInfo *)this;
else if (IID_IMemberInfo == iid)
return m_pMemberInfoIUnknown->QueryInterface(iid, ppv);
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown *)*ppv)->AddRef();
return NOERROR;
}//QueryInterface
I know what you’re thinking, and you’re right…sort of. You’re thinking that if
m_pMemberInfoIUnknown is a pointer to the inner object’s IUnknown, and the inner
object’s IUnknown simply delegates to the outer object’s IUnknown, then we’ve just created a
circular reference! You are right, except that m_pMemberInfoIUnknown doesn’t point to the
inner object’s IUnknown. The MemberInfo object actually implements two IUnknowns: the
delegating unknown, which simply delegates to the controlling unknown, and the nondelegating
unknown, which actually implements IUnknown as we know it. To avoid a circular reference,
CAccountInfo’s m_pMemberInfoIUnknown points to the nondelegating unknown, which
is used by the outer object to control the lifetime of the inner object and access its interfaces. The
MemberInfo object’s nondelegating unknown interface is created by first defining its VTBL
layout, which must be identical to the VTBL layout of IUnknown:

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:

class CMemberInfo : IMemberInfo, INonDelegatingUnknown


{
private:
ULONG m_cRef;
//Controlling unknown
IUnknown *m_pControllingIUnknown;
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);
//NonDelegatingUnknown
STDMETHODIMP NonDelegatingQueryInterface(REFIID iid, LPVOID
*ppv);
STDMETHODIMP_(ULONG)NonDelegatingAddRef(void);
STDMETHODIMP_(ULONG)NonDelegatingRelease(void);
HRESULT Initialize(void);
//Constructor
CMemberInfo(IUnknown* pUnknownOuter);
//Destructor
~CMemberInfo();
};//CMemberInfo
The interface functions of the MemberInfo object’s nondelegating unknown,
INonDelegatingUnknown, are then implemented the way you would normally implement the
functions of IUnknown:

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

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//Even though we are creating a copy of an interface


//don't call AddRef, because it will create a circular
//reference count!
if (pUnknownOuter)
//Part of an aggregate
//save the controlling IUnknown
m_pControllingIUnknown = pUnknownOuter;
else
//Not part of an aggregate
//the controlling IUnknown is the NonDelegating Unknown
m_pControllingIUnknown = (IUnknown *)(INonDelegatingUnknown
*)this;
}//CMemberInfo
That’s all there is to it! Aggregation allows an outer COM object to expose the interfaces of an
inner COM object as if the outer object implemented them directly. As a potential outer object, you
can determine if an object supports aggregation by simply calling CoCreateInstance and
attempting to aggregate it. If the call succeeds, the object supports aggregation; otherwise, it
doesn’t:

hr = CoCreateInstance(CLSID_MemberInfo, (IUnknown *)this,


CLSCTX_INPROC_SERVER, IID_IUnknown,
(LPVOID *)&38;m_pMemberInfoIUnknown);
if (SUCCEEDED(hr))
{
//Object supports aggregation
}
else
{
//Object doesn't support aggregation
}
Despite its inherent benefits, aggregation has two major restrictions:
• Both the outer and inner objects must be in the same process.
• The inner object must specifically support aggregation by supporting both a delegating and
nondelegating IUnknown.
For those COM objects that don’t support aggregation, your only opportunity for reuse is through
the containment process. For this reason, I strongly suggest that you take the extra step and support
aggregation when creating your own COM objects. Source code for the AccountInfoClient
application can be found on the accompanying CD-ROM. The AccountInfoClient application
puts the AccountInfo object through its paces by using various interface functions from each of
the object’s supported interfaces.

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.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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

Like all COM interfaces, IDispatch inherits from IUnknown, but


IDispatch also defines the member functions in Table 5-1.
Table 5-1 IDispatch Member Functions

Function Name Purpose

Invoke Provides access to properties and methods exposed by an


Automation object
GetIDsOfNames Maps a single member name and an optional set of
argument names to a corresponding set of DISPIDs (see
below), which may then be used in subsequent calls to
Invoke
GetTypeInfo Retrieves type information about an Automation object
GetTypeInfoCount Determines whether type information is available for a
particular interface

The IDispatch interface separately maintains a special dispatch interface,


called a dispinterface. Like a VTBL, the dispinterface maintains the addresses
of each supported interface function. However, unlike a VTBL, the
dispinterface identifies each function using a special dispatch identifier, or
DISPID. A DISPID is not a GUID, but simply a long integer that uniquely
identifies a function within a particular dispinterface. Clients call
IDispatch::GetIDsOfNames with the name of the property or method
that they are interested in accessing. The Automation object’s implementation
of IDispatch::GetIDsOfNames uses the name to perform a lookup
against the type library to obtain the appropriate DISPID, which it returns to
the client. The client may then call IDispatch::GetTypeInfoCount to
determine if the Automation object supplies a type library. If the Automation
object supplies a type library for the dispinterface in question, the client can
obtain a pointer to it by calling IDispatch::GetTypeInfo. The client
can use this type information to perform any necessary type checking. At this
point, the client can call IDispatch::Invoke with the DISPID and
appropriate function parameters to actually access the desired property or
method. This entire process is illustrated in Figure 5-1.

Figure 5-1 The Automation process


While Automation provides a mechanism for the run-time type checking
required by popular script and macro languages, it doesn’t come for free.
Instead of directly accessing an interface function through a VTBL (VTBL
binding), Automation controllers are required to use IDispatch::Invoke
with DISPIDs that are obtained at run time, either via calls to
IDispatch::GetIDsOfNames, in a process known as late binding, or at
compile time via access to the Automation object’s type library, in a process
known as early binding.
Late binding is an expensive operation because the DISPID for each property
or method used must be retrieved at run time using
IDispatch::GetIDsOfNames before the property or method can be
accessed by IDispatch::Invoke. However, some Automation controllers
are capable of obtaining and caching the various property and method
DISPIDs of an Automation object from the object’s type library at compile
time by using early binding. By obtaining and caching the DISPIDs from the
type library, the Automation controller can avoid calling
IDispatch::GetIDsOfNames every time it needs to access a particular
property or method. By avoiding the additional call to
IDispatch::GetIDsOfNames, the Automation controller is able to
increase its overall performance. However, to support early binding, the
Automation controller must have access to the Automation object’s type
library at compile time, unlike late binding, in which the Automation
controller must have access to the object’s type library at run time.
In either case, supporting IDispatch requires additional programming effort
for the programmer when creating the Automation controller, due in large part
to the process of packaging each required function parameter into a variant.
While Automation definitely has its drawbacks, so does direct VTBL binding,
the most significant of which is its lack of support for run-time type checking.
In order to provide support for the run-time type checking required by popular
scripting and macro languages, without penalizing C/C++ developers who are
most interested in the speed and ease of development offered by VTBL
binding, you should implement dual interfaces.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

VARIANT_BOOL Data item having either a TRUE or FALSE value


char 8-bit signed data item
double 64-bit IEEE floating-point number
int System-dependent signed integer
float 32-bit IEEE floating-point number
long 32-bit signed integer
short 16-bit signed integer
BSTR Length-prefixed string
CURRENCY 8-byte, fixed-point number
DATE 64-bit floating-point fractional number of days since December
30, 1899
SCODE Built-in error type that corresponds to VT_ERROR. An
SCODE (used on 16-bit systems only) does not contain the
additional error information provided by an HRESULT.
IDispatch* Pointer to an IDispatch interface
IUnknown* Pointer to an IUnknown interface
SAFEARRAY(TypeName) An array of TypeName types. TypeName can be any of the
above datatypes.
TypeName* A pointer to TypeName. TypeName can be any of the above
datatypes.
VARIANT A structure consisting of a union of all the above datatypes.
void Allowed only as a function return type or in a parameter list to
indicate no arguments.
HRESULT Return type used for reporting error information in interfaces
as described in Chapter 1.

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

typedef struct FARSTRUCT tagVARIANT VARIANT;

typedef struct tagVARIANT {


VARTYPE vt;
unsigned short wReserved1;
unsigned short wReserved2;
unsigned short wReserved3;
union {
unsigned char bVal; // VT_UI1
short iVal; // VT_I2
long lVal; // VT_I4
float fltVal; // VT_R4
double dblVal; // VT_R8
VARIANT_BOOL bool; // VT_BOOL
SCODE scode; // VT_ERROR
CY cyVal; // VT_CY
DATE date; // VT_DATE
BSTR bstrVal; // VT_BSTR
Iunknown FAR* punkVal; // VT_UNKNOWN
IDispatch FAR* pdispVal; // VT_DISPATCH
SAFEARRAY FAR* parray; // VT_ARRAY | *
unsigned char FAR* pbVal; // VT_BYREF | VT_UI1
short FAR* piVal; // VT_BYREF | VT_I2
long FAR* plVal; // VT_BYREF | VT_I4
float FAR* pfltVal; // VT_BYREF | VT_R4
double FAR* pdblVal; // VT_BYREF | VT_R8
VARIANT_BOOL FAR* pbool; // VT_BYREF | VT_BOOL
SCODE FAR* pscode; // VT_BYREF | VT_ERROR
CY FAR* pcyVal; // VT_BYREF | VT_CY
DATE FAR* pdate; // VT_BYREF | VT_DATE
BSTR FAR* pbstrVal; // VT_BYREF | VT_BSTR
Iunknown FAR* FAR* ppunkVal; // VT_BYREF | VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal; // VT_BYREF | VT_DISPATCH
SAFEARRAY FAR* FAR* parray; // VT_ARRAY | *
VARIANT FAR* pvarVal; // VT_BYREF | VT_VARIANT
void FAR* byref; // Generic ByRef
};
};

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//Initialize each variant


VariantInit(&ageVariant);
VariantInit(&nameVariant);

//Use the variant for storage of a long


ageVariant.vt = VT_I4;
ageVariant.lVal = 333;
//Use the variant for storage of a BSTR
nameVariant.vt = VT_BSTR
nameVariant.bstrVal = SysAllocString(OLESTR("Ken Lewis"));
A variant’s vt value is used to determine which member of the union contains valid data.
Table 5-3 lists and describes the various variant vt values:
Table 5-3 Interpreting Variant VT Values
VT Value Defined Constant Description

0 VT_EMPTY VARIANT contains no data


1 VT_NULL VARIANT contains NULL
2 VT_I2 A 2-byte integer is in iVal
3 VT_I4 A 4-byte integer is in lVal
4 VT_R4 An IEEE 4-byte real is in fltVal
5 VT_R8 An IEEE 8-byte real is in dblVal
6 VT_CY An 8-byte two’s complement currency value is
in cyVal
7 VT_DATE A double-precision date is in date
8 VT_BSTR A BSTR is in bstrVal
9 VT_DISPATCH An IDispatch pointer is in pdispVal
10 VT_ERROR An SCODE is in scode
11 VT_BOOL A Boolean (True = 0xFFFF/False = 0) value is
in bool
12 VT_VARIANT Must be combined with VT_BYREF. A pointer
to a VARIANTARG is in pvarVal.
13 VT_UNKNOWN An Iunknown pointer is in punkVal
0×800 VT_RESERVED Reserved
0×400 VT_BYREF Can be combined with other VT values to
indicate values being passed by reference
0×200 VT_ARRAY Can be combined with other VT values, except
VT_EMPTY and VT_NULL, to indicate an array
of that datatype. The array descriptor is in
pByrefVal.

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

VariantChangeType Converts a variant from one type to another


VariantChangeTypeEx Converts a variant from one type to another according
to a locale identifier (LCID), an identifier used to
determine the text and data formatting conventions of a
particular geographical region
VariantClear Releases resources associated with a particular variant
and sets the variant to VT_EMPTY
VariantCopy Copies a variant
VariantCopyInd Copies a variant that may contain a pointer
VariantInit Initializes a variant
DosDateTimeToVariantTime Converts MS-DOS date and time representations to a
variant time
VariantTimeToDosDateTime Converts a variant time to MS-DOS date and time
representations
VariantTimeToSystemTime Converts a variant time to system date and time
representations
SystemTimeToVariantTime Converts system date and time representations to a
variant time

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
Understanding BSTRs

As you looked through the datatypes supported by variants and Automation,


you may have noticed two that were unfamiliar: BSTR and SAFEARRAY
datatypes. A BSTR, or binary string, is a pointer used to refer to
NULL-terminated string data. However, unlike traditional char*s, the length
of the BSTR is stored as an integer value that precedes any actual data (see
Figure 5-4).

Figure 5-4 Unlike a traditional char*, the length of a BSTR is stored as an


integer that precedes any actual data.
Even though the length of a BSTR precedes any actual data, note that the
BSTR pointer itself points to the memory location of the first byte of data.
Because BSTRs include their length, they are capable of containing binary
data that may include multiple NULL characters. On 16-bit systems, BSTRs
contain ANSI characters, while on 32-bit systems, BSTRs contain Unicode
characters. (See the sidebar “Understanding Character Sets” for more
information on different character sets.) Table 5-5 lists Win32 API functions
that are useful for working with BSTRs.
Table 5-5 Win32 API Functions Useful for Working with BSTRs

Function Purpose

SysAllocString Creates and initializes a BSTR


SysAllocStringByteLen Creates a NULL-terminated BSTR of a specified
length (32-bit only)
SysAllocStringLen Creates a BSTR of a specified length
SysFreeString Releases BSTR resources previously allocated with
SysAllocString
SysReAllocString Changes the size and value of an existing BSTR
SysReAllocStringLen Changes the size of an existing BSTR
SysStringByteLen Returns the length of a BSTR in bytes (32-bit only)
SysStringLen Returns the length of a BSTR

Understanding Character Sets


A character set is basically an encoding scheme in which each character in
the set is associated with a unique code that serves as its identifier. Character
sets are typically categorized according to the number of bytes they use to
represent each ID. A single-byte character set (SBCS) can use up to one byte
(8 bits) of storage to represent each character, while a double-byte character
set (DBCS) can use up to two bytes (16-bits) of storage to represent each
character. Because some of the characters of a DBCS only require one byte
of storage while others require two, DBCSs are often referred to as multibyte
character sets (MBCSs). The more bytes a particular character set uses for
each of its IDs, the more unique IDs are possible, and thus the more possible
characters the set can represent. For example, most of us are familiar with
the American National Standards Institute (ANSI) SBCS, which is a
fixed-width character set that uses eight bits for each character ID, which
means that it is only capable of representing 256 different character IDs:
0–255. Furthermore, there is more than one ANSI character set — Western
European, Eastern European, Baltic, Arabic, Hebrew, Greek, Turkish, etc.
Windows tracks the current ANSI character set in a code page; whichever
ANSI code page is currently loaded determines the character set used to
decode the character IDs. This not only makes it tough for developers, but
the numerous ANSI character sets also make it very difficult to create
multilingual documents. While room for 256 different characters is more
than adequate to represent the 26 characters of the English language, some
languages — like the Chinese language, with more than 10,000 characters —
have far greater requirements.
Rather than force developers to manage all of the various single- and
multibyte character sets, a group of vendors united to form the Unicode
Consortium, and they created the Unicode standard. Unicode is a fixed-width
character set that uses 16 bits for each character ID. The 16 bits of storage
means that Unicode has room for up to 65,536 unique character IDs.
Currently, Windows NT is the only version of Windows that uses Unicode as
its base character-encoding mechanism. However, because the Win32 API is
supported on Windows 3.x and Windows 95 as well as Windows NT, the
Win32 APIs provide two different entry points for each system function that
expects a string parameter: an ANSI version and a Unicode version, called a
wide-character version. Which entry point is used depends on whether or not
the compile-time UNICODE symbol is defined. If UNICODE is defined,
then the Unicode version of each Win32 API is used; otherwise, the ANSI
version is used.
This all works thanks to macro-magic in the Windows.h file. Windows.h
provides a macro for each Win32 API function, which expands to the
appropriate version, whether or not UNICODE is defined. Windows.h also
defines several useful generic text-mapping macros such as TEXT and T, as
well as several generic datatypes, such as TCHAR and LPTSTR. The TEXT
and T macros should be used to ensure that string and character literals are
appropriately defined as either ANSI or Unicode, depending on whether or
not UNICODE is defined. TCHAR and LPTSTR are generic datatypes that
should be used to replace references to the char and LPSTR datatypes. If
UNICODE is defined, TCHAR equates to a wchar_t or wide character,
which is essentially a Unicode character; otherwise, it equates to a char.
Likewise, LPTSTR equates to either a pointer to an ANSI string or a pointer
to a Unicode string, depending on whether or not UNICODE is defined.
These and other text-mapping macros and generic datatypes help developers
write a single source code base to the Win32 APIs, and still easily target
Unicode systems like Windows NT by simply defining the UNICODE
symbol at compile time.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

typedef struct tagSAFEARRAY


{
USHORT cDims;
USHORT fFeatures;
ULONG cbElements;
ULONG cLocks;
PVOID pvData;
SAFEARRAYBOUND rgsabound[ 1 ];
} SAFEARRAY;

typedef struct tagSAFEARRAYBOUND


{
ULONG cElements;
LONG lLbound;
} SAFEARRAYBOUND;
A SAFEARRAY can be used to store any of the variant-supported datatypes
except VT_ARRAY, VT_EMPTY, VT_NULL, and those types that have their
VT_BYREF flag set. Table 5-6 lists Win32 API functions that are useful for
working with SAFEARRAYs.
Table 5-6 Win32 API Functions Useful for Working with SAFEARRAYs

Function Purpose

SafeArrayAccessData Increments the lock count of an array and returns


a pointer to array data
SafeArrayAllocData Allocates memory for a safe array based on a
descriptor created with
SafeArrayAllocDescriptor
SafeArrayAllocDescriptor Allocates memory for a safe array descriptor
SafeArrayCopy Copies an existing safe array
SafeArrayCopyData Copies a source array to a target array after
releasing source resources
SafeArrayCreate Creates a new array descriptor
SafeArrayCreateVector Creates a one-dimensional array whose lower
bound is always zero
SafeArrayDestroy Destroys an array descriptor
SafeArrayDestroyData Frees memory used by the data elements in a
safe array
SafeArrayDestroyDescriptor Frees memory used by a safe array descriptor
SafeArrayGetDim Returns the number of dimensions in an array
SafeArrayGetElement Retrieves an element of an array
SafeArrayGetElemsize Returns the size of an element
SafeArrayGetLBound Retrieves the lower bound for a given dimension
SafeArrayGetUBound Retrieves the upper bound for a given dimension
SafeArrayLock Increments the lock count of an array
SafeArrayPtrOflndex Returns a pointer to an array element
SafeArrayPutElement Assigns an element to an array
SafeArrayRedim Resizes a safe array
SafeArrayUaccessData Frees a pointer to array data and decrements the
lock count of the array
SafeArrayUnlock Decrements the lock count of an array

Building an Automation Object


We will now expand on our introductory knowledge of Automation by taking
the AccountInfo COM object that we created in Chapter 4 and recreating it
as the AccountInfoAuto Automation object. However, to reduce any
potential confusion, AccountInfoAuto fully implements each of the
interfaces that it exposes, and doesn’t rely on any other COM objects.
Isolating Automation Specifics

The AccountInfoAuto object will provide the same basic information


regarding individual credit card accounts as the AccountInfo object, but in
an Automation-compatible manner. The AccountInfoAuto object exposes
the exact same IAccountInfo and IMemberInfo custom interfaces as the
AccountInfo object. However, the AccountInfoAuto object also
exposes the IAccountInfoDispatch dual interface. Table 5-7 lists the
properties defined by each AccountInfoAuto object-supported interface.
As you look at the interface definitions, you will notice that the
IAccountInfoDispatch dual interface is an Automation-compatible
combination of the IAccountInfo and IMemberInfo custom interfaces.
However, because IAccountInfoDispatch is a dual interface, it is
restricted to the Automation-compatible datatypes listed in Table 5-2 and
replaces the LPSTR datatype of the properties originally defined by the
IMemberInfo interface with the Automation-compatible, BSTR datatype.
Table 5-7 AccountInfoAuto Object Properties

Property Name Datatype Implemented In

Number long IAccountInfoDispatch


Balance float IAccountInfoDispatch
Limit long IAccountInfoDispatch
Name BSTR IAccountInfoDispatch
Sex unsigned char IAccountInfoDispatch
Address BSTR IAccountInfoDispatch
City BSTR IAccountInfoDispatch
State BSTR IAccountInfoDispatch
Zip BSTR IAccountInfoDispatch
Phone BSTR IAccountInfoDispatch
Number long IAccountInfo
Balance float IAccountInfo
Limit long IAccountInfo
Name LPSTR IMemberInfo
Sex unsigned char IMemberInfo
Address LPSTR IMemberInfo
City LPSTR IMemberInfo
State LPSTR IMemberInfo
Zip LPSTR IMemberInfo
Phone LPSTR IMemberInfo

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
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);

[propget, helpstring("Sets or returns the address of the


member.")]
HRESULT Address([out, retval] BSTR *bstrRetAddress);
[propput, helpstring("Sets or returns the address of the
member.")]
HRESULT Address([in] BSTR bstrAddress);
[propget, helpstring("Sets or returns the city of the
member.")]
HRESULT City([out, retval] BSTR *bstrRetCity);
[propput, helpstring("Sets or returns the city of the
member.")]
HRESULT City([in] BSTR bstrCity);
[propget, helpstring("Sets or returns the name of the
member.")]
HRESULT Name([out, retval] BSTR *bstrRetName);
[propput, helpstring("Sets or returns the name of the
member.")]
HRESULT Name([in] BSTR bstrName);
[propget, helpstring("Sets or returns the phone number of the
member.")]
HRESULT Phone([out, retval] BSTR *bstrRetPhone):
[propput, helpstring("Sets or returns the phone number of the
member.")]
HRESULT Phone([in] BSTR bstrPhone);
[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] BSTR *bstrRetState);
[propput, helpstring("Sets or returns the state of the
member.")]
HRESULT State([in] BSTR bstrState);
[propget, helpstring("Sets or returns the zip code of the
member.")]
HRESULT Zip([out, retval] BSTR *bstrRetZip);
[propput, helpstring("Sets or returns the zip code of the
member.")]
HRESULT Zip([in] BSTR bstrZip);
}
Because the AccountInfoAuto object supports several interfaces, we use the IDL keyword
default in the definition of the AccountInfoAuto coclass, to indicate
IAccountInfoDispatch as the default interface to be used by Automation controllers:
//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 IAccountInfoAuto;
interface IMemberInfoAuto;
};
}

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//Use the custom interface


hr = get_Address(&szAddress);
if (SUCCEEDED (hr))
{
//Allocate enough storage for the string
lStringLen = strlen(szAddress);
if (lStringLen > 0)
{
olestrAddress = new OLECHAR[lStringLen + 1];
//Convert from the multibyte character set to the wide
//character set
mbstowcs(olestrAddress, szAddress, lStringLen + 1);
}
}
*bstrRetAddress = SysAllocString(olestrAddress);
return hr;
}//get_Address

//
//put_Address
//
STDMETHODIMP CAccountInfoAuto::put_Address(BSTR bstrAddress)
{
HRESULT hr;
LPSTR szAddress = NULL;
long lStringLen;

//Allocate enough storage for the string


if (bstrAddress)
lStringLen = SysStringLen(bstrAddress);
if (lStringLen > 0)
{
szAddress = new char[lStringLen + 1];
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrAddress, -1, szAddress,
lStringLen + 1, NULL, NULL);
}
//Use the custom interface
hr = put_Address(szAddress);
return hr;
}//put_Address
.
.
.
//
//get_Address
//
STDMETHODIMP CAccountInfoAuto::get_Address(LPSTR *lpszRetAddress)
{
*lpszRetAddress = m_lpszAddress;
return NOERROR;
}//get_Address

//
//put_Address
//
STDMETHODIMP CAccountInfoAuto::put_Address(LPSTR lpszAddress)
{
long lStringLen;

//Deallocate any previously allocated storage


if (m_lpszAddress)
delete[] m_lpszAddress;
m_lpszAddress = NULL;
//Allocate enough storage for the string
lStringLen = strlen(lpszAddress);
if (lStringLen > 0)
{
m_lpszAddress = new char[lStringLen + 1];
//Copy the string
strcpy(m_lpszAddress, lpszAddress);
}
return NOERROR;
}//put_Address

Exposing a Type Library

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:

1 TYPELIB MOVEABLE PURE "AccountInfoAuto.tlb"

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

STDMETHODIMP CAccountInfoAuto::GetTypeInfoCount(UINT *pctinfo)


{
//1 if the object does provide type information
//0 if the object does not provide type information

*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;

//load the type information for the IAccountInfoDispatch


//dispatch interface
hr = ::LoadTypeInfo(&m_pTypeInfo,
LIBID_AccountInfoAuto, IID_IAccountInfoDispatch, 0);

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

HRESULT LoadTypeInfo(ITypeInfo **pptinfo, REFGUID rguid,


REFCLSID clsid, LCID lcid)
{
HRESULT hr;
_TCHAR szModuleName[MAX_STRING_LENGTH];
wchar_t wszModuleName[MAX_STRING_LENGTH];
LPTYPELIB ptlib = NULL;
LPTYPEINFO ptinfo = NULL;

*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;

//This will ensure that the type library is registered


//for next time.
hr = RegisterTypeLib(ptlib, wszModuleName, NULL) ;
if(FAILED(hr))
return hr;
}
// Get type information for interface of the object.
hr = ptlib->GetTypeInfoOfGuid(clsid, &ptinfo);
if (FAILED(hr))
{
ptlib->Release();
return hr;
}
ptlib->Release();
*pptinfo = ptinfo;

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.

STDMETHODIMP CAccountInfoAuto::GetTypeInfo(UINT itinfo,


LCID lcid, ITypeInfo **ppinfo)
{

*ppinfo = NULL;

//if not trying to get type information about IDispatch


if(itinfo != 0)
return DISP_E_BADINDEX;

//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;

return DispGetIDsOfNames(m_pTypeInfo, rgszNames, cNames,


rgdispid);
}//GetIDsOfNames

//
//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;

return DispInvoke(this, m_pTypeInfo, dispidMember,


wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
}//Invoke

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

STDMETHODIMP CAccountInfoAuto::QueryInterface(REFIID iid,


LPVOID *ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid)
*ppv = (LPVOID)(IUnknown *)(IAccountInfoDispatch *)this;
else if (IID_IDispatch == iid)
*ppv = (LPVOID)(IDispatch *)(IAccountInfoDispatch *)this;
else if (IID_IAccountInfo == iid)
*ppv = (LPVOID)(IAccountInfo *)this;
else if (IID_IMemberInfo == iid)
*ppv = (LPVOID)(IMemberInfo *)this;
else if (IID_IAccountInfoDispatch == iid)
*ppv = (LPVOID)(IAccountInfoDispatch *)this;
else
return E_NOINTERFACE; //Interface not supported
//Perform reference count through the returned interface
((IUnknown *)*ppv)->AddRef();
return NOERROR;
}//QueryInterface
The rest of the implementation of the AccountInfoAuto object is pretty straight-forward:
Complete the implementation of the various interface functions, provide a class factory for the
AccountInfoAuto object, and fill out DLLMain.cpp. Therefore, we will turn our attention
to the additional registry entries that are required of Automation objects.

Registering an Automation Object

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:

Set objWord6Doc = CreateObject("Word.Document.6")


Set objWordDoc = CreateObject("Word.Document")
ProgIDs can be up to 39 characters long, and their entries must be placed under the object’s
CLSID key:

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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

IN THE LAST chapter we built the AccountInfoAuto Automation object. The


AccountInfoAuto object supports the IAccountInfoDispatch dual interface, which
means that IAccountInfoDispatch can be accessed both at compile time via VTBL
binding as well as at run time via IDispatch. In addition, AccountInfoAuto also supports
the IAccountInfo and IMemberInfo custom interfaces, which may only be bound to at
compile time via VTBL binding. You already learned how to access custom interfaces via VTBL
binding by building the UserInfoClient application in Chapter 2. The UserInfoClient
was able to manipulate the UserInfo COM object by using the IUserInfo custom interface.
Therefore, in this chapter, we will focus on how to access the VTBL side of the
IAccountInfoDispatch dual interface, by building the AccountInfoAutoVTBL
application. You will also learn how to access the IDispatch side of the
IAccountInfoDispatch dual interface, by building the AccountInfoAutoDisp
application. Let’s start by taking a look at the AccountInfoAutoVTBL application first.

Building the AccountInfoAutoVTBL Application


The AccountInfoAutoVTBL application is a simple application that will create and use the
AccountInfoAuto object that we developed in the last chapter. Once the
AccountInfoAuto object is created, we will use the VTBL-side of the
IAccountInfoDispatch dual interface to set each of the object’s properties. After each
property has been set, we will again use the VTBL side of the IAccountInfoDispatch dual
interface to retrieve the various properties of the AccountInfoAuto object. Once all of the
properties have been retrieved, we will format them and display their values in a small window,
via the MessageBox Win32 API function. The various properties defined for the
AccountInfoAuto object can be seen below in Table 6-1.
Table 6-1 AccountInfoAuto Object Properties

Property Name Datatype

Number long
Balance float
Limit long
Name BSTR
Sex unsigned char
Address BSTR
City BSTR
State BSTR
Zip BSTR
Phone BSTR

We will build the AccountInfoAutoVTBL application by following the responsibilities of a


COM client. In Chapter 2, you learned that a COM client is responsible for the following:
• 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

Initializing the COM Library

When the AccountInfoAutoVTBL application is first loaded, Windows invokes the


WinMain application entry-point function. Once inside WinMain, the
AccountInfoAutoVTBL application’s first responsibility is to initialize the COM library
with a call to the CoInitialize COM API function:

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;
IAccountInfoDispatch *pIAccountInfoDispatch = NULL;

//Initialize the COM Library


hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("The COM Library has been
initialized."));
.
.
.
}
else
DisplayMessage(_TEXT("The COM Library initialization
failed."));

Obtaining an Initial Interface

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:

DisplayMessage(_TEXT("The COM Library has been


initialized."));

//Ask the COM Library to instantiate the


//AccountInfoAuto object and return us an initial

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
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("The AccountInfoAuto object


has been created."));
//Begin using the object

//QueryInterface for the IAccountInfoAuto interface


hr = pIUnknown->QueryInterface
(IID_IAccountInfoDispatch,
(LPVOID *)&pIAccountInfoDispatch);
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("Changed to the
IAccountInfoDispatch interface."));
.
.
.
}
else
DisplayMessage(_TEXT("Couldn't change to the
IAccountInfoDispatch interface."));
The process of setting the various properties of the IAccountInfoDispatch dual
interface from the VTBL side is the same as setting the properties of a custom interface, the
only difference being that dual interfaces are restricted to Automation-compatible datatypes
only. (See Table 5-2 for a list of Automation-compatible datatypes.) The Win32 API provides
functions for manipulating some of the more specialized Automation datatypes, such as the
BSTR and the SAFEARRARY. Table 5-5 lists Win32 API functions that are useful for
working with BSTRs, while Table 5-6 lists Win32 API functions that are useful for working
with SAFEARRAYs.
Before we can set each property of the IAccountInfoDispatch interface, we need to
initialize the various BSTR variables that will be used to transfer the actual value of each
BSTR property. The BSTR transfer variable for each BSTR property is initialized using the
SysAllocString Win32 API function. SysAllocString takes a pointer to an
OLESTR as its only parameter, and returns a BSTR. OLESTRs are created using the OLESTR
macro, and are essentially wide-character strings. (See the sidebar “Understanding Character
Sets” in Chapter 5 for more information on different character sets.)

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:

//Set each property


pIAccountInfoDispatch->put_Number(333);
pIAccountInfoDispatch->put_Balance(250.25);
pIAccountInfoDispatch->put_Limit(1000);
pIAccountInfoDispatch->put_Address(bstrAddress);
pIAccountInfoDispatch->put_City(bstrCity);
pIAccountInfoDispatch->put_Name(bstrName);
pIAccountInfoDispatch->put_Phone(bstrPhone);
pIAccountInfoDispatch->put_Sex('M');
pIAccountInfoDispatch->put_State(bstrState);
pIAccountInfoDispatch->put_Zip(bstrZip);

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//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);

//Format the Account number


//Convert the long to a string
_ltot(lAccountNumber, szAccountNumber, 10);
_tcscpy(szDisplayText, _TEXT("Account Number: "));
_tcscat(szDisplayText, szAccountNumber);
//Add a carriage return
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');

//Format the Account balance


//Convert the float to a string
_stprintf(szAccountBalance, _TEXT("%-8.2f"), flAccountBalance);
_tcscat(szDisplayText, _TEXT("Account Balance: "));
_tcscat(szDisplayText, szAccountBalance);
//Add a carriage return
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');

//Format the Account limit


//Convert the long to a string
_ltot(lAccountLimit, szAccountLimit, 10);
_tcscat(szDisplayText, _TEXT("Account Limit: "));
_tcscat(szDisplayText, szAccountLimit);
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');

//Format the Name


_tcscat(szDisplayText, _TEXT("Name: "));
if (bstrName)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrName);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrName, -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 Sex


_tcscat(szDisplayText, _TEXT("Sex: "));
#ifdef _UNICODE
//UNICODE
mbtowc(&charSex, (LPCH)&bySex, 1);
#else
//SBCS and MBCS
charSex = bySex;
#endif
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = charSex;
//Add a carriage return
szDisplayText[lStringLen + 1] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 2] = _T('\0');

//Format the Address


_tcscat(szDisplayText, _TEXT("Street Address: "));
if (bstrAddress)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrAddress);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrAddress, -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 City


_tcscat(szDisplayText, _TEXT("City: "));
if (bstrCity)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrCity);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrCity, -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 State


_tcscat(szDisplayText, _TEXT("State: "));
if (bstrState)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrState);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrState, -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 Zip


_tcscat(szDisplayText, _TEXT("Zip: "));
if (bstrZip)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrZip);

#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

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
Releasing the COM Object

After DisplayAccountInfoDispatch is called to retrieve, format, and display the various


IAccountInfoDispatch properties, WinMain continues by calling the Release function
on all of its outstanding interface pointers, which includes one IUnknown pointer and one
IAccountInfoDispatch pointer. At that point, the AccountInfoAuto object destroys
itself:

//Release the IAccountInfoAuto interface


pIAccountInfoDispatch->Release();
DisplayMessage(_TEXT("Released the
IAccountInfoDispatch interface."));
.
.
.
//Release the IUnknown interface
pIUnknown->Release();
DisplayMessage(_TEXT("Released the Iunknown
interface."));

Uninitializing the COM Library

Once all the outstanding interface references have been Released, all that’s left to do is
uninitialize the COM library by calling CoUninitialize:

//Shut down the COM Library


CoUninitialize();
DisplayMessage(_TEXT("Shut down the COM Library."));
The source code for the AccountInfoAutoVTBL application can be seen in Listing 6-2.
Listing 6-2. AccountInfoAutoVTBL.cpp

//
//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);

//Format the Account number


//Convert the long to a string
_ltot(lAccountNumber, szAccountNumber, 10);
_tcscpy(szDisplayText, _TEXT("Account Number: "));
_tcscat(szDisplayText, szAccountNumber);
//Add a carriage return
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');

//Format the Account balance


//Convert the float to a string
_stprintf(szAccountBalance, _TEXT("%-8.2f"),
flAccountBalance);
_tcscat(szDisplayText, _TEXT("Account Balance: "));
_tcscat(szDisplayText, szAccountBalance);
//Add a carriage return
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');

//Format the Account limit


//Convert the long to a string
_ltot(lAccountLimit, szAccountLimit, 10);
_tcscat(szDisplayText, _TEXT("Account Limit: "));
_tcscat(szDisplayText, szAccountLimit);
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');

//Format the Name


_tcscat(szDisplayText, _TEXT("Name: "));
if (bstrName)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrName);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrName, -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 Sex


_tcscat(szDisplayText, _TEXT("Sex: "));
#ifdef _UNICODE
//UNICODE
mbtowc(&charSex, (LPCH)&bySex, 1);
#else
//SBCS and MBCS
charSex = bySex;
#endif
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = charSex;
//Add a carriage return
szDisplayText[lStringLen + 1] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 2] = _T('\0');

//Format the Address


_tcscat(szDisplayText,_TEXT("Street Address: "));
if (bstrAddress)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrAddress);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrAddress, -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 City


_tcscat(szDisplayText, _TEXT("City: "));
if (bstrCity)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrCity);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrCity, -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 State


_tcscat(szDisplayText, _TEXT("State: "));
if (bstrState)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrState);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrState, -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 Zip


_tcscat(szDisplayText, _TEXT("Zip: "));
if (bstrZip)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrZip);
#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

//
//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;

//Initialize the COM Library


hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("The COM Library has been
initialized."));

//Ask the COM Library to instantiate the AccountInfoAuto


//object and return us an initial 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."));
//Begin using the object

//QueryInterface for the IAccountInfoAuto interface


hr = pIUnknown->QueryInterface
(IID_IAccountInfoDispatch,
(LPVOID *)&pIAccountInfoDispatch);
if (SUCCEEDED(hr))
{
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


pIAccountInfoDispatch->put_Number(333);
pIAccountInfoDispatch->put_Balance(250.25);
pIAccountInfoDispatch->put_Limit(1000);
pIAccountInfoDispatch->put_Address(bstrAddress);
pIAccountInfoDispatch->put_City(bstrCity);
pIAccountInfoDispatch->put_Name(bstrName);
pIAccountInfoDispatch->put_Phone(bstrPhone);
pIAccountInfoDispatch->put_Sex('M');
pIAccountInfoDispatch->put_State(bstrState);
pIAccountInfoDispatch->put_Zip(bstrZip);

SysFreeString(bstrAddress);
SysFreeString(bstrCity);
SysFreeString(bstrName);
SysFreeString(bstrPhone);
SysFreeString(bstrState);
SysFreeString(bstrZip);

DisplayAccountInfoDispatch(pIAccountInfoDispatch,
_TEXT("Each property has been retreived."));

//Release the IAccountInfoAuto interface


pIAccountInfoDispatch->Release();
DisplayMessage(_TEXT("Released the
IAccountInfoDispatch interface."));
}
else
DisplayMessage(_TEXT("Couldn't change to the
IAccountInfoDispatch interface."));

//Release the IUnknown interface


pIUnknown->Release();
DisplayMessage(_TEXT("Released the Iunknown
interface."));
}

else
DisplayMessage(_TEXT("The AccountInfoAuto object
couldn't be created."));

//Shut down the COM Library


CoUninitialize();
DisplayMessage(_TEXT("Shut down the COM Library."));
}
else
DisplayMessage(_TEXT("The COM Library initialization
failed."));

DisplayMessage(_TEXT("Terminating the Application."));


//Terminate the application
return FALSE;
}//WinMain
Working with the VTBL side of a dual interface is very similar to working with custom
interfaces. However, because dual interfaces are restricted to Automation-compatible datatypes
only, you will have to get acquainted with the various Win32 APIs useful for working with some
of the more specialized Automation datatypes, such as the BSTR and the SAFEARRAY.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Setting Property Values Using IDispatch

Like the AccountInfoAutoVTBL application, the AccountInfoAutoDisp application begins


by initializing the COM library, creating the AccountInfoAuto object, and obtaining an initial
interface pointer to the AccountInfoAuto object’s IUnknown interface. However, instead of
using IUnknown::QueryInterface to obtain an IAccountInfoDispatch interface
pointer, the AccountInfoAutoDisp application uses IUknown::QueryInterface to obtain
an IDispatch interface pointer. After retrieving the IDispatch interface pointer, the
AccountInfoAutoDisp application uses it to set each of the properties defined by the
AccountInfoAuto object. However, before we can set any of the AccountInfoAuto object’s
properties using IDispatch, we must have the dispatch identifier (DISPID) for each individual
property. Each DISPID can be obtained by calling IDispatch::GetIDsOfNames with the name
of each property. In order to automate the process of obtaining the DISPID for each property, I have
defined a global array of wide-character strings containing the names of each individual property:

//
//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:

//Get the DISPID for the each property


for (index = 0; index < 10; index++)
pIDispatch->GetIDsOfNames(IID_NULL, &rgszNames[index], 1,
LOCALE-SYSTEM-DEFAULT, &dispids[index]);
Once the DISPID for each property has been retrieved, we can use them to read and write each
property. Reading and writing an individual property is a two-step process:
• Fill in a DISPPARAMS structure with information regarding any parameters expected by the
property or method.
• Make the actual call to the property or method using IDispatch::Invoke.
The DISPPARAMS structure is used to send arguments into properties and methods.

typedef struct FARSTRUCT tagDISPPARAMS{


VARIANTARG FAR* rgvarg; // Array of arguments.
DISPID FAR* rgdispidNamedArgs; // Dispatch IDs of named
arguments.
unsigned int cArgs; // Number of arguments.
unsigned int cNamedArgs; // Number of named arguments.
} DISPPARAMS;
Note that rgvarg identifies an array of VARIANT argument values and cArgs identifies the
number of arguments supplied in the rgvarg array. Arguments may be passed by position and/or by
name. To pass arguments by position, package each argument into a VARIANT and store the
VARIANTs in the rgvarg array in the reverse order of their positions, such that the last positional
argument is in rgvarg[0] and the first positional argument is in rgvarg [cArgs - 1]. (See Figure
6-1.)

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]

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.

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

bstrAddress = SysAllocString(OLESTR("1 One Way"));


.
.
.
//Address
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = bstrAddress;
pIDispatch->Invoke(dispids[3], IID_NULL,
LOCALE-SYSTEM-DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
.
.
.
SysFreeString(bstrAddress);
Once a property’s value has been supplied, IDispatch::Invoke is called to actually
update its value. The first parameter expected by IDispatch::Invoke is the DISPID of
the property or method being accessed. The second parameter is reserved, and must be
IID_NULL, while the third parameter specifies the ID of the locale to be used to interpret
any arguments supplied in the call. To avoid naming conflicts between properties and
methods with the same name on the same Automation object, the fourth parameter to
IDispatch::Invoke provides a way for the Automation controller to specify the
context of a call, and may be one of the values in Table 6-2:
Table 6-2 Defined IDispatch::Invoke Context Constants

Context Constant Description

DISPATCH_METHOD Accesses a method. This value may be combined with


DISPATCH_PROPERTYGET to indicate a property with
the same name.
DISPATCH_PROPERTYGET Returns the value of a property
DISPATCH_PROPERTYPUT Assigns a new value to a property
DISPATCH_PROPERTYPUTREF Assigns a valid object reference, rather than a value, to a
property.

The fifth parameter expected by IDispatch::Invoke is the address of the


DISPPARAMS structure containing the parameter information. The sixth parameter is used
to store any expected return results. The seventh parameter is used to gather any exception
information that may be returned and, finally, the eighth parameter is used to return
information about the first invalid argument. Because all arguments are supplied as
VARIANTs, the Automation client is responsible for converting data from one
VARIANT-supported datatype to another where appropriate.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
Retrieving Property Values Using IDispatch

IDispatch::Invoke is also used to read property values. The AccountInfoAutoDisp


application’s implementation of the DisplayAccountInfoDispatch function has been
modified to use IDispatch::Invoke to read each property value before formatting and
displaying them. DISPATCH_PROPERTYGET is used to indicate that we are interested in receiving
the value of a property. Notice how dispparamsNoArgs is configured, with its pointer elements
set to NULL and its numeric elements set to zero, reflecting that fact that no arguments are required
to read a property. The return value of each property is actually returned as a VARIANT in
vRetVal, the sixth parameter of IDispatch::Invoke:

.
.
.
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
VARIANT vRetVal;

//Retrieve each property


//Number
pIDispatch->Invoke(dispids[0], IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET, &dispparamsNoArgs, &vRetVal,
NULL, NULL);
lAccountNumber = vRetVal.lVal;
.
.
.
The source code for AccountInfoAutoDisp can be seen in Listing 6-3.
Listing 6-3. AccountInfoAutoDisp.cpp

//
//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;

//Retrieve each property


//Number
pIDispatch->Invoke(dispids[0], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
lAccountNumber = vRetVal.lVal;
//Balance
pIDispatch->Invoke(dispids[1], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
flAccountBalance = vRetVal.fltVal;
//Limit
pIDispatch->Invoke(dispids[2], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
lAccountLimit = vRetVal.lVal;
//Address
pIDispatch->Invoke(dispids[3], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
bstrAddress = vRetVal.bstrVal;
//City
pIDispatch->Invoke(dispids[4], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
bstrCity = vRetVal.bstrVal;
//Name
pIDispatch->Invoke(dispids[5], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
bstrName = vRetVal.bstrVal;
//Phone
pIDispatch->Invoke(dispids[6], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
bstrPhone = vRetVal.bstrVal;
//Sex
pIDispatch->Invoke(dispids[7], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
bySex = (unsigned char)vRetVal.iVal;
//State
pIDispatch->Invoke(dispids[8], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
bstrState = vRetVal.bstrVal;
//Zip
pIDispatch->Invoke(dispids[9], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, &vRetVal, NULL, NULL);
bstrZip = vRetVal.bstrVal;

DisplayMessage(lpszRetrievedMsg);

//Format the Account number


//Convert the long to a string
_ltot(lAccountNumber, szAccountNumber, 10);
_tcscpy(szDisplayText, _TEXT("Account Number: "));
_tcscat(szDisplayText, szAccountNumber);
//Add a carriage return
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');

//Format the Account balance


//Convert the float to a string
_stprintf(szAccountBalance, _TEXT("%-8.2f"),
flAccountBalance);
_tcscat(szDisplayText, _TEXT("Account Balance: "));
_tcscat(szDisplayText, szAccountBalance);
//Add a carriage return
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');

//Format the Account limit


//Convert the long to a string
_ltot(lAccountLimit, szAccountLimit, 10);
_tcscat(szDisplayText, _TEXT("Account Limit: "));
_tcscat(szDisplayText, szAccountLimit);
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 1] = _T('\0');

//Format the Name


_tcscat(szDisplayText, _TEXT("Name: "));
if (bstrName)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrName);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrName, -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 Sex


_tcscat(szDisplayText, _TEXT("Sex: "));
#ifdef _UNICODE
//UNICODE
mbtowc(&charSex, (LPCH)&bySex, 1);
#else
//SBCS and MBCS
charSex = bySex;
#endif
lStringLen = _tcsclen(szDisplayText);
szDisplayText[lStringLen] = charSex;
//Add a carriage return
szDisplayText[lStringLen + 1] = _T('\r');
//Null terminate the string
szDisplayText[lStringLen + 2] = _T('\0');

//Format the Address


_tcscat(szDisplayText, _TEXT("Street Address: "));
if (bstrAddress)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrAddress);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrAddress, -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 City


_tcscat(szDisplayText, _TEXT("City: "));
if (bstrCity)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrCity);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrCity, -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 State


_tcscat(szDisplayText, _TEXT("State: "));
if (bstrState)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrState);
#else
//SBCS and MBCS
//Convert from the wide character set to the multibyte
//character set
WideCharToMultiByte(CP_ACP, 0, bstrState, -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 Zip


_tcscat(szDisplayText, _TEXT("Zip: "));
if (bstrZip)
{
#ifdef _UNICODE
//UNICODE
_tcscpy(szConvertedText, bstrZip);
#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

//
//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."));

//Ask the COM Library to instantiate the AccountInfoAuto


//object and return us an initial 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."));
//Begin using the object

//QueryInterface for the IDispatch interface


hr = pIUnknown->QueryInterface(IID_IDispatch,
(LPVOID *)&pIDispatch);
if (SUCCEEDED(hr))
{
DisplayMessage(_TEXT("Changed to the IDispatch
interface."));

//Get the DISPID for the each property


for (index = 0; index < 10; index++)
pIDispatch->GetIDsOfNames(IID_NULL,
&rgszNames[index], 1,
LOCALE_SYSTEM_DEFAULT, &dispids[index]);

//Initialize the dispatch parameters


dispparams.rgvarg = &varg[0];
dispparams.rgdispidNamedArgs =
&dispidsNamedArgs[0];
dispparams.cArgs = 1;
dispparams.cNamedArgs = 1;

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


//Number
dispparams.rgvarg[0].vt = VT_14;
dispparams.rgvarg[0].lVal = 333;
pIDispatch->Invoke(dispids[0], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
//Balance
dispparams.rgvarg[0].vt = VT_R4;
dispparams.rgvarg[0].fltVal = 250.25;
pIDispatch->Invoke(dispids[1], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
//Limit
dispparams.rgvarg[0].vt = VT_14;
dispparams.rgvarg[0].lVal = 1000;
pIDispatch->Invoke(dispids[2], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
//Address
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = bstrAddress;
pIDispatch->Invoke(dispids[3], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
//City
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = bstrCity;
pIDispatch->Invoke(dispids[4], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
//Name
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = bstrName;
pIDispatch->Invoke(dispids[5], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
//Phone
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = bstrPhone;
pIDispatch->Invoke(dispids[6], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
//Sex
dispparams.rgvarg[0].vt = VT_12;
dispparams.rgvarg[0].iVal = (short)'M';
pIDispatch->Invoke(dispids[7], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
//State
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = bstrState;
pIDispatch->Invoke(dispids[8], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
//Zip
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = bstrZip;
pIDispatch->Invoke(dispids[9], IID_NULL,
LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT
&dispparams, NULL, NULL, NULL);
SysFreeString(bstrAddress);
SysFreeString(bstrCity);
SysFreeString(bstrName);
SysFreeString(bstrPhone);
SysFreeString(bstrState);
SysFreeString(bstrZip);

DisplayAccountInfoDispatch(pIDispatch,
_TEXT("Each property has been retreived."));

//Release the IDispatch interface


pIDispatch->Release();
DisplayMessage(
_TEXT("Released the IDispatch interface."));
}
else
DisplayMessage(_TEXT("Couldn't change to the
IDispatch interface."));

//Release the IUnknown interface


pIUnknown->Release();
DisplayMessage(_TEXT("Released the IUnknown
interface."));
}
else
DisplayMessage(_TEXT("The AccountInfoAuto object
couldn't be created."));

//Shut down the COM Library


CoUninitialize();
DisplayMessage(_TEXT("Shut down the COM Library."));
}
else
DisplayMessage(_TEXT("The COM Library initialization
failed."));

DisplayMessage(_TEXT("Terminating the Application."));


//Terminate the application
return FALSE;
}//WinMain

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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.

Defining an Object Hierarchy


The various objects of the OrderEntry object hierarchy fall into one of two
broad categories: entry objects and collection objects. Entry objects are used to
convey relatively static information about an individual entry in the data
source and are typically named according to the information they convey. For
example, the Account entry object would be used to convey information
about a particular customer account in the underlying data source. Collection
objects are used to manage and represent multiple data source entries of a
particular type and are typically given the plural version of the entry object
names they represent. For example, the Accounts collection object would be
used to maintain multiple customer account entries. As Figure 7-1 illustrates,
the OrderEntry object hierarchy defines four different types of entry objects:
the Account object, the Product object, the Invoice object, and the
LineItem object; and four different types of collection objects: the
Accounts object, the Products object, the Invoices object, and the
LineItems object. Information for each type of entry object is stored in a
different table in the underlying OrderEntry.mdb Microsoft Access database.
Information for the Account object is stored in the Accounts table; the
Product object’s information is in the Products table; the Invoice
object’s information is in the Invoices table; and the LineItem object’s
information is in the LineItems table.

Figure 7-1 The OrderEntry object hierarchy

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
The Account Object

The Account object is used to provide information regarding a particular


customer account, similar to that provided by the AccountInfoAuto object
developed in Chapter 5. Even though the Account object and
AccountInfoAuto object share many resemblances, we will develop the
Account object from the ground up as opposed to reusing Chapter 5’s
AccountInfoAuto object. The Account object maintains the information
given in Table 7-1 for each customer’s account.
Table 7-1 Properties of 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

The InService property is used to determine if a particular account has


been removed from service. Individual accounts are removed from service, as
opposed to being deleted from the system. By deactivating an account, the
company is not only able to maintain records of every customer it has ever
had, it is also able to maintain data integrity between the Accounts and
Invoices database tables. The InService property has a
VARIANT_BOOL datatype, which, like a C/C++ Boolean, is used to reflect
values that are either true or false. However, unlike C/C++ Booleans that use
zero to represent FALSE and nonzero to represent TRUE, VARIANT_BOOLs
use negative one (-1) to represent TRUE and zero (0) to represent FALSE. The
properties of the Account object are designed to reflect the columns of the
underlying Accounts database table as closely as possible (see Table 7-2).
Table 7-2 Columns of the Accounts Database Table

Column Name Datatype

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

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

Again, notice the InService property, which is used to determine if an


individual product has been removed from service. Products no longer
available for purchase are removed from service, not deleted, which helps to
maintain data integrity between the Products and Invoices database
tables. Table 7-4 lists the columns of the Products database table. Again,
notice how the columns of the Products database table reflect the properties
of the Product object.
Table 7-4 Columns of the Products Database Table

Column Name Datatype

Number long
Description text(40)
Price single
Stock long
InService Boolean

The Invoice Object

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*

The CustomerAccount property can be used in conjunction with the


Accounts object to determine for a particular invoice the account of the
customer making the purchase. The internal LineItems object, which is
exposed through the LineItems property, provides a mechanism for
iterating through the list of products that were purchased as part of an invoice,
and also determining how many units of each product were purchased. Table
7-6 lists the columns of the Invoices database table.
Table 7-6 Columns of the Invoices Database Table

Column Name Datatype

Number long
EntryDate Date/Time
AccountNumber long

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Table 7-8 lists the columns of the LineItems database table.


Table 7-8 Columns of the LineItems Database Table

Column Name Datatype

Number long
InvoiceNumber long
ProductNumber long
UnitsPurchased long

While each entry object is responsible for conveying information regarding an


individual entry in the data source, none of the entry objects actually
manipulate the underlying data source. Instead, the various collection objects
are responsible for all data source manipulation. Each collection object
provides the same basic functionality, allowing you to add new entries to the
underlying data source; update existing entries; remove specific entries; and
locate and retrieve specific entries. Whenever a collection object is used to
return information regarding a specific entry, it first creates a blank entry
object and initializes it with information retrieved from the underlying data
source. The following sections briefly describe the various collection objects
of the hierarchy.

The Accounts Object

The functionality provided by the Accounts object is listed in Table 7-9.


Table 7-9 Properties and Methods of the Accounts Object

Property Datatype Description

BOF short Returns a Boolean value used to


determine whether the current
record pointer has gone beyond
the beginning of the record set.
EOF short Returns a Boolean value used to
determine whether the current
record pointer has gone beyond
the end of the record set.
Item(long IAccount* Returns a pointer to the Account
AccountNumber) object identified by
AccountNumber, the account’s
unique number. The Item
property also allows the user to
access the Account identified by
the current record pointer, by
calling Item with -1.
Count long Returns the number of accounts in
the data source.
Method Description
Add(IAccount*) Used to add the account defined by the incoming
Account object to the data source.
Update(IAccount*) Used to modify an existing account entry in the data
source.
Remove(long Used to remove the account identified by
AccountNumber) AccountNumber, the account’s unique number, from
service, such that it can no longer be used.
Move(long INumRecs) Enables the user to reposition the current record
pointer INumRecs to the next record in the record set.
MoveFirst Enables the user to reposition the current record
pointer to the first record in the record set.
MoveLast Enables the user to reposition the current record
pointer to the last record in the record set.
MovePrev Enables the user to reposition the current record
pointer to the previous record in the record set.
MoveNext Allows the user to reposition the current record
pointer to the next record in the record set.

The Item property of the Accounts, Products, Invoices, and


LineItems objects can be used in two different ways. In the first scenario,
the Item property can be used to return information regarding a specific
customer account identified by the customer’s account number. The following
code shows how a client written in Visual Basic might use the Accounts
object’s Item property in the previously described manner:

'Return information about account #333


Set objAccount = g_objAccounts.Item(333)
In the second scenario, the Item property is used in conjunction with the five
navigation methods, Move, MoveFirst, MoveLast, MovePrev, or
MoveNext, to retrieve information for the account identified by the current
record pointer. The following code illustrates this second scenario from a
Visual Basic client’s perspective:

'Move to the first record


g_objAccounts.MoveFirst
If Not g_objAccounts.EOF Then
'Return the information about the first account
Set objAccount = g_objAccounts.Item
End If
Regardless of which technique is used, whenever the Item property must
return an individual object, the Accounts object first creates a blank
Account object, then retrieves the account information from the underlying
data source and uses it to initialize the newly created Account object.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
The Products Object

The Products object is used to interact with the underlying Products


database table in much the same way that an Accounts object is used to
interact with the underlying Accounts database table. The functionality
provided by the Products object is listed in Table 7-10.
Table 7-10 Properties and Methods of the Products Object

Property Datatype Description

BOF short Returns a Boolean value used to


determine whether the current
record pointer has gone beyond
the beginning of the record set.
EOF short Returns a Boolean value used to
determine whether the current
record pointer has gone beyond
the end of the record set.
Item(long IProduct* Returns a pointer to the Product
AccountNumber) object identified by
ProductNumber, the product’s
unique number. The Item
property also allows the user to
access the Product identified by
the current record pointer, by
calling Item with -1.
Count long Returns the number of products in
the data source.
Method Description
Add(IProduct*) Used to add the account defined by the incoming
Product object to the data source.
Used to modify an existing product entry in the data
Update(IProduct*)
source.
Remove(long Used to remove the product identified by
ProductNumber) ProductNumber, the product’s unique number, from
service, such that it can no longer be purchased.
Move(long INumRecs) Enables the user to reposition the current record
pointer INumRecs to the next record in the record set.
MoveFirst Enables the user to reposition the current record
pointer to the first record in the record set.
MoveLast Enables the user to reposition the current record
pointer to the last record in the record set.
MovePrev Enables the user to reposition the current record
pointer to the previous record in the record set.
MoveNext Enables the user to reposition the current record
pointer to the next record in the record set.

The Invoices Object

The Invoices object is used to interact with the underlying Invoices


database table, in much the same way that the Accounts and Products
objects are used to interact with their respective underlying database tables.
The functionality provided by the Invoices object is listed in Table 7-11.
Table 7-11 Properties and Methods of the Invoices Object

Property Datatype Description

BOF short Returns a Boolean value used to


determine whether the current
record pointer has gone beyond
the beginning of the record set.
EOF short Returns a Boolean value used to
determine whether the current
record pointer has gone beyond
the end of the record set.
Item(long InvoiceNumber) IInvoice* Returns a pointer to the Invoice
object identified by
InvoiceNumber, the invoice’s
unique number. The Item
property also allows the user to
access the Invoice identified by
the current record pointer, by
calling Item with -1.
Count long Returns the number of invoices in
the data source.
Method Description
Add(IInvoice*) Used to add the invoice defined by the incoming
Invoice object to the data source.
Used to modify an existing invoice entry in the
Update(IInvoice*)
data source.
Remove(long Used to remove the invoice identified by
InvoiceNumber) InvoiceNumber, the invoice’s unique number,
from the data source.
Move(long INumRecs) Enables the user to reposition the current record
pointer INumRecs to the next record in the record
set.
MoveFirst Enables the user to reposition the current record
pointer to the first record in the record set.
MoveLast Enables the user to reposition the current record
pointer to the last record in the record set.
MovePrev Enables the user to reposition the current record
pointer to the previous record in the record set.
MoveNext Enables the user to reposition the current record
pointer to the next record in the record set.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
The LineItems Object

The LineItems object is used to interact with the underlying LineItems


database table, in much the same way that the Accounts and Products
objects are used to interact with their respective underlying database tables.
The functionality provided by the LineItems object is listed in Table 7-12.
Table 7-12 Properties and Methods of the LineItems Object

Property Datatype Description

BOF short Returns a Boolean value used to


determine whether the current
record pointer has gone beyond
the beginning of the record set.
EOF short Returns a Boolean value used to
determine whether the current
record pointer has gone beyond
the end of the record set.
Item(long ILineItem* Returns a pointer to the
LineItemNumber) LineItem object identified by
LineItemNumber, the line item’s
unique number. The Item
property also allows the user to
access the LineItem identified
by the current record pointer, by
calling Item with -1.
Count long Returns the number of line items
in the invoice.
Method Description
Add(ILineItemm*) Used to add the line item defined by the incoming
LineItem object to the data source.
Used to modify an existing line item entry in the
Update(ILineItem*)
data source.
Remove(long Used to remove the line item identified by
LineItemNumber) LineItemNumber, the line item’s unique number,
from the data source.
Move(long INumRecs) Enables the user to reposition the current record
pointer INumRecs to the next record in the record
set.
MoveFirst Enables the user to reposition the current record
pointer to the first record in the record set.
MoveLast Enables the user to reposition the current record
pointer to the last record in the record set.
MovePrev Enables the user to reposition the current record
pointer to the previous record in the record set.
MoveNext Enables the user to reposition the current record
pointer to the next record in the record set.

Building an Object Hierarchy


Now that you have a clearer understanding of the roles of each object in the
order entry object hierarchy, we’ll move on to the next step, which is to
actually build the various entry and collection objects of the hierarchy! We’ll
start by building the Account, Product, Invoice, and LineItem
“noun” objects because their static nature makes them a cinch to implement.
Then we’ll build the Accounts, Products, Invoices, and LineItems
“verb” objects, which are a lot more involved because of their interaction with
their underlying database tables, which are accessed via ODBC. Each of the
various objects of the hierarchy will be implemented as part of a single
in-process COM server (OrderEntry.DLL).

Building the Entry Objects

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.

class CAccount : IAccount


{
private:
.
.//Other member variables
.
long m_lNumber;
.
.//Other member variables
.
public:
.
. //Other member functions
.
STDMETHODIMP get_Number(long *lRetNumber);
STDMETHODIMP put_Number(long lNumber);
.
. //Other member functions
.
};//CAccount

STDMETHODIMP CAccount::get_Number(long *lRetNumber)


{
*lRetNumber = m_lNumber;
return NOERROR;
}//get_Number

STDMETHODIMP CAccount::put_Number(long lNumber)


{
m_lnumber = lNumber;
return NOERROR;
}//put_Number

Building the Collection Objects

The Accounts, Products, Invoices, and LineItems objects are a


little more complex because of their interaction with the underlying database
tables, which are accessed via ODBC. As each of these objects is developed in
identical fashion, we will focus our investigation on the Accounts object;
the same techniques are used to implement each of the other collection objects.
However, because each of the collection objects encapsulates interactivity with
an underlying database table, I think it makes sense to provide a quick and
dirty overview of ODBC programming. For a more in-depth look at ODBC
programming, you should pick up a copy of the Microsoft ODBC 3.0
Programmer’s Reference and SDK Guide published by Microsoft Press.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Figure 7-2 An architectural overview of ODBC.


Of the four ODBC architectural segments depicted in Figure 7-2, building the application
is probably the easiest. The steps for building an application, or in this case an object
hierarchy, that interacts with an ODBC data source are pretty well choreographed:
1. Allocate a new ODBC environment handle by calling SQLAllocEnv. The
environment maintains global information regarding each valid connection handle
and the current active connection handle. There is only one environment per
application.
2. Allocate a new connection handle by calling SQLAllocConnect. Once a
connection handle has been created, you can establish an actual connection with
the data source by calling SQLConnect. An application can have multiple
connections to any number of data sources.
3. Establish a connection with the desired data source using SQLConnect and a
connection handle created using SQLAllocConnect.
4. Allocate a new statement handle by calling SQLAllocStmt and initialize it
with an SQL statement.
5. Use the statement handle created with SQLAllocStmt to execute your SQL
statement(s) and receive and process their results. SQL statements can be executed
by calling either SQLExecDirect or SQLExecute. Use SQLExecDirect for
SQL statements that you will only execute one time; otherwise, use SQLPrepare
and SQLExecute.
6. Free each statement’s resources by calling SQLFreeStmt. Each successful
call to SQLAllocStmt should have a matching SQLFreeStmt call.
7. Disconnect from the data source by calling SQLDisconnect. Each successful
call to SQLConnect should have a matching SQLDisconnect call.
8. Free each connection’s resources by calling SQLFreeConnect. Each
successful call to SQLAllocConnect should have a matching
SQLFreeConnect call.
9. Free the environment resources by calling SQLFreeEnv. Each successful call
to SQLAlocEnv should have a matching SQLFreeEnv call.
While this is admittedly a highly oversimplified blueprint for building an ODBC
application, it should serve the purpose of providing you with enough background
information for you to feel your way through the code presented throughout the rest of
this chapter.
Before making any ODBC API calls, each application must allocate a new ODBC
environment handle, which must be released as part of the application cleanup process.
We will use the DLL entry point, DllMain, to determine when a client process attaches
or detaches, so that we can allocate or free the application’s global ODBC environment
handle using SQLAllocEnv and SQLFreeEnv respectively:

BOOL APIENTRY DllMain(HANDLE hmodule, DWORD dwReason, LPVOID


lpReserved)
{
if (DLL_PROCESS_ATTACH == dwReason)
{
//Save the dll module handle for later use
g_hModule = hModule;
//Initialize the ODBC environment for the attaching
//process
if (SQL_ERROR == SQLAllocEnv(&g_hEnvironment))
return FALSE;
.
.//Other initialization code
.
}
if (DLL_PROCESS_DETACH == dwReason)
{
//Clean up the ODBC environment for this process
if (g_hEnvironment)
SQLFreeEnv(g_hEnvironment);
.
.//Other clean up code
.
}

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.

DATA ACCESS AND MANIPULATION PROPERTIES AND


METHODS
We will begin our investigation of the Accounts object with its initialization. The
initialization of the Accounts object occurs immediately after the object is first created
as part of the CreateInstance member function of the Accounts class factory:

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

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

//Create prepared statements to reflect the


//SQL operations that will be used extensively
//Add
//Create a statement handle for each prepared statement
SQLAllocStmt(m_hdbc, &m_hstmtInsert);
if (!SQL_SUCCEEDED(SQLPrepare(m_hstmtInsert, szSQLInsert,
SQL_NTS)))
return E_UNEXPECTED;
Notice how the current record pointer is positioned using the MoveFirst member function
once all of the prepared statements are created. We will look into the MoveFirst method a
little later, when we discuss the Accounts object’s navigation properties and methods, but let’s
first examine some of the Accounts object’s data manipulation properties and methods.
The first data manipulation method that we will investigate is the Add method. The following
code shows how a client written in Visual Basic might use the Accounts object to add a new
customer account to the data source. As you look at the source code, notice that the Number
property is not set when adding a new account. This is because the value for the Number
property is automatically generated by the data source, to prevent the possibility of two accounts
having the same account number:

'New accounts start off with zero balance


objNewAccount.Balance = 0
'New accounts start off with a $1200.00 limit
objNewAccount.Limit = 1200
objNewAccount.Name = "Ken Lewis"
objNewAccount.Sex = F
objNewAccount.Address = "9000 Essex"
objNewAccount.City = "Chicago"
objNewAccount.State = "IL"
objNewAccount.Zip = "60617"
objNewAccount.Phone = "(123) 456-7890"
'New accounts start off in service
objNewAccount.InService = TRUE
'Add the new account to the data source
g_objAccounts.Add objNewAccount
The Add method performs two basic steps to actually add the new account to the data source.
The first step is to retrieve the information from the incoming Account object:

//retrieve the data from the incoming object


//Balance
pIAccount->get_Balance(&flBalance);
//Limit
pIAccount->get_Limit(&lLimit);
The second step is to execute the prepared insert statement using the incoming account
information. However, before the prepared insert statement can be executed, we must supply
values for each ? placeholder used in the insert statement’s definition:

SQLTCHAR szSQLInsert[] = _TEXT("INSERT INTO Accounts


(Balance, Limit, Name, Sex, Address, City, State,
Zip, Phone, Inservice) VALUES (?, ?, ?, ?, ?, ?, ?,
?, ?, ?)");
To supply values for each of the prepared statement parameters, we will use the ODBC
SQLBindParameter function. The SQLBindParameter function uses C variables as
transfer buffers to shuttle data into and out of SQL prepared statements. The definition of the
SQLBindParameter function looks like the following:

RETCODE SQLBindParameter(hstmt, ipar, fParamType, fCType,


fSqlType, cbColDef, ibScale, rgbValue, cbValueMax, pcbValue)

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
The SQLBindParameter function accepts the arguments given in Table 7-13.
Table 7-13 Arguments Accepted by SQLBindParameter

Type Argument Use Description

HSTMT hstmt Input Statement handle


UWORD ipar Input Parameter number, ordered sequentially
left to right, starting at 1.
SWORD fParamType Input The type of the parameter, either
SQL_PARAM_INPUT,
SQL_PARAM_INPUT_OUTPUT, or
SQL_PARAM_OUTPUT.
SWORD fCType Input The C datatype of the parameter (see
Table 7-5).
SWORD fSqlType Input The SQL datatype of the parameter (see
Table 7-6).
UDWORD cbColDef Input For character and binary data, specifies
the actual length of the rgbValue
buffer. For all other datatypes, this value
is ignored.
SWORD ibScale Input The maximum number of digits to the
right of the decimal point. Ignored by all
datatypes except SQL_DECIMAL and
SQL_NUMERIC.
PTR rgbValue Input/Output A pointer to a buffer containing the actual
data for the parameter.
SDWORD cbValueMax Input For character and binary data, specifies
the maximum length of the rgbValue
buffer. For all other datatypes, this value
is ignored.
SDWORD FAR* pcbValue Input/Output A pointer to a buffer to receive the length
of the character or binary data returned in
rgbValue. For all other datatypes, this
value is ignored.

Table 7-14 C Datatypes Supported by SQLBindParameter

SQL_C_BINARY SQL_C_BIT SQL_C_CHAR


SQL_C_DATE SQL_C_TIME SQL_C_TIMESTAMP
SQL_C_DEFAULT SQL_C_DOUBLE SQL_C_FLOAT
SQL_C_LONG SQL_C_SLONG SQL_C_ULONG
SQL_C_SHORT SQL_C_SSHORT SQL_C_USHORT
SQL_C_TINYINT SQL_C_STINYINT SQL_C_UTINYINT

Table 7-15 SQL Datatypes supported by SQLBindParameter

SQL_TINYINT SQL_SMALLINT SQL_BIGINT


SQL_DATE SQL_TIME SQL_TIMESTAMP
SQL_DECIMAL SQL_DOUBLE SQL_FLOAT
SQL_INTEGER SQL_NUMERIC SQL_REAL
SQL_BINARY SQL_VARBINARY SQL_LONGVARBINARY
SQL_CHAR SQL_VARCHAR SQL_LONGVARCHAR
SQL_BIT

Once a C variable has been bound for each parameter, the prepared insert statement can be
executed using SQLExecute:

//execute the prepared statement


retcode = SQLExecute(m_hstmtInsert);
Finally, after the statement has been executed, the bound parameters are released by calling
SQLFreeStmt with the SQL_RESET_PARAMS option:

//release the bound parameter buffers


SQLFreeStmt(m_hstmtInsert, SQL_RESET_PARAMS);
The source code for the Accounts object’s Add method can be seen in its entirety in Listing 7-1.
Listing 7-1. The Accounts object’s Add method

STDMETHODIMP CAccounts::Add(IAccount *pIAccount)


{
RETCODE retcode;
BSTR bstrName = NULL;
BSTR bstrSex = NULL;
BSTR bstrAddress = NULL;
BSTR bstrCity = NULL;
BSTR bstrState = NULL;
BSTR bstrZip = NULL;
BSTR bstrPhone = NULL;
float flBalance;
long lLimit;
char szName[NAME_LEN];
char szSex[SEX_LEN];
char szAddress[ADDRESS_LEN];
char szCity[CITY_LEN];
char szState[STATE_LEN];
char szZip[ZIP_LEN];
char szPhone[PHONE_LEN];
VARIANT_BOOL vbInService;

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;

//retrieve the data from the incoming object


//Balance
pIAccount->get_Balance(&flBalance);
//Limit
pIAccount->get_Limit(&lLimit);
//Name
pIAccount->get_Name(&bstrName);
wcstombs(szName, bstrName, NAME_LEN);
if (bstrName)
SysFreeString(bstrName);
//Sex
pIAccount->get_Sex(&bstrSex);
wcstombs(szSex, bstrSex, SEX_LEN);
if (bstrSex)
SysFreeString(bstrSex);
//Address
pIAccount->get_Address(&bstrAddress);
wcstombs(szAddress, bstrAddress, ADDRESS_LEN);
if (bstrAddress)
SysFreeString(bstrAddress);
//City
pIAccount->get_City(&bstrCity);
wcstombs(szCity, bstrCity, CITY_LEN);
if (bstrCity)
SysFreeString(bstrCity);
//State
pIAccount->get_State(&bstrState);
wcstombs(szState, bstrState, STATE_LEN);
if (bstrState)
SysFreeString(bstrState);
//Zip
pIAccount->get_Zip(&bstrZip);
wcstombs(szZip, bstrZip, ZIP_LEN);
if (bstrZip)
SysFreeString(bstrZip);
//Phone
pIAccount->get_Phone(&bstrPhone);
wcstombs(szPhone, bstrPhone, PHONE_LEN);
if (bstrPhone)
SysFreeString(bstrPhone);
//InService
pIAccount->get_InService(&vbInService);

//Setup parameter transfer buffers


//Balance
SQLBindParameter(m_hstmtInsert, 1, SQL_PARAM_INPUT,
SQL_C_FLOAT, SQL_REAL, 0, 0, &flbalance, 0, &cbBalance);
//Limit
SQLBindParameter(m_hstmtInsert, 2, SQL_PARAM_INPUT,
SQL_C_SLONG, SQL_INTEGER, 0, 0, &lLimit, 0, &cbLimit);
//Name
SQLBindParameter(m_hstmtInsert, 3, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, NAME_LEN, 0, szName, 0, &cbName);
//Sex
SQLBindParameter(m_hstmtInsert, 4, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, SEX_LEN, 0, szSex, 0, &cbSex);
//Address
SQLBindParameter(m_hstmtInsert, 5, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, ADDRESS_LEN, 0, szAddress, 0,
&cbAddress);
//City
SQLBindParameter(m_hstmtInsert, 6, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, CITY_LEN, 0, szCity, 0, &cbCity);
//State
SQLBindParameter(m_hstmtInsert, 7, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, STATE_LEN, 0, szState, 0, &cbState);
//Zip
SQLBindParameter(m_hstmtInsert, 8, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, ZIP_LEN, 0, szZip, 0, &cbZip);
//Phone
SQLBindParameter(m_hstmtInsert, 9, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, PHONE_LEN, 0, szPhone, 0, &cbPhone);
//InService
SQLBindParameter(m_hstmtInsert, 10, SQL_PARAM_INPUT,
SQL_C_SSHORT, SQL_SMALLINT, 1, 0, &vbInService, 0,
&cbInService);
//execute the prepared statement
retcode = SQLExecute(m_hstmtInsert);
//release the bound parameter buffers
SQLFreeStmt(m_hstmtInsert, SQL_RESET_PARAMS);
if (!SQL_SUCCEEDED(retcode))
return E_UNEXPECTED;

return NOERROR;
}//Add
Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

'Update the user's account information to reflect a recent change of


address
objExistingAccount.Address = "One Redmond Way"
objExistingAccount.City = "Redmond"
objExistingAccount.State = "WA"
objExistingAccount.Zip = "98052"
objExistingAccount.Phone = "(123) 456-7890"
'Propagate the account changes to the data source
g_objAccounts.Update objExistingAccount
As you might imagine, implementation of the Update method is practically identical to that of the
Add method in Listing 7-1.
The Remove method of the Accounts and Products objects doesn’t actually remove an entry
from the data source, but rather marks an entry as deactivated. The logic behind deactivating account
and product entries as opposed to deleting them is simple. Part of the process of displaying an invoice
is displaying the name of the purchasing customer, as well as a description of each purchased
product. However, if the customer’s account has been deleted, we have no way of knowing to whom
the invoice belongs. Likewise, if a product entry has been deleted, we have no way of knowing what
products were purchased on the invoice. In other words, if we were to delete customer accounts and
product entries, we would have no way of knowing which customer purchased which products on an
invoice! The deactivation solution is just one of many possible solutions to this problem, and was
chosen because it is the simplest. On the other hand, invoices themselves are actually deleted from
the system. The Remove method of the Invoices object actually deletes invoice entries from the
underlying Invoices database table. However, deleting an invoice has no direct effect on either the
Accounts or Products database table.
The Count property is used to determine the number of entries in the underlying data source:

Dim lNumRecs As Long

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;

//Setup parameter transfer buffers


SQLBindParameter(m_hstmtRemove, 1, SQL_PARAM_INPUT,
SQL_C_SLONG, SQL_INTEGER, 0, 0, &lNumber, 0, &cbNumber);
//execute the prepared statement
retcode = SQLExecute(m_hstmtRemove);
//release the bound parameter buffers
SQLFreeStmt(m_hstmtRemove, SQL_RESET_PARAMS);
if (!SQL_SUCCEEDED(retcode))
return E_UNEXPECTED;

return NOERROR;
}//Remove

//
//get_Count
//
STDMETHODIMP CAccounts::get_Count(long *lRetCount)
{
RETCODE retcode;
SDWORD cbCount = 0;

//execute the prepared statement


retcode = SQLExecute(m_hstmtCount);
if (!SQL_SUCCEEDED(retcode))
return E_UNEXPECTED;
//retrieve the count
retcode = SQLFetch(m_hstmtCount);
if (SQL_SUCCEEDED(retcode))
SQLGetData(m_hstmtCount, 1, SQL_C_SLONG, lRetCount, 0,
&cbCount);
//close the recordset
SQLFreeStmt(m_hstmtCount, SQL_CLOSE);
if (!SQL_SUCCEEDED(retcode))
return E_UNEXPECTED;

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:

'Return information about account #333


Set objAccount = g_objAccounts.Item(333)
To retrieve information regarding a specific customer account, we use the SQLBindParameter
and SQLExecute functions to transfer the user-supplied account number to the prepared query
statement and execute it. However, unlike the prepared insert, update, and remove statements,
the prepared query statement returns a record set of data. To retrieve the record set data generated
by the prepared query statement, we will use the SQLBindcol and SQLFetch functions. The
SQLBindcol function allows you to use variables as transfer buffers to retrieve data from specific
columns of a record set, similar to the way that SQLBindParameter allows you to use variables to
shuttle data into and out of SQL prepared statements.
The definition of the SQLBindCol function looks like the following:

RETCODE SQLBindCol (hstmt, icol, fCType, rgbValue, cbValueMax,


pcbValue)
The SQLBindcol function accepts the arguments shown in Table 7-16.
Table 7-16 Arguments Accepted by SQLBindCol

Type Argument Use Description

HSTMT hstmt Input Statement handle


UWORD icol Input Column number of result data,
ordered sequentially left to right,
starting at 1.
SWORD fCType Input The C datatype of the parameter
(see Table 7-14).
PTR rgbValue Input/Output A pointer to a buffer containing the
actual data for the parameter.
SDWORD cbValueMax Input For character and binary data,
specifies the maximum length of the
rgbValue buffer. For all other
datatypes, this value is ignored.
SDWORD FAR* pcbValue Input/Output A pointer to a buffer to receive the
length of the character or binary
data returned in rgbValue. For all
other datatypes, this value is
ignored.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
Once each parameter has been bound, we can retrieve the column values by calling SQLFetch:

//Setup transfer buffers


//Number
SQLBindCol(m_hstmtQuery, 1, SQL_C_SLONG, &lNumber, 0,
&cbNumber);
//Balance
SQLBindCol(m_hstmtQuery, 2, SQL_C_FLOAT, &flBalance, 0,
&cbBalance);
.
.//More code
.
//Retrieve the data
SQLFetch(m_hstmtQuery);
Finally, after the column values have been fetched, the bound parameters are released by calling
SQLFreeStmt with the SQL_UNBIND option, and the record set is closed by calling
SQLFreeStmt with the SQL_CLOSE option:

//unbind any columns


SQLFreeStmt(m_hstmtQuery, SQL_UNBIND);
//close the recordset
SQLFreeStmt(m_hstmtQuery, SQL_CLOSE);
Once all of the account information has been retrieved, a blank Account object is created and
initialized using the account data:

//create a blank Account object


hr = g_pIAccountFactory->CreateInstance(NULL, IID_IAccount,
(LPVOID *)pIAccount);
if (SUCCEEDED(hr))
{
//set the properties of the return object
//Number
(*pIAccount)->put_Number(lNumber);
.
.//More code
.
}
The second scenario for using the Item method is to retrieve the information for the account
identified by the current record pointer, which is manipulated using the Move, MoveFirst,
MoveLast, MovePrev, and MoveNext navigation methods. The following code snippet shows
how a VB client might use the Item method in this second manner:

'Move to the first record


g_objAccounts.MoveFirst
If Not g_objAccounts.EOF Then
'Return the information about the first account
Set objAccount = g_objAccounts.Item
End If
The key to the special behavior of the Item property is in its definition. As you look at the following
definition for the Item property, notice the optional and defaultvalue IDL keywords:

[propget, defaultcollelem, id(0), helpstring("Returns an Account


from the database.")]
HRESULT Item([in, optional, defaultvalue(-1)] VARIANT Key, [out,
retval] IAccount **pIAccount);
The optional keyword is used to signal an optional parameter, and is only allowed on VARIANT and
VARIANT * type parameters. To determine whether or not an optional parameter has been supplied,
we can use the default value IDL keyword to assign a value to an optional parameter, to be used
whenever the user doesn’t provide an argument for the parameter. In our implementation of the Item
property, we simply test the Key parameter for -1 to determine whether or not the user has supplied a
value for the Key parameter, since the Item property uses -1 as its default value. If Key is equal to
-1, the developer is requesting information about the account identified by the current record pointer;
otherwise, the developer is requesting information about the account identified by the Key. In the
first scenario, 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. Internally, the navigation methods use
SQLExtendedFetch to reposition the current record pointer as well as to transfer data to
CAccounts member variables previously bound to record set columns using SQLBIndcol. After
the successful completion of a navigation method, you can retrieve information about the current
customer account by simply querying the Item property with no parameters. The Item property
responds by copying the data from the CAccounts member variables to local variables, which then
uses the data to initialize a newly created Account object, which is then returned to the developer.
Source code for the Accounts object’s Item method can be seen in Listing 7-3.
Listing 7-3. The Accounts object’s Item property

STDMETHODIMP CAccounts::get_Item(VARIANT Key, IAccount


**pIAccount)
{
HRESULT hr;
RETCODE retcode;
BSTR bstrName = NULL;
BSTR bstrSex = NULL;
BSTR bstrAddress = NULL;
BSTR bstrCity = NULL;
BSTR bstrState = NULL;
BSTR bstrZip = NULL;
BSTR bstrPhone = NULL;

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;

//Initialize the return value


*pIAccount = NULL;
//Coerce the incoming VARIANT into a long
VariantChangeType(&Key, &Key, VARIANT_NOVALUEPROP, VT_14);
//Retrieve the converted long value
lNumber = V_I4(&Key);
//Determine if the default is being used
//which would signal us to retrieve the data pointed to by
//the current record pointer
if (-1 == lNumber)
{
//Only return an object if not BOF or EOF
if (m_bBOF || m_bEOF)
return NOERROR;

//The user is trying to retrieve the current Account

//Copy the data from the appropriate member functions used


//to store the values for the current record
//Number
lNumber = m_lNavNumber;
//Balance
flBalance = m_flNavBalance;
//Limit
lLimit = m_lNavLimit;
//Name
memcpy(szName, m_szNavName, NAME_LEN);
//Sex
memcpy(szSex, m_szNavSex, SEX_LEN);
//Address
memcpy(szAddress, m_szNavAddress, ADDRESS_LEN);
//City
memcpy(szCity, m_szNavCity, CITY_LEN);
//State
memcpy(szState, m_szNavState, STATE_LEN);
//Zip
memcpy(szZip, m_szNavZip, ZIP_LEN);
//Phone
memcpy(szPhone, m_szNavPhone, PHONE_LEN);
//InService
vbInService = m_vbNavInService;
}
else
{
//The user is trying to retrieve a specific account

//Setup parameter transfer buffers

//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;

//Setup transfer buffers


//Number
SQLBindCol(m_hstmtQuery, 1, SQL_C_SLONG, &lNumber, 0,
&cbNumber);
//Balance
SQLBindCol(m_hstmtQuery, 2, SQL_C_FLOAT, &flBalance, 0,
&cbBalance);
//Limit
SQLBindCol(m_hstmtQuery, 3, SQL_C_SLONG, &lLimit, 0,
&cbLimit);
//Name
SQLBindCol(m_hstmtQuery, 4, SQL_C_CHAR, szName, NAME_LEN,
&cbName);
//Sex
SQLBindCol(m_hstmtQuery, 5, SQL_C_CHAR, szSex, SEX_LEN,
&cbSex);
//Address
SQLBindCol(m_hstmtQuery, 6, SQL_C_CHAR, szAddress,
ADDRESS_LEN, &cbAddress);
//City
SQLBindCol(m_hstmtQuery, 7, SQL_C_CHAR, szCity, CITY_LEN,
&cbCity);
//State
SQLBindCol(m_hstmtQuery, 8, SQL_C_CHAR, szState,
STATE_LEN, &cbState);
//Zip
SQLBindCol(m_hstmtQuery, 9, SQL_C_CHAR, szZip, ZIP_LEN,
&cbZip);
//Phone
SQLBindCol(m_hstmtQuery, 10, SQL_C_CHAR, szPhone,
PHONE_LEN, &cbPhone);
//InService
SQLBindCol(m_hstmtQuery, 11, SQL_C_SSHORT, &vbInService,
0, &cbInService);

//Get the next row


retcode = SQLFetch(m_hstmtQuery);
//Unbind any columns
SQLFreeStmt(m_hstmtQuery, SQL_UNBIND);
//Close the recordset
SQLFreeStmt(m_hstmtQuery, SQL_CLOSE);
if (SQL_NO_DATA == retcode)
return NOERROR;
if (!SQL_SUCCEEDED(retcode))
return E_UNEXPECTED;
}
//Create a blank Account object
hr = g_pIAccountFactory->CreateInstance(NULL, IID_IAccount,
(LPVOID *)pIAccount);
if (SUCCEEDED(hr))
{
//Set the properties of the return object
//Number
(*pIAccount)->put_Number(lNumber);
//Balance
(*pIAccount)->put_Balance(flBalance);
//Limit
(*pIAccount)->put_Limit(lLimit);
//Name
//Convert the string to a unicode string
mbstowcs(wszName, szName, NAME_LEN);
//Convert the unicode string to a BSTR
bstrName = SysAllocString(wszName);
(*pIAccount)->put_Name(bstrName);
//Free the BSTR
SysFreeString(bstrName);
//Sex
//Convert the string to a unicode string
mbstowcs(wszSex, szSex, SEX_LEN);
//Convert the unicode string to a BSTR
bstrSex = SysAllocString(wszSex);
(*pIAccount)->put_Sex(bstrSex);
//Free the BSTR
SysFreeString(bstrSex);
//Address
//Convert the string to a unicode string
mbstowc (szAddress, szAddress, ADDRESS_LEN);
//Convert the unicode string to a BSTR
bstrAddress = SysAllocString(wszAddress);
(*pIAccount)->put_Address(bstrAddress);
//Free the BSTR
SysFreeString(bstrAddress);
//City
//Convert the string to a unicode string
mbstowcs(wszCity, szCity, CITY_LEN);
//Convert the unicode string to a BSTR
bstrCity = SysAllocString(wszCity);
(*pIAccount)->put_City(bstrCity);
//Free the BSTR
SysFreeString(bstrCity);
//State
//Convert the string to a unicode string
mbstowcs(wszState, szState, STATE_LEN);
//Convert the unicode string to a BSTR
bstrState = SysAllocString(wszState):
(*pIAccount)->put_State(bstrState);
//Free the BSTR
SysFreeString(bstrState);
//Zip
//Convert the string to a unicode string
mbstowcs(wszZip, szZip, ZIP_LEN);
//Convert the unicode string to a BSTR
bstrzip = SysAllocString(wszZip);
(*pIAccount)->put_Zip(bstrZip);
//Free the BSTR
SysFreeString(bstrZip);
//Phone
//Convert the string to a unicode string
mbstowcs(wszPhone, szPhone, PHONE_LEN);
//Convert the unicode string to a BSTR
bstrPhone = SysAllocString(wszPhone);
(*pIAccount)->put_Phone(bstrPhone);
//Free the BSTR
SysFreeString(bstrPhone);
//InService
(*pIAccount)->put_InService(vbInService);
}
return hr;
}//get_Item
Finaly, the only remaining properties and methods left to implement are those regarding navigation
through the collection object’s underlying data source.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

SQLTCHAR szSQLNavigationalAccess[] = _TEXT("SELECT * FROM


Accounts ORDER BY Number");
Before the navigational prepared statement is actually created, we free any resources
that may have been previously allocated by an earlier call to MoveFirst, because a
developer is free to call the MoveFirst method at any point in time. Once previously
allocated resources have been freed, a statement handle is allocated using SQLAllocStmt
and configured to create a keyset-driven record set using SQLSetStmtOption, after which
the navigational prepared statement is finally created and executed:

//Free all resources associated with navigational statement


if (m_hstmtNavigationalAccess)
{
SQLFreeStmt(m_hstmtNavigationalAccess, SQL_DROP);
m_hstmtNavigationalAccess = NULL;
}
//Create the prepared statement that will be used
//for navigational access
SQLAllocStmt(m_hdbc, &m_hstmtNavigationalAccess);
//Configure the options for the statement
SQLSetStmtOption(m_hstmtNavigationalAccess, SQL_CURSOR_TYPE,
SQL_CURSOR_KEYSET_DRIVEN);
if (!SQL_SUCCEEDED(SQLPrepare(m_hstmtNavigationalAccess,
szSQLNavigationalAccess, SQL_NTS)))
return E_UNEXPECTED;
//Execute the prepared statement
retcode = SQLExecute(m_hstmtNavigationalAccess);
if (!SQL_SUCCEEDED(retcode))
return E_UNEXPECTED;
Next, using SQLBindCol and several CAccounts member variables, transfer buffers are
established for each column returning data from the navigational record set:

//Setup transfer buffers


//Number
SQLBindCol(m_hstmtNavigationalAccess, 1, SQL_C_SLONG,
&m_lNavNumber, 0, &m_cbNumber);
//Balance
SQLBindCol(m_hstmtNavigationalAccess, 2, SQL_C_FLOAT,
&m_flNavBalance, 0, &m_cbBalance);
//Limit
SQLBindCol(m_hstmtNavigationalAccess, 3, SQL_C_SLONG,
&m_lNavLimit, 0, &m_cbLimit);
//Name
SQLBindCol(m_hstmtNavigationalAccess, 4, SQL_C_CHAR,
m_szNavName, NAME_LEN,
&m_cbName);
//Sex
SQLBindCol(m_hstmtNavigationalAccess, 5, SQL_C_CHAR,
m_szNavSex, SEX_LEN, &m_cbSex);
//Address
SQLBindCol(m_hstmtNavigationalAccess, 6, SQL_C_CHAR,
m_szNavAddress, ADDRESS_LEN,
&m_cbAddress);
//City
SQLBindCol(m_hstmtNavigationalAccess, 7, SQL_C_CHAR,
m_szNavCity, CITY_LEN,
&m_cbCity);
//State
SQLBindCol(m_hstmtNavigationalAccess, 8, SQL_C_CHAR,
m_szNavState, STATE_LEN,
&m_cbState):
//Zip
SQLBindCol(m_hstmtNavigationalAccess, 9, SQL_C_CHAR,
m_szNavZip, ZIP_LEN,
&m_cbZip);
//Phone
SQLBindCol(m_hstmtNavigationalAccess, 10, SQL_C_CHAR,
m_szNavPhone, PHONE_LEN,
&m_cbPhone);
//InService
SQLBindCol(m_hstmtNavigationalAccess, 11, SQL_C_SSHORT,
&m_vbNavInService, 0, &m_cbInService);
Finally, SQLExtendedFetch is used to position the current record pointer to the first
record of the navigational record set, and to retrieve the account information contained
therein. The last responsibility of the MoveFirst method is to determine whether or not the
current record pointer is before the beginning of the record set (BOF) or after the end of the
record set (EOF), and to adjust the member variables responsible for reporting these
conditions accordingly:

//Get the first 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_bBOF = TRUE;
m_bEOF = TRUE;
}

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
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");

//Free all resources associated with navigational statement


if (m_hstmtNavigationalAccess)
{
SQLFreeStmt(m_hstmtNavigationalAccess, SQL_DROP);
m_hstmtNavigationalAccess = NULL;
}
//Create the prepared statement that will be used
//for navigational access
SQLAllocStmt(m_hdbc, &m_hstmtNavigationalAccess);
//Configure the options for the statement
SQLSetStmtOption(m_hstmtNavigationalAccess, SQL_CURSOR_TYPE,
SQL_CURSOR_KEYSET_DRIVEN);
if (!SQL_SUCCEEDED(SQLPrepare(m_hstmtNavigationalAccess,
szSQLNavigationalAccess, SQL_NTS)))
return E_UNEXPECTED;
//Execute the prepared statement
retcode = SQLExecute(m_hstmtNavigationalAccess);
if (!SQL_SUCCEEDED(retcode))
return E_UNEXPECTED;

//Initialize return byte counters


m_cbNumber = 0;
m_cbBalance = 0;
m_cbLimit = 0;
m_cbName = SQL_NTS;
m_cbSex = SQL_NTS;
m_cbAddress = SQL_NTS;
m_cbCity = SQL_NTS;
m_cbState = SQL_NTS;
m_cbZip = SQL_NTS;
m_cbPhone = SQL_NTS;
m_cbInService = 0;

//Setup transfer buffers


//Number
SQLBindCol(m_hstmtNavigationalAccess, 1, SQL_C_SLONG,
&m_lNavNumber, 0, &m_cbNumber);
//Balance
SQLBindCol(m_hstmtNavigationalAccess, 2, SQL_C_FLOAT,
&m_flNavBalance, 0, &m_cbBalance);
//Limit
SQLBindCol(m_hstmtNavigationalAccess, 3, SQL_C_SLONG,
&m_lNavLimit, 0, &m_cbLimit);
//Name
SQLBindCol(m_hstmtNavigationalAccess, 4, SQL_C_CHAR,
m_szNavName, NAME_LEN, &m_cbName);
//Sex
SQLBindCol(m_hstmtNavigationalAccess, 5, SQL_C_CHAR,
m_szNavSex, SEX_LEN, &m_cbSex);
//Address
SQLBindCol(m_hstmtNavigationalAccess, 6, SQL_C_CHAR,
m_szNavAddress, ADDRESS_LEN, &m_cbAddress);
//City
SQLBindCol(m_hstmtNavigationalAccess, 7, SQL_C_CHAR,
m_szNavCity, CITY_LEN, &m_cbCity);
//State
SQLBindCol(m_hstmtNavigationalAccess, 8, SQL_C_CHAR,
m_szNavState, STATE_LEN, &m_cbState):
//Zip
SQLBindCol(m_hstmtNavigationalAccess, 9, SQL_C_CHAR,
m_szNavZip, ZIP_LEN, &m_cbZip);
//Phone
SQLBindCol(m_hstmtNavigationalAccess, 10, SQL_C_CHAR,
m_szNavPhone, PHONE_LEN, &m_cbPhone);
//InService
SQLBindCol(m_hstmtNavigationalAccess, 11, SQL_C_SSHORT,
&m_vbNavInService, 0, &m_cbInService);

//Get the first 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_bBOF = TRUE;
m_bEOF = TRUE;
}
return hr;
}//MoveFirst

//
//MoveLast
//
STDMETHODIMP CAccounts::MoveLast(void)
{
HRESULT hr = NOERROR;
RETCODE retcode;
UDWORD cRowsFetched;
UWORD rgfRowStatus[1];

//Get the last record


retcode = SQLExtendedFetch(m_hstmtNavigationalAccess,
SQL_FETCH_LAST, 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_bBOF = TRUE;
m_bEOF = TRUE;
}
return hr;
}//MoveLast

//
//MovePrev
//
STDMETHODIMP CAccounts::MovePrev(void)
{
HRESULT hr = NOERROR;
RETCODE retcode;
UDWORD cRowsFetched;
UWORD rgfRowStatus[1];

//Get the last record


retcode = SQLExtendedFetch(m_hstmtNavigationalAccess,
SQL_FETCH_PRIOR, 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_bBOF = TRUE;
}
return hr;
}//MovePrev

//
//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];

//Get the last record


retcode = SQLExtendedFetch(m_hstmtNavigationalAccess,
SQL_FETCH_RELATIVE, lRecs, &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
if (lRecs < 0)
m_bBOF = TRUE;
else
m_bEOF = TRUE;
hr = E_UNEXPECTED;
}
return hr;
}//Move
While we’ve only implemented the Accounts object thus far, each of the other collection
objects is implemented using these very same techniques. The OrderEntry object hierarchy
encapsulates not only the data access to the underlying ODBC data source, but also
fundamental business rules — for example, the concept of removing an account from service as
opposed to physically deleting it from the data source. By encapsulating business rules, we can
assure ourselves that every application using the OrderEntry object model will follow the
appropriate business rules. Also, if we should decide to change our business rule logic, we can
make the change in one place (the object hierarchy) and have it affect existing applications
without them having to be recompiled.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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.

Figure 8-1 The OrderEntry object hierarchy developed in Chapter 7.

Understanding the Order-Entry Application


The order-entry application resembles an application that might be used by a
mail-order company to manage customer accounts that are in the form of
special credit cards, product inventory, and customer purchase invoices.
Customers use special mail-order credit cards to purchase products from the
company’s catalog. Telephone operators who work for the mail-order
company complete purchase invoices whenever customers call in to place an
order. The functionality offered by the order-entry application can be divided
into three basic sections: customer account management, product inventory
management, and customer invoice management. Customer account
management functions include adding new customer account entries,
modifying existing customer account entries, deactivating customer accounts,
and displaying a list of all the available customer account entries in the system.
Likewise, the product inventory management functions include adding new
product entries, modifying existing product entries, removing products from
service so that they cannot be purchased, and displaying a list of all the
available product entries in the system. Customer invoice management
functions include adding new invoice entries, modifying existing invoice
entries, deleting invoice entries, and displaying a list of all the invoice entries
in the system. Table 8-1 is a quick reference guide detailing the functionality
offered by the order-entry application.
Table 8-1 The order-entry application quick reference guide

Menu Option Function Description

Accounts New Account Used to add a new customer


account entry to the system.
Modify Existing Account Used to change some aspect of an
existing customer account entry.
Deactivate Account Used to remove a customer
account from service.
List Accounts Used to present a list of all the
customer account entries in the
system.
Products New Product Used to add a new product entry
to the system.
Modify Existing Product Used to change some aspect of an
existing product entry.
Deactivate Product Used to remove a product from
service.
List Product Used to present a list of all the
product entries in the system.
Invoices New Invoice Used to add a new invoice entry
to the system.
Modify Existing Invoice Used to change some aspect of an
existing invoice entry.
Delete Invoice Used to remove an invoice entry
from the system.
List Invoices Used to present a list of all the
invoice entries in the system.

Understanding the Client/Server Application


Architecture
The order-entry application that we build in this chapter follows a client/server
architecture in which the client-side of the order-entry application is
responsible for gathering information from the user, formatting it, and
forwarding it to the server for processing. The client is also responsible for
receiving processed information from the server, formatting it, and presenting
it to the user. The server is responsible for receiving information from the
client, processing it, and returning the results back to the client.
Each client application maintains one global reference to an individual
Accounts object, Products object, and Invoices object, which are used
whenever the application needs to interact with the underlying data source for
adding, updating, removing, or retrieving information (see Figure 8-2).

Figure 8-2 An architectural overview of the client/server order-entry system.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Private Sub MDIForm_Load()


Set g_AccountsCol = CreateObject("OrderEntry.Accounts")
Set g_ProductsCol = CreateObject("OrderEntry.Products")
Set g_InvoicesCol = CreateObject("OrderEntry.Invoices")
End Sub
These global object references provide the functionality we need to manage customer
accounts, product inventory, and customer purchase invoices, which are all very similar. Each
allows you to add new entries, as well as update, remove, and list existing entries. Since
managing accounts is similar to managing products and invoices, we will focus our discussion
on the implementation of the account management functions with the understanding that the
same techniques used to implement the account management functions are used to implement
the product and invoice management functions. However, during our investigation, I will
point out any significant differences between the various implementations of the account,
product, and invoice management functions.

Adding New Accounts

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

Private Sub cmdOK_Click()


If f_ADDING Then
Set f_localAccount = CreateObject("OrderEntry.Account")
End If
f_localAccount.Balance = CLng(txtBalance.Text)
f_localAccount.Limit = CLng(txtLimit.Text)
'
f_localAccount.Name = Trim$(txtName.Text)
f_localAccount.Sex = Trim$(txtSex.Text)
f_localAccount.Address = Trim$(txtAddress.Text)
f_localAccount.City = Trim$(txtCity.Text)
f_localAccount.State = Trim$(txtState.Text)
f_localAccount.Zip = Trim$(txtZip.Text)
f_localAccount.Phone = Trim$(txtPhone.Text)
f_localAccount.InService = (chkInService.Value = 1)
If f_ADDING Then
'add to the data source
g_AccountsCol.Add f_localAccount
Else
'update the current product
g_AccountsCol.Update f_localAccount
End If
Unload Me
End Sub

Retrieving Existing Accounts

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:

Function LocateAccount(ByVal sCaption$, retAccount As Object)


As Integer
Dim lNumber As Long

Set retAccount = Nothing


Load Me
Me.Caption = sCaption$
Me.Show 1
LocateAccount = f_iRetVal
If f_iRetVal Then
If Trim$(txtNumber.Text) <> "" Then
lnumber = CLng(txtNumber.Text)
'retrieve the account information
Set retAccount = g_AccountsCol.item(lNumber)
End If
End If
Unload Me
End Function
Locating an account using the account number only works if the customer happens to have his
or her account number handy. If the customer doesn’t know his or her account number, the
account can also be located using the customer’s name (see Figure 8-5).
Figure 8-5 Customer accounts can also be located using the customer’s name.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Sub ModifyAccount(localAccount As Object)


f_ADDING = False
'release any existing references
If Not (f_localAccount Is Nothing)
Then Set f_localAccount = Nothing
Set f_localAccount = localAccount
'release the reference on the incoming object
Set localAccount = Nothing
Load Me
Me.Caption = "Modify Account - " & CStr(f_localAccount.Number)
'display the information
txtNumber.Text = CStr(f_localAccount.Number)
txtBalance.Text = CStr(f_localAccount.Balance)
txtLimit.Text = CStr(f_localAccount.Limit)
'
txtName.Text = f_localAccount.Name
txtSex.Text = f_localAccount.Sex
txtAddress.Text = f_localAccount.Address
txtCity.Text = f_localAccount.City
txtState.Text = f_localAccount.State
txtZip.Text = f_localAccount.Zip
txtPhone.Text = f_localAccount.Phone
chkInService.Value = Abs(f_localAccount.InService)
Me.Show
End Sub
Once the information is displayed, it can be modified and propagated to the underlying data source
by simply clicking on the OK button. The source code for the Click event of the
AccountInfoForm’s OK button has logic to determine whether a new account is being created
or an existing account is being updated. If an existing account is being updated, the data supplied
in the various fields of the Modify Account window is transferred to the Account object being
updated. Once the information has been transferred, it is propagated to the data source using the
global Accounts object reference that was obtained when the application was first started. The
source code for the Click event of the AccountInfoForm’s OK button can be seen in Listing
8-1.

Removing Existing Accounts

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:

Dim localAccount As Object

If SearchForm.LocateAccount("Deactivate Account . . .",


localAccount) Then
If localAccount Is Nothing Then
MsgBox "No such account could be located."
Else
'deactivate the account
g_AccountsCol.Remove localAccount.Number
End If
End If

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
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-7 The Modify Invoice window.


Adding and updating invoices is slightly different than adding and updating accounts or
products, primarily because each invoice maintains a list of line items, each consisting of a
product and the quantity being purchased (see Figure 8-8).

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

Private Sub cmdOK_Click()


Dim invoiceLineItems As Object
Dim lineItem As Object
Dim item As Long

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

Limitations of the Client/Server Application Architecture


The client/server design of the order-entry application allows us to use client-side system
services such as the ODBC API as well as server-side resources such as DBMSs to create
powerful yet flexible applications. However, the benefits of client/server application architecture
do not come for free. One of the primary drawbacks of client/server design is that each client
machine’s system services must be properly configured in order for the application to work
properly. In the case of the order-entry application, each client desktop must have the appropriate
ODBC drivers installed and configured in order to connect to the server-side OrderEntry DBMS.
This may not seem like a terribly big problem, but imagine if you had to install the order-entry
application on hundreds or thousands of desktops. Other marks against client/server applications
are in the areas of deployment and support. Imagine that it’s your responsibility to deploy and
support the order-entry application. As you make modifications and improvements, how would
you upgrade all of your users to the latest version of the application?
All of these issues affect the application’s Total Cost of Ownership (TCO), and are just some of
the more prevalent TCO problems with regard to the client/server application architecture.
Recently, a new application architecture has come along with the promise of solving all of these
problems, and more. That application architecture is the Web application architecture, and it is
the focus of the next chapter.

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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

IN CHAPTER 8 you created a client/server order-entry application that allows


operators to manage product inventory, individual customer accounts, and
customer purchase invoices. The application is built on top of an object
hierarchy, which encapsulates the functionality necessary for the order-entry
application to perform its required tasks of managing accounts, products, and
invoices. The object hierarchy itself is built on top of ODBC, which allows the
hierarchy, and, ultimately the order-entry application, to be
DBMS-independent. As you saw however, the client/server version of the
order entry application suffers from several significant limitations:
• ODBC must be properly installed and configured on each client
machine.
• The order-entry application must be deployed to each client machine.
• Each client’s copy of the order-entry application must be updated as
newer versions are made available.
Recently, a new Web application, or weblication, architecture has presented
itself as a more effective alternative to the traditional client/server architecture.
In this chapter, we explore the Web application architecture by building a Web
version of the order-entry application created in Chapter 8.

Understanding the Web Application Architecture


The Web application architecture has increased in popularity for many
reasons, which include its ability to address many of the shortcomings of the
client/server architecture. Ironically, the Web application architecture is
actually a variation of the client/server architecture and is often referred to as
thin client/server. One immediate distinction between Web applications and
other client/server architectures is that the infrastructure of a Web application
is, at least at this point, very well-defined. The anatomy of a Web application
consists of a Web server, the HyperText Transfer Protocol (HTTP), the
HyperText Markup Language (HTML), and a Web browser (see Figure 9-1).

Figure 9-1 The infrastructure of a Web application consists of a Web server,


the HyperText Transfer Protocol (HTTP), the HyperText Markup Language
(HTML), and a Web browser.
Clients use the Web browser to request HTML documents from the Web
server. Each HTML document can contain text, images and other forms of
multimedia, executable or interpretable code, and links to other HTML
documents. The Web server delivers the HTML page using HTTP as its
transfer protocol. Once the document arrives at the client machine, the browser
interprets, formats, and displays the document’s contents to the user according
to the HTML tags contained therein. At a simplistic level, Web publishers
create static HTML documents and place them on the Web server, where they
can be served to client Web browsers. HTML documents of this type are
considered static in the sense that they are created ahead of time, as opposed to
being created dynamically, on-the-fly, in response to some outside influence.
While static HTML pages are extremely effective as pure communications
mechanisms, they don’t lend themselves well to application development,
primarily because applications require the ability to generate output
dynamically, in response to user or system demands. Microsoft’s Active
Server Pages (ASP) is a server-side technology that works in conjunction with
Microsoft’s Internet Information Server (IIS) to generate HTML documents
dynamically in response to user request. Developers mix HTML and
application script logic together in ASP files to control the dynamic HTML
document creation process. ASP scripts can be written in a wide variety of
scripting languages, including JavaScript (JScript) and a subset of Visual Basic
known as Visual Basic Scripting Edition (VBScript). If a client requests a
static HTML page, IIS fetches it and sends it to the client, just as you would
expect. However, if a client requests an ASP page, the ASP engine is
automatically invoked and proceeds to fetch the ASP page and process it from
top to bottom, executing any script logic it finds and skipping over any HTML.
The ASP engine identifies anything contained within <% %> as script logic.
Once the entire ASP document has been processed, the resulting HTML
document is sent to the client just like any other HTML document, and the
client has no clue as to what has taken place (see Figure 9-2).

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Adding New Accounts

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

Figure 9-3a The Web version of the order-entry application.

Figure 9-3b The client/server version of the order-entry application.

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

'The account that we should display is in the AccountNumber


'Response parameter
lNumber = Request("AccountNumber")
'Retrieve the account information
Set localAccount = Session("g_AccountsCol").item(lNumber)
If localAccount Is Nothing Then
'The account was not located

'add the new account


'create a blank object
Set localAccount = CreateObject("OrderEntry.Account")
Call SetProperties(localAccount)
'add to the data source
Session("g_AccountsCol").Add localAccount
%>
<p align="center"><font size="5">
<strong>Account successfully added!</strong>
</font></p>
<%
Else
'The account was located

'update the existing account


Call SetProperties(localAccount)
'add to the data source
Session("g_AccountsCol").Update localAccount
%>
<p align="center"><font size="5">
<strong>Account successfully updated!</strong>
</font></p>
<%
End If
'Release the object's reference
Set localAccount = Nothing
%>

Identifying Existing Accounts

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-4b The client/server version of the same functionality.

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.

Figure 9-5b The client/server version of the same functionality.

The ListAccounts.asp file is used to generate an HTML form-based UI comprised of a


listbox containing the names of each customer account in the data source, and a Details button
(see Figure 9-5a). After obtaining a reference to a Session-level Accounts object, the
ListAccounts.asp file moves to the first record of the data source and iterates through each
record, adding each account name and account number to the listbox, which is generated
using the HTML <SELECT> tag:

<select name="lstListing" size="15">


<%
Dim localObject

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

'The account that we should display is in the AccountNumber


'Response parameter
lNumber = Request("AccountNumber")
'Retrieve the account information
Set localAccount = Session("g_AccountsCol").item(lNumber)
If localAccount Is Nothing Then
'The account was not located
%>
<p align="center"><font size="5">
<strong>No such account could be located.</strong>
</font></p>
<%
Else
'The account was located
%>
<p><font size="5">
<strong>Modify Existing Account </strong>
</font></p>

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

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

'The account that we should deactivate is in the AccountNumber


'Response parameter
lNumber = Request("AccountNumber")
'Retrieve the account information
Set localAccount = Session("g_AccountsCol").item(lNumber)
If localAccount Is Nothing Then
'The account was not located
%>
<p align="center"><font size="5">
<strong>No such account could be located.</strong>
</font></p>
<%
Else
'The account was located
'deactivate the account
Session("g_AccountsCol").Remove localAccount.Number
%>
<p align="center"><font size="5">
<strong>Account successfully deactivated!</strong>
</font></p>
<%
End If
'Release the object's reference
Set localAccount = Nothing
%>

Limitations of the Web Application Architecture


While the Web version of the order-entry application definitely solves some of the major problems
plaguing the client/server version of the application, specifically the installation, configuration,
and support issues, it also presents an entirely new set of problems. The most fundamental
problem with the basic Web application architecture is that the Web application is only able to
access server-side resources. This also means that developers must use an entirely new, unfamiliar
programming model to develop Web applications. For example, the client/server version of the
order-entry application simply maintains global references to the various objects that provide
access to the underlying data source, using them whenever it needs to interact with the data
source. However, since the Web version of the application doesn’t have access to client-side
system resources, specifically ODBC, this technique doesn’t work. Instead, the Web version of the
order-entry application must take a two-step approach, in which data is gathered using one HTML
page, and processed on the server as part of the creation of a second, ASP-generated, HTML page.

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.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------

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

Name Level Description

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

Name Level Description

RPC_C_IMP_LEVEL_ANONYMOUS 1 The client is anonymous to the


server, preventing the server
from obtaining identification
information or impersonating the
client. (Currently unsupported)
RPC_C_IMPLEVEL_IDENTIFY 2 The server can impersonate the
client to ascertain access
privileges, but not to actually
access resources.
RPC_C_lMP_LEVEL_IMPERSONATE 3 The server can impersonate the
client to access resources.
RPC_C_IMP_LEVEL_DELEGATE 4 The server can impersonate the
client to not only access
resources, but also to make calls
to other servers. (Currently
unsupported)

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

As Figure 10-1 illustrates, DCOMCNFG has two tabs for providing


system-wide configuration information: Default Properties and Default
Security. As you can see in Figure 10-2, the Default Properties tab allows you
to:
• Enable/disable DCOM for the entire computer.
Manipulates the EnableDCOM value under the
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registry
key to control whether or not client applications can launch or connect
to any installed servers or classes. A Y means DCOM is enabled; an N
means it’s not.
• Specify the default authentication level.
Manipulates the LegacyAuthenticationlevel value under the
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registry
key to control which level of authentication to use in the event that an
object doesn’t supply an authentication level of its own. See Table 10-1
for legal values.
• Specify the default impersonation level.
Manipulates the Legacy Impersonationlevel value under the
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registry
key to control which level of impersonation to use in the event that an
object doesn’t supply an impersonation level of its own. See Table 10-2
for legal values.
• Request additional security for client calls to an object’s AddRef
and Release methods. Manipulates the
LegacySecureReferences value under the
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registry
key to control whether or not security should be applied to client calls
on an object’s AddRef and Release methods. A Y means add
security; an N means don’t.

Figure 10-2 System-wide DCOM configuration information is accessed from


the Default Properties tab of DCOMCNFG.
The other DCOMCNFG tab useful for providing system-wide configuration
information is the Default Security tab (see Figure 10-3). The Default Security
tab allows you to:
• Control which users are allowed access to registered COM
objects. Stores access control information under the
DefaultAccessPermission value of the
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registry
key for those users authorized to access any of the system’s registered
COM objects.
• Control which users are allowed to launch a COM server. Stores
access control information under the DefaultLaunchPermission
value of the
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registry
key for those users authorized to launch any of the system’s registered
COM servers. (NT only)
• Control which users are allowed to modify OLE class
configuration information in the system registry. Manipulates the
system registry’s Access Control List (ACL). (NT only)

Figure 10-3 System-wide DCOM security information is accessed from the


Default Security tab of DCOMCNFG.
Because these values affect the entire machine, your applications typically will
never need to modify these values, which should only be modified by the
system’s administrators.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

-----------
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):

localAccountObj.GetProperties lNumber, sBalance, lLimit, sName,


sSex, sAddress, sCity, sState, sZip, sPhone, bInService
Figure 10-7 You can reduce the number of network round-trips associated with retrieving
several individual properties by implementing a single method that returns multiple property
values.
Likewise, the localAccount Account object would support a single method, perhaps
with optional named parameters, that would allow you to set multiple properties at the same
time:

localAccountObj.SetProperties Balance:=sBalance, Limit:=lLimit,


Name:=sName, Sex:=sSex, Address:=sAddress, City:=sCity,
State:=sState, Zip:=sZip, Phone:=sPhone,
InService:=bInService

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

HRESULT QueryMultiplelnterfaces(ULONG cMQls, MULTI_QI *pMQls);


where:
• cMQIs is the number of MULTI_QI structures in the array pointed to by pMQIs.
• pMQIs is a pointer to an array of MULTI_QI structures.
A MULTI_QI structure is defined as

typedef struct _MULTI_QI {


const IID* pIID;
IUnknown * pItf;
HRESULT hr;
}MULTI_QI ;
where:
• pIID is the IId of the desired interface.
• pItf is the pointer that will receive the actual interface pointer. Must be NULL on
entry.
• hr is the result of the remote QueryInterface call used to actually retrieve the
interface pointer. Must be zero on entry.
Upon the successful completion of QueryMultipleInterfaces, each MULTI_QI
structure in the array will contain an interface pointer value (pItf) and a return value (hr) that
can be used to determine whether or not pItf contains a valid interface pointer.
Besides IMultiQI, DCOM provides several additional APIs that enable you to use DCOM
programmatically, as opposed to using DCOMCNFG or mucking with the system registry. You
can find a complete list of these new APIs in the DCOM section of Appendix A.
Now that you have more insight into what DCOM is and how it can and should be used, let’s
see how we can use DCOM to improve the architectures of both the client/server and Web
versions of the order-entry application.

Improving the Client/Server Order-Entry Application


In Chapter 7, we built eight interoperable COM objects as part of the cohesive OrderEntry
object hierarchy. Then, in Chapter 8, we used that object hierarchy to develop a client/server
order-entry application. The client/server architecture afforded us the ability to use client-side
system resources, such as the ODBC API, as well as server-side resources, such as the
ODBC-compliant DBMS itself. However, we also discovered in Chapter 8 that the client/server
version of the order-entry application has several significant limitations:
• It is time-consuming to properly install and configure client-side resources, such as
ODBC, on each client machine.
• It is time-consuming to deploy the client side of the application to each client machine.
• It is difficult to update and synchronize a single version of the client/server application
across every client machine.
While using DCOM won’t make it any easier to deploy the client side of the application to each
client machine, and it won’t make it any easier to synchronize a single version of the
client/server application across every client machine, it can eliminate the need to install and
configure ODBC on each client machine. If you recall, the OrderEntry object hierarchy is
comprised of two different types of objects: entry objects, which are used to maintain fairly
static information; and collection objects, which are used to interact with the underlying ODBC
data source. It is the collection objects that ultimately require each client machine to have
ODBC properly installed and configured. However, by moving the collection objects to the
server, and accessing them remotely from the client using DCOM, we eliminate the need to
install and configure ODBC on each client machine. We then deploy the entry objects to each
client machine as part of the order-entry weblication itself. Finally, we add the
GetProperties and SetProperties methods described earlier to each entry object to
help minimize any unnecessary network round-trips that may occur whenever a collection
object needs to access multiple properties from a remote entry object. Following is the IDL
definition for the Account object’s new GetProperties and SetProperties methods:

[helpstring("Sets multiple properties simultaneously.")]


HRESULT SetProperties([in] long lNumber, [in] float fbalance,
[in] long lLimit, [in] BSTR bstrName, [in] BSTR bstrSex,
[in] BSTR bstrAddress, [in] BSTR bstrCity,
[in] BSTR bstrState, [in] BSTR bstrZip, [in] BSTR bstrPhone,
[in] VARIANT_BOOL vbInService);
[helpstring("Returns multiple properties simultaneously.")]
HRESULT GetProperties([out] long *lRetNumber,
[out] float *fRetBalance, [out] long *lRetLimit,
[out] BSTR *bstrRetName, [out] BSTR *bstrRetSex,
[out] BSTR *bstrRetAddress, [out] BSTR *bstrRetCity,
[out] BSTR *bstrRetState, [out] BSTR *bstrRetZip,
[out] BSTR *bstrRetPhone,
[out] VARIANT BOOL *vbRetInService);

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

[helpstring("Returns an Account from the database.")]


HRESULT Item([in] IAccount *pIAccount, [in, optional,
defaultvalue(-1)] VARIANT Key);
As a result, instead of writing the following code to retrieve information for a specific account:

Dim localAccount As Object

Set localAccount = g_AccountsCol.Item(lNumber)

.
.//Manipulate retrieved object
.
the client will use code like this:

Dim localAccount As Object

Set localAccount = CreateObject("OrderEntryClient.Account")


g_AccountsCol.Item localAccount, lNumber
.
.//Manipulate retrieved object
.

Previous Table of Contents Next

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

SDWORD cbCity = SQL_NTS;


SDWORD cbState = SQL_NTS;
SDWORD cbZip = SQL_NTS;
SDWORD cbPhone = SQL_NTS;
SDWORD cbInService = O;

//coerce the incoming VARIANT into a long


VariantChangeType(&Key, &Key, VARIANT-NOVALUEPROP, VT_I4);
//retrieve the converted long value lNumber = V_I4(&Key);
//determine if the default is being used
//which would signal us to retrieve the data pointed to by
//the current record pointer
if (-1 == lNumber)
{
//only return an object if not BOF or EOF
if (m_bBOF || m_bEOF)
return NOERROR;

//the user is trying to retrieve the current Account


//copy the data from the appropriate member functions
//used to store the values for the current record
//Number
lNumber = m_lNavNumber;
//Balance
flBalance = m_flNavBalance;
//Limit
lLimit = m_lNavLimit
//Name
memcpy(szName, m_szNavName, NAME_LEN);
//Sex
memcpy(szSex, m_szNavSex, SEX_LEN);
//Address
memcpy(szAddress, m_szNavAddress, ADDRESS_LEN);
//City
memcpy(szCity, m_szNavCity, CITY_LEN);
//State
memcpy(szState, m_szNavState, STATE_LEN);
//Zip
memcpy(szZip, m_szNavZip, ZIP_LEN);
//Phone
memcpy(szPhone, m_szNavPhone, PHONE_LEN);
//InService vblnservice = m_vbNavInService;
}
else
{
//the user is trying to retrieve a specific account

//Setup parameter transfer buffers


//Number
SQLBindParameter(m_hstmtQuery, 1, SQL_PARAM_INPUT,
SQL_C_SLONG, SQL_INTEGER, O, O, &lNumber, O,
&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;

//Set up transfer buffers


//Number
SQLBindCol(m_hstmtQuery, 1, SQL_C_SLONG, &lNumber, 0,
&cbNumber);
//Balance
SQLBindCol(m_hstmtQuery, 2, SQL_C_FLOAT, &flBalance, 0,
&cbBalance);
//Limit
SQLBindCol(m_hstmtQuery, 3, SQL_C_SLONG, &lLimit, 0,
&cbLimit);
//Name
SQLBindCol(m_hstmtQuery, 4, SQL_C_CHAR, szName, NAME_LEN,
&cbName);
//Sex
SQLBindCol(m_hstmtQuery, 5, SQL_C_CHAR, szSex, SEX_LEN,
&cbSex);
//Address
SQLBindCol(m_hstmtQuery, 6, SQL_C_CHAR, szAddress,
ADDRESS_LEN,
&cbAddress);
//City
SQLBindCol(m_hstmtQuery, 7, SQL_C_CHAR, szCity, CITY_LEN,
&cbCity);
//State
SQLBindCol(m_hstmtQuery, 8, SQL_C_CHAR, szState,
STATE_LEN, &cbState);
//Zip
SQLBindCol(m_hstmtQuery, 9, SQL_C_CHAR, szZip, ZIP_LEN,
&cbZip)
//Phone
SQLBindCol(m_hstmtQuery, 10, SQL_C_CHAR, szPhone,
PHONE_LEN, &cbPhone);
//InService
SQLBindCol(m_hstmtQuery, 11, SQL_C_SSHORT, &vbInService,
0, &cbInService);

//get the next row


retcode = SQLFetch(m_hstmtQuery);
//unbind any columns
SQLFreeStmt(m_hstmtQuery, SQL_UNBIND);
//close the recordset SQLFreeStmt(m_hstmtQuery, SQL-CLOSE);
if (SQL_NO_DATA == retcode)
return NOERROR;
if (!SQL_SUCCEEDED(retcode))
return E_UNEXPECTED;
}
//Name
//convert the string to a unicode string
mbstowcs(wszName, szName, NAME_LEN);
//Sex
//convert the string to a unicode string
mbstowcs(wszSex, szSex, SEX_LEN);
//Address
//convert the string to a unicode string
mbstowcs(wszAaddress, szAddress, ADDRESS_LEN);
//City
//convert the string to a unicode string
mbstowcs(wszCity, szCity, CITY_LEN);
//State
//convert the string to a unicode string
mbstowcs(wszState, szState, STATE_LEN);
//Zip
//convert the string to a unicode string
mbstowcs(wszZip, szZip, ZIP_LEN);
//Phone
//convert the string to a unicode string
mbstowcs(wszPhone, szPhone, PHONE_LEN);
//set the properties of the return object
hr = pIAccount->SetProperties(lNumber, flBalance, lLimit,
wszName, wszSex, wszAddress, wszCity, wszState, wszZip
wszPhone, vbInservice);

return hr;
}//Item

Improving the Web Order-Entry Application


In Chapter 9 we developed a Web version of the order-entry application, which was made up of
both statically generated HTML pages and dynamically generated HTML pages. The dynamically
generated HTML pages are created using Microsoft’s Active Server Pages (ASP). Each ASP is
made up of a combination of HTML and application script logic. While ASP supports a variety of
scripting languages, the order-entry weblication was developed using Visual Basic Scripting Edition
(VBScript). While the Web version of the order-entry application manages to overcome the
limitations of the client/server version, it suffers from several significant limitations of its own:
• It is limited to server-side resources only.
• Developers must use an entirely new, unfamiliar programming model to develop
weblications.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Set localAccount = Nothing


Set localAccountsCol = Nothing
Window.location.href = "SuccessMessage.htm"
End Sub
->
</script>
Listing 10-4. VBScript code from the AccountInfoForm of the client/server version of the
order-entry application responsible for actually adding new customer accounts.

Private Sub cmdOK_Click()


If f_ADDING Then
Set f_localAccount = CreateObject
("DCOMOrderEntryClient.Account")
End If
f_localAccount.Balance = CLng(txtBalance.Text)
f_localAccount.Limit = CLng(txtLimit.Text)
'
f_localAccount.Name = Trim$(txtName.Text)
f_localAccount.Sex = Trim$(txtSex.Text)
f_localAccount.Address = Trim$(txtAddress.Text)
f_localAccount.City = Trim$(txtCity.Text)
f_localAccount.State = Trim$(txtState.Text)
f_localAccount.Zip = Trim$(txtZip.Text)
f_localAccount.Phone = Trim$(txtPhone.Text)
f_localAccount.InService = (chkInService.Value = 1)
If f_ADDING Then
'add to the data source
g_AccountsCol.Add f_localAccount
Else
'update the current product
g_AccountsCol.Update f_localaccount
End If
Unload Me
End Sub

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

Previous Table of Contents Next

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

Set localAccountsCol = CreateObject


("DCOMOrderEntryServer.Accounts")
Set localAccount = CreateObject
("DCOMOrderEntryClient.Account")

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

Set localAccount = Nothing


Set localAccountsCol = Nothing
'notify the user that the operation was successful
Window.location.href = "../SuccessMessage.htm"
End Sub
->
</script>
IE 3.0’s support for VBScript, combined with its intrinsic support for COM objects, enables us
to create a Web DCOM version of the order-entry application using the same basic
architectural design that we used earlier to create the client/server DCOM version of the
application. In addition, because the DCOM Web version of the application is still
HTML-based, we can continue to use the Web as a way to distribute the application to each
client’s machine.
The ideal application architecture seems to be a combination of DCOM and a hybrid
client/server/weblication solution. What we want to do is use HTML to create the application’s
user interface; program the HTML-generated UI in such a way that we can create the various
objects of the OrderEntry object hierarchy; and manipulate the objects using client-side code.
This allows us to:
• Leverage the weblication architecture’s distribution model of HTML over HTTP to
ensure on-demand delivery of the latest version of the order-entry application to each
client desktop
• Eliminate the need to install and configure ODBC on each client machine
• Continue to develop applications using the familiar client/server COM programming
model

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.

Previous Table of Contents Next


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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

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

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

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

The Microsoft Website


Web Site URL
The Microsoft OLE Web Site http://www.microsoft.com/oledev/
The Microsoft Developer Network http://www.microsoft.com/msdn/
The Microsoft Knowledge Base http://www.microsoft.com/kb/
Microsoft TechNet World Wide Web http://www.microsoft.com/technet/
Edition
Site Builder Network http://www.microsoft.com/sitebuilder/
Developer Network News http://www.microsoft.com/devnews/

Table of Contents

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

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

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

Sample Source Code


The source code files on the CD-ROM are arranged according to chapter and
can be copied directly to your hard drive. The C++ samples in this book were
built using Microsoft Visual C++5.0 and can be built using either an MS
VC++5.0 project file or a standard makefile, both of which are included on the
CD-ROM. Each project has four folders: Debug, Release, DebugU, and
ReleaseU, supporting both ANSI and Unicode debug and release builds. The
Visual Basic samples in this book were built using Microsoft Visual Basic 4.0.
As you investigate the various source code files, keep in mind that each
chapter builds on information covered in previous chapters, and as a result,
you may need to refer to source code files from previous chapters.

The COM Specification


The COM Specification provides in-depth information about the Component
Object Model and is a very valuable resource. On the CD-ROM, you will find
a folder named com_spec. This folder contains two additional folders: doc and
rtf, which contain versions of the October 24, 1995 draft of the Component
Object Model Specification. Printed out, the COM Specification is roughly
270 pages. However, to facilitate on-line reading and printing, the document
exists in multiple formats. The doe folder contains each chapter of the
specification in a separate Word 6.0 document. The filenames for each
document reflect the contents of that chapter (e.g. CH08 Security.doc). The
The COM Specification.DOC file is a Word 6.0 Master Document that holds
all of the individual chapters together as well as providing the title page, table
of contents, and appendix. The rtf folder contains a single Rich Text Format
(RTF) file, COM_Spec.RTF.

DCOM for Windows 95


While DCOM is included as part of Windows NT 4.0, it is not included as part
of Windows 95. However, if you look in the DCOM95 folder on the
companion CD-ROM, you will find two self-extracting executables:
DCOM95.EXE and DCM95CFG.EXE. DCOM95.EXE will install version
1.0 of DCOM for Windows 95, and DCM95CFG.EXE will install the
Windows 95 version of DCOMCNFG.EXE. However, if you are currently
running Internet Explorer 4.0 (IE 4.0) or Windows 98, you should not install
either of these two executables, as both of these products ship with more
current builds of DCOM. While there are several differences between DCOM
on Windows 95 and DCOM on Windows NT, one of the biggest is that by
default, DCOM for Windows 95 doesn’t allow COM servers to act as DCOM
servers. Thank goodness this functionality is controlled by the
EnableRemoteConnect named-value under the
HKEY_LOCAL_MACHINE\Software\Microsoft\ OLE registry key. This
value is set to “N” by default, which means that the machine is allowed to
connect to remote objects, but cannot act as a server. Setting this value to “Y”
allows remote clients to connect to running objects. Notice I said running
object; unlike Windows NT, DCOM for Windows 95 only allows clients to
connect to running COM servers. DCOM for Windows 95 will not launch a
COM server. Additional information regarding DCOM for Windows 95
including the latest builds can be obtained from the Microsoft Web site at
http://www.microsoft.com/oledev/olemkt/oledcom/dcom95.htm.

Table of Contents
[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.

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

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

Contents of a Type Library


Type libraries are compound document files (.tlb files) that include information about types and objects
exposed by an ActiveX application. A type library can contain any of the following:
• Information about data types, such as aliases, enumerations, structures, or unions.
• Descriptions of one or more objects, such as a module, interface, IDispatch interface
(dispinterface), or COM object class (coclass). Each of these descriptions is commonly referred to
as a typeinfo.
• References to type descriptions from other type libraries.
By including the type library with a product, the information about the objects in the library can be made
available to the users of the applications and programming tools. Type libraries can be shipped in any of
the following forms:
• A resource in a dynamic link library (DLL). This resource should have the type TypeLib and an
integer identifier. It must be declared in the resource (.rc) file as follows:

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.

Using MIDL and MkTypLib


Files parsed by MkTypLib are .odl files. Files parsed by MIDL are referred to as .idl files, although they
can contain the same syntax elements as .odl files. The MIDL compiler and the MkTypLib utility both
compile scripts written in ODL. However, MkTypLib is obsolete, and you should use the MIDL compiler
instead. The following sections describe the differences and special considerations for using MkTypLib
and MIDL to create type libraries.

Adding ODL to an IDL Definition


The .odl files provide object definitions that are added to the type descriptions in a type library. The
MkTypLib utility parses files written in ODL syntax, generates the type libraries, and optionally creates
C++ header files that contain the same definitions.
The top-level element of ODL syntax is the library statement (or library block). Every other ODL
statement (with the exception of the attributes that can be applied to the library statement) must be
defined in the library block.
The MIDL compiler generates a type library when it sees a library statement in the same way that
MkTypLib does. The statements found in the library block follow essentially the same syntax as earlier
versions of ODL.
ODL attributes can be applied to an element both inside and outside of the library block. Outside the
block, they typically do nothing, unless the element is referenced from within the block by using it as a
base type, inheriting from it, or referencing it on a line such as this:

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.

Differences Between MIDL and MkTypLib


There are a few key areas in which the MIDL compiler differs from the MkTypLib utility. Most of these
differences arise because MIDL is more C-syntax oriented than MkTypLib. All of the differences
described here, with the exception of floating point constants and the enum scope, can be resolved by
using the /mktyplib203 MIDL compiler option (see “The /mktyplib203 Option” later in this appendix).
This switch forces MIDL to behave like MkTypLib.exe, version 2.03, the last release of MkTypLib.exe.
In general, you will want to use the MIDL syntax in your .idl files. However, the /mktyplib203 option
is useful if you need to compile an existing .odl file, or otherwise maintain compatibility with MkTypLib.
TYPEDEF SYNTAX FOR COMPLEX DATA TYPES
In MkTypLib, both of the following definitions generate a TKIND_RECORD for “bar” in the type library.
The tag “foo” is optional and, if used, will not show up in the type library.

typedef struct foo { ... } bar;


typedef struct { ... } bar;
In MIDL, the first definition will generate a TKIND_RECORD for “foo” and a TKIND_ALIAS for “bar”
(defining “bar” as an alias for “foo”). For the second definition, MIDL will generate a TKIND_RECORD
for a mangled name internal to MIDL that is not meaningful to the user and a TKIND_ALIAS for “bar”.
This has potential implications for type library browsers that simply show the name of a record in its user
interface. If you expect a TKIND_RECORD to have a real name, there is a potential for unrecognizable
names to appear in the user interface. This behavior also applies to union and enum definitions, with the
MIDL compiler generating TKIND_UNIONs and TKIND_ENUMs, respectively.
MIDL also allows C-style struct, union, and enum definitions. For example, the following definition
is legal in MIDL:

struct foo { ... };


typedef struct foo bar;
MKTYPLIB AND BOOLEAN DATA TYPES
In MkTypLib, the Boolean base type and the MkTypLib datatype BOOL equate to VT_BOOL, which
maps to VARIANT_BOOL, and which is defined as a short. In MIDL, the Boolean base type is equivalent
to VT_UI1, which is defined as an unsigned char, and the BOOL datatype is defined as a long. This leads
to difficulties if you mix IDL syntax and ODL syntax in the same file while still trying to maintain
compatibility with MkTypLib. Because the datatypes are different sizes, the marshaling code will not
match what is described in the type information. If you want a VT_BOOL in your type library, you should
use the VARIANT_BOOL datatype.
GUID DEFINITIONS IN HEADER FILES
When using the MkTypLib utility, GUIDs are defined in the header file with a macro that can be
conditionally compiled to generate either a GUID predefinition or an instantiated GUID. MIDL normally
puts GUID predefinitions in its generated header files and only puts GUID instantiations in the file
generated by the /iid switch.
SCOPE OF SYMBOLS IN AN ENUM DECLARATION
In MkTypLib the scope of symbols in an enum is local. In MIDL, the scope of symbols in an enum is
global, as it is in C. For example, the following code will compile in MkTypLib, but will generate a
duplicate name error in MIDL:

typedef struct { ... } a;


enum {a=1, b=2, c=3};
SUPPORT FOR ODL BASE TYPES
There are a number of base types supported by MkTypLib that are not directly supported by MIDL. The
MIDL function gets its definitions for these base types by automatically importing oleauto.idl, and
oleidl.idl whenever it encounters a library statement. This means that oleauto.idl, oaidl.idl, and oleidl.idl
(along with the imported unknwn.idl and wtypes.idl files) must be somewhere in the user's INCLUDE
path. The OLE and Automation DLLs must also be in the system if the user compiles an .idl file that
contains a library statement.
THE /MKTYPLIB203 OPTION
The MIDL compiler behaves differently from the MkTypLib utility. The /mktyplib203 option
removes most of these differences and makes MIDL act like MkTypLib, version 2.03.
For example, BOOL (a MkTypLib base type) is defined differently in MIDL than it is in MkTypLib.
MkTypLib treats BOOL as a VARIANT_BOOL. However, BOOL is defined in the file Wtypes.idl as a
long data type. If a VARIANT_BOOL is to be placed in the type library, it has to explicitly use
VARIANT_BOOL in the .idl/.odl file. If BOOL is used when VARIANT_BOOL is meant to be used,
then the /mktyplib203 option should also be used.
MIDL normally puts globally unique identifier (GUID) predefinitions in its generated header files, and
only puts GUID instantiations in the file generated by the /iid option. With the /mktyplib203
option, MIDL defines GUIDs in the header files in the way that MkTypLib does. They are defined with a
macro that can be compiled conditionally to generate either a predefined or an instantiated GUID. With
the /mktyplib203 option enabled, it is invalid to put any statements outside of the library block. A
pure ODL syntax must be used; it cannot be mixed and matched in this mode.
MkTypLib is used to require struct, union, and enum to be defined as part of type definitions. For
example:

typedef struct foo { int i; } bar;


In this statement, MkTypLib generates a TKIND_RECORD named “bar.” Because the “foo” was not
recorded anywhere in the type library, it can be omitted. MIDL allows normal C definitions of struct,
union, and enum:

struct foo {int i;};


typedef struct foo bar;
- Or -

typedef struct foo {int i;} bar;


This statement generates a TKIND_RECORD named “foo” and (if the type definition is public) a
TKIND_ALIAS named “bar.” The “foo” can still be omitted, in which case MIDL generates a name for it.
When the /mktyplib203 option is enabled, the original MkTypLib type definition syntax is required
for structures, unions, and enumerators. The behavior is the same as under MkTypLib (that is, “foo” is not
included in the type library).

MkTypLib: Type Library Creation Tool


MkTypLib processes scripts written in ODL, producing a type library and an optional C or C++ header
file.
MkTypLib uses the ICreateTypeLib and ICreateTypeInfo interfaces to create type libraries.
Type libraries can then be accessed by tools, such as type browsers and compilers that use the ITypeLib
and ITypeInfo interfaces.

Invoking MkTypLib
To invoke MkTypLib, enter the following command line:

MkTypLib [options] ODLfile


MkTypLib creates a type library (.tlb) file based on the object description script in the file specified by
ODLfile. It can optionally produce a header (.h) file, which is a stripped version of the input file. This file
is included in C or C++ programs that want to access the types defined in the input file. In the header file,
MkTypLib inserts DEFINE_GUID macros for each element defined in the type library (such as
interface, dispinterface, and so on).
There can be a series of options, each prefixed with a hyphen (-) or a slash (/), as follows:
Option Description
/? or /help Displays command line Help. In this case, ODLfile does not need to be
specified
/align:alignment Sets the default alignment for types in the library. An alignment value of
1 indicates natural alignment; n indicates alignment on byte n.
/cpp_cmd cpppath Specifies cpppath as the command to run the C preprocessor. By default,
MkTypLib invokes CL.
/cpp_opt “options” Specifies options for the C preprocessor. The default is /C
/E/D_MkTypLib_.
/D define[=value] Defines the name define for the C preprocessor. The value is its optional
value. No space is allowed between the equal sign (=) and the value.
/h filename Specifies filename as the name for a stripped version of the input file.
This file can be used as a C or C++ header file.
/I includedir Specifies includedir as the directory where include files are located for
the C preprocessor.
/nocpp Suppresses invocation of the C preprocessor.
/nologo Disables the display of the copyright banner.
/o outputfile Redirects output (for example, error messages) to the specified outputfile.
/tlb filename Specifies filename as the name of the output .tlb file. If not specified, it
will be the same name as the ODLfile, with the extension .tlb.
/win16 /win32
/mac /mips
/alpha /ppc
/ppc32 Specifies the output type library to be produced. The default is the current
operating system.
/w0 Disables warnings.

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.

ODL File Syntax


The general syntax for an .odl file is as follows:

[attributes] library libname


{
definitions
};
The attributes associate characteristics with the library, such as its Help file and universally unique
identifier (UUID). Attributes must be enclosed in square brackets.
The definitions consist of the descriptions of the imported libraries, data types, modules, interfaces,
dispinterfaces, and coclasses that are part of the type library. Braces ({}) must surround the definitions.
Each module, interface, dispinterface, and coclass in the definitions section follows the same general
syntax:

[attributes] elementname typename


{
memberdescriptions
};
The attributes set characteristics for the element. The elementname is a keyword that indicates the kind of
item (module, interface, dispinterface, or coclass), and the typename defines the name of the item. The
memberdescriptions define the members (constants, functions, properties, and methods) of each element.
Aliases, enumerations, unions, and structures have the following syntax:

typedef [typeattributes] typekind typename


{
memberdescriptions
};
For these types, the attributes follow the typedef keyword, and the typekind indicates the data type
(enum, union, or struct). For details, see “Attribute Descriptions” later in this appendix.

Source File Contents


The following sections describe the proper format for comments, constants, identifiers, and other syntactic
items in an .odl file.
ARRAY DEFINITIONS
MkTypLib accepts both fixed-size arrays and arrays declared as SAFEARRAY. Use a C-style syntax for a
fixed size array:

type arrname[size];
To describe a SAFEARRAY, use the following syntax:

SAFEARRAY (elementtype) *arrayname


A function returning a SAFEARRAY has the following syntax:

SAFEARRAY (elementtype) myfunction(parameterlist);


COMMENTS
To include comments in an .odl file, use a C-style syntax in either block form (/*...*/) or single-line form
(//). MkTypLib ignores the comments and does not preserve them in the header (.h) file.
CONSTANTS
A constant can be either numeric or a string, depending on the attribute.
NUMERIC Numeric input is usually an integer (in either decimal or in hexadecimal, using the standard
0x format), but can also be a single character constant (for example, \0).
STRING A string is delimited by double quotation marks (") and cannot span multiple lines. The
backslash character (\) acts as an escape character. The backslash character followed by any character
(even another backslash) prevents the second character from being interpreted with any special meaning.
The backslash is not included in the text.
For example, to include a double quotation mark (") in the text without causing it to be interpreted as the
closing delimiter, it should be preceded with a backslash (\"). Similarly, a double backslash (\\) should be
used to put a backslash into the text. Some examples of valid strings are:

"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

Set b1 = f.Button1 ' Property syntax


Set b = f!Button1 ' ! syntax
End Sub

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);
}

The following rules apply:


1. It is invalid to specify a default value for a parameter whose type is a safe array. It is
invalid to specify a default value for any type that cannot go in a variant, including structures
and arrays.
2. Parameters can be mixed. Optional parameters and default value parameters must follow
mandatory parameters.
3. The default value can be any constant that is represented by a VARIANT datatype.
Flags
None.
Example

interface QueryDef
{

// Type is now known to be a LONG type (good for browser in VBA


// and for a C/C++ programmer) and also has a default value of
// dbOpenTable (constant).

HRESULT OpenRecordset([in, defaultvalue(dbOpenTable)] LONG Type,


[out,retval] Recordset **pprst);
}
defaultvtbl
Description
Enables an object to have two different source interfaces.
Comments
The default interface is an interface or dispinterface that is the default source interface. If the
interface is a:
• Dual interface, sinks receive events through IDispatch.
• VTBL interface, event sinks receive events through VTBL.
• Dispinterface, sinks receive events through IDispatch.
• Defaultvtable, a default VTBL interface, which cannot be a dispinterface — it must be a
dual, VTBL, or interface. If the interface is a dual interface, then sinks receive events through
the VTBL.

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.

Interface Flag it translates into


default IMPLTYPEFLAG_FDEFAULT
default, source IMPLTYPEFLAG_FDEFAULT
IMPLTYPEFLAG_FSOURCE
defaultvtable,
IMPLTYPEFLAG_FDEFAULT
source
IMPLTYPEFLAG_FDEFAULTVTABLE
IMPLTYPEFLAG_FSOURCE
Flags
IMPLTYPEFLAG_FDEFAULTVTABLE. (If this flag is set, then IMPLTYPEFLAG_ FSOURCE
is also set.)
Example

[odl, dual, uuid(1e196b20-1f3c-1069-996b-00dd010ef676), restricted]


interface IForm: IDispatch
{
[propget]
HRESULT Backcolor([out, retval] long *Value);
[propput]
HRESULT Backcolor([in] long Value);
[propget]
HRESULT Name([out, retval] BSTR *Value);
[propput]
HRESULT Name([in] BSTR Value);
}

[odl, dual, uuid(1e196b20-1f3c-1069-996b-00dd010ef767),


restricted]
interface IFormEvents: IDispatch
{

HRESULT Click();
HRESULT Resize();
}

[uuid(1e196b20-1f3c-1069-996b-00dd010fe676)]
coclass Form
{

[default] interface IForm;


[default, source] interface IFormEvents;
[defaultvtable, source] interface IFormEvents;
}
displaybind
Description
Indicates that a property should be displayed as bindable to the user.
Allowed on
Property.
Comments
Properties that have the displaybind attribute must also have the bindable attribute. An
object can support data binding and not have this attribute.
Flags
FUNCFLAG_FDISPLAYBIND
VARFLAG_FDISPLAYBIND
dllname(str)
Description
Defines the name of the DLL that contains the entry points for a module.
Allowed on
Module (required).
Comments
The str argument gives the file name of the DLL.
dual
Description
Identifies an interface that exposes properties and methods through IDispatch and directly
through the VTBL.
Allowed on
Interface.
Comments
The interface must be compatible with Automation and derive from IDispatch. Not allowed on
dispinterfaces.
The dual attribute creates an interface that is both a Dispatch interface and a COM interface. The
first seven entries of the VTBL for a dual interface are the seven members of IDispatch, and the
remaining entries are COM entries for direct access to members of the dual interface. All of the
parameters and return types specified for members of a dual interface must be compatible with
Automation types.
All members of a dual interface must pass an HRESULT as the function’s return value. Members
that need to return other values should specify the last parameter as [retval, out] indicating an
output parameter that returns the value of the function. In addition, members that need to support
multiple locales should pass an lcid parameter.
A dual interface provides for both the speed of direct VTBL binding and the flexibility of
IDispatch binding. For this reason, dual interfaces are recommended whenever possible.
Specifying dual on an interface implies that the interface is compatible with Automation, and
therefore causes both the TYPEFLAG_FDUAL and TYPEFLAG_FOLEAUTOMATION flags to
be set.
Flags
TYPEFLAG_FDUAL
TYPEFLAG_FOLEAUTOMATION
entry(entryid)
Description
Identifies the entry point in the DLL.
Allowed on
Functions in a module (required).
Comments
If entryid is a string, this is a named entry point. If entryid is a number, the entry point is defined by
an ordinal. This attribute provides a way to obtain the address of a function in a module.
helpcontext(numctxt)
Description
Sets the context in the Help file.
Allowed on
Library, interface, dispinterface, struct, enum, union, module, typedef, method,
struct member, enum value, property, coclass, const.
Comments
Retrieved by the GetDocumentation functions in the ITypeLib and ITypeInfo interfaces.
The numctxt is a 32-bit Help context identifier in the Help file.
helpfile(filename)
Description
Sets the name of the Help file.
Allowed on
Library.
Comments
Retrieved through the GetDocumentation functions in the ITypeLib and ITypeInfo
interfaces.
All types in a library share the same Help file.
helpstring(string)
Description
Sets the Help string.
Allowed on
Library, interface, dispinterface, struct, enum, union, module, typedef, method,
struct member, enum value, property, coclass, const.
Comments
Retrieved through the GetDocumentation functions in the ITypeLib and ITypeInfo
interfaces.
helpstringcontext(contextid)
Description
Sets the string context in the Help file.
Allowed on
Type library, type information (Typeinfo), function, and variable level.
Comments
Retrieved by the GetDocumentation2 functions in the ITypeLib2 and ITypeInfo2
interfaces. The contextid is a 32-bit Help context identifier in the Help file.
helpstringdll(dllname)
Description
Sets the name of the DLL to use to perform the document string lookup (localization).
Allowed on
Type library.
Comments
Retrieved through the GetDocumentation2 functions in the ITypeLib2 and ITypeInfo2
interfaces.
hidden
Description
Indicates that the item exists, but should not be displayed in a user-oriented browser.
Allowed on
Property, method, coclass, dispinterface, interface, library.
Comments
This attribute allows members to be removed from an interface by shielding them from further use,
while maintaining compatibility with existing code.
When specified for a library, the attribute prevents the entire library from being displayed. It is
intended for use by controls. Hosts need to create a new type library that wraps the control with
extended properties.
Flags
VARFLAG_FHIDDEN
FUNCFLAG_FHIDDEN
TYPEFLAG_FHIDDEN
id(num)
Description
Identifies the DISPID of the member.
Allowed on
Method or property in an interface or dispinterface.
Comments
The num is a 32-bit integral value in the following format:

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.

Negative identifiers are reserved for use by Automation.


immediatebind
Description
Allows individual bindable properties on a form to specify this behavior. When this bit is set, all
changes will be notified.
Comments
Allows controls to differentiate two different types of bindable properties. One type of
bindable property needs to notify every change to the database (for example, with a check box
control where every change needs to be sent through to the underlying database, even though the
control has not lost the focus). However, controls such as a list box need to have the change of a
property communicated to the database when the control loses focus, because the user may have
changed the selection with the arrow keys before finding the desired setting. If the change
notification was sent to the database every time the user pressed an arrow key, it would give an
unacceptable performance.
The bindable and requestedit attribute bits need to be set for this new bit to have an effect.
Flags
VARFLAG_FIMMEDIATEBIND
FUNCFLAG_FIMMEDIATEBIND
in
Description
Specifies an input parameter.
Allowed on
Parameter.
Comments
The parameter can be a pointer (such as char*) but the value it refers to is not returned.
lcid
Description
Indicates that the parameter is a locale ID (LCID).
Allowed on
Parameter in a member of an interface.
Comments
Only one parameter can have this attribute. The parameter must have the in attribute and not the
out attribute, and its type must be long. The lcid attribute is not allowed on dispinterfaces.
The lcid attribute allows members in the VTBL to receive an LCID at the time of invocation. By
convention, the lcid parameter is the last parameter not to have the retval attribute. If the
member specifies propertyput or propertyputref, the lcid parameter must precede the
parameter that represents the right side of the property assignment.
ITypeInfo::Invoke passes the LCID of the type information into the lcid parameter.
Parameters with this attribute are not displayed in user-oriented browsers.
lcid(numid)
Description
This attribute identifies the locale for a type library.
Allowed on
Library.
Comments
The numid is a 32-bit LCID, as used in Win32 National Language Support. The LCID is typically
entered in hexadecimal format.
licensed
Description
Indicates that the class is licensed.
Allowed on
Coclass.
Flags
TYPEFLAG_FLICENSED
nonbrowsable
Description
Indicates that the property appears in an object browser (which does not show property values), but
does not appear in a properties browser (which does show property values).
Allowed on
Property.
Flags
VARFLAG_FNONBROWSABLE
FUNCFLAG_FNONBROWSABLE
noncreatable
Description
Indicates that the class does not support creation at the top level (for example, through
ITypeInfo::CreateInstance or CoCreateInstance). An object of such a class is
usually obtained through a method call on another object.
Allowed on
Coclass.
Example

[
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

[odl, dual, uuid(1e196b20-1f3c-1069-996b-00dd010ef676),


restricted]
interface IForm: IDispatch
{
[propget]
HRESULT Backcolor([out, retval] long *Value);
[propput]
HRESULT Backcolor([in] long Value);

[propget, uidefault]
HRESULT Name([out, retval] BSTR *Value);
[propput, uidefault]
HRESULT Name([in] BSTR Value);
}

[odl, dual, uuid(1e196b20-1f3c-1069-996b-00dd010ef767),


restricted]
interface IFormEvents: Idispatch
{
[uidefault]
HRESULT Click();
HRESULT Resize();
}
[uuid(1e196b20-1f3c-1069-996b-00dd010fe676)]
coclass Form
{
[default] interface IForm;
[default, source] interface IFormEvents;
}
usesgetlasterror
Description
Tells the caller that, if there is an error when calling that function, the caller can then call
GetLastError to retrieve the error code.
Allowed on
Member of a module.
Comments
The usesgetlasterror attribute can be set on a module entry point, if that entry point uses the
Win32 function SetLastError to return error codes. The attribute tells the caller that, if there is
an error when calling that function, the caller can then call GetLastError to retrieve the error
code.
Example

[dllname("MyOwn.dll")]
module MyModule
{
[entry("One"), usesgetlasterror, bindable, requestedit, propputref,
defaultbind]
void Func1 ([in]IUnknown * iParam1, [out] long * Param2) ;

[entry("TwentyOne"), usesgetlasterror, hidden, vararg]


SAFEARRAY (int) Func2 ([in, out] SAFEARRAY (variant) *varP) ;
};
uuid(uuidval)
Description
Specifies the universally unique ID (UUID) of the item.
Allowed on
Required for library, dispinterface, interface, and coclass. Optional for struct, enum, union,
module, and typedef.
Comments
The uuidval is a 16-byte value using hexadecimal digits in the following format:
12345678-1234-1234-1234-123456789ABC. This value is returned in the TypeAttr structure
retrieved by ITypeInfo::GetTypeAttr.
vararg
Description
Indicates a variable number of arguments.
Allowed on
Function.
Comments
Indicates that the last parameter is a safe array of VARIANT type, which contains all of the
remaining parameters.
version(versionval)
Description
Specifies a version number.
Allowed on
Library, struct, module, dispinterface, interface, coclass, enum, union.
Comments
The argument versionval is a real number in the format n.m, where n is a major version number and
m is a minor version number.

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

[attributes] dispinterface intfname


{
properties:
proplist
methods:
methlist
};
Syntax 2

[attributes] dispinterface intfname


{
interface interfacename
};
Syntax Elements
attributes
The helpstring, helpcontext, hidden, uuid, and version attributes are accepted
before dispinterface. For more information about the attributes accepted before a dispinterface
definition, see “Attribute Descriptions ” earlier in this appendix. Attributes (including the brackets)
can be omitted, except for the uuid attribute, which is required.
intfname
The name by which the dispinterface is known in the type library. This name must be unique within
the type library.
interfacename
(Syntax 2) The name of the interface to declare as an IDispatch interface.
proplist
(Syntax 1) An optional list of properties supported by the object, declared in the form of variables.
This is the short form for declaring the property functions in the methods list. See the comments
section for details.
methlist
(Syntax 1) A list comprising a function prototype for each method and property in the dispinterface.
Any number of function definitions can appear in methlist. A function in methlist has the following
form:

[attributes] returntype methname(params);

The following attributes are accepted on a method in a dispinterface: helpstring,


helpcontext, string (for compatibility with the Interface Definition Language), bindable,
defaultbind, displaybind, propget, propput, propputref, and vararg. If
vararg is specified, the last parameter must be a safe array of VARIANT type.
The parameter list is a comma-delimited list, each element of which has the following form:
[attributes] type paramname

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();

[id(1), propput, bindable, defaultbind, displaybind]


void x(long rhs);
}
enum
This statement defines a C-style enumerated type.
Syntax

typedef [attributes] enum [tag]


{
enumlist
} enumname;
Syntax Elements
attributes
The helpstring, helpcontext, hidden, and uuid attributes are accepted before an enum
statement. The helpstring and helpcontext attributes are accepted on an enumeration
element. For more information about the attributes accepted before an enumeration definition, see
“Attribute Descriptions” earlier in this appendix. Attributes (including the brackets) can be omitted.
If uuid is omitted, the enumeration is not uniquely specified in the system.
tag
An optional tag, as with a C enum.
enumlist
List of enumerated elements.
enumname
Name by which the enumeration is known in the library.
Comments
The enum keyword must be preceded by typedef. The enumeration description must precede
other references to the enumeration in the library. If value is not specified for enumerators, the
numbering progresses, as with enumerations in C. The type of the enum element is int, the system
default integer, which depends on the target type library specification.
Examples

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

[attributes] interface interfacename [:baseinterface]


{
functionlist
};
Syntax Elements
attributes
The attributes dual, helpstring, helpcontext, hidden, odl, oleautomation, uuid,
and version are accepted before interface. If the interface is a member of a coclass, the
attributes source, default, and restricted are also accepted. For more information about
the attributes that can be accepted before an interface definition, refer to the section “Attribute
Descriptions” earlier in this appendix. The attributes odl and uuid are required on all
interface declarations.
interfacename
The name by which the interface is known in the type library.
Baseinterface
The name of the interface that is the base class for this interface.
Functionlist
List of function prototypes for each function in the interface. Any number of function definitions
can appear in the function list. A function in the function list has the following form:

[attributes] returntype [calling convention] funcname(params);

The following attributes are accepted on a function in an interface: helpstring,


helpcontext, string, propget, propput, propputref, bindable, defaultbind,
displaybind, and vararg. If vararg is specified, the last parameter must be a safe array of
VARIANT type. The optional calling convention can be __pascal/_pascal/pascal,
__cdecl/_cdecl/cdecl, or __stdcall/_stdcall/stdcall. The calling convention
specification can include up to two leading underscores.
The parameter list is a comma-delimited list, as follows:

[attributes] type paramname

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);

// A method returning a string.


HRESULT SayMessage([in] long NumTimes, [in, lcid]
DWORD lcid, [out, retval] BSTR *pbstrRetVal);
}

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

[attributes] library libname


{
definitions
};
Syntax Elements
attributes
The helpstring, helpcontext, lcid, restricted, hidden, control, and uuid
attributes are accepted before a library statement. For more information about the attributes
accepted before a library definition, see “Attribute Descriptions” earlier in this appendix. The
uuid attribute is required.
libname
The name by which the type library is known.
definitions
Descriptions of any imported libraries, data types, modules, interfaces, dispinterfaces, and coclasses
relevant to the object being exposed.
Comments
The library statement must precede any other type definitions.
Example

[
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

[attributes] module modulename


{
elementlist
};
Syntax Elements
attributes
The attributes uuid, version, helpstring, helpcontext, hidden, and dllname are
accepted before a module statement. For more information about the attributes that can be
accepted before a module definition, see “Attribute Descriptions” earlier in this appendix. The
dllname attribute is required. If uuid is omitted, the module is not uniquely specified in the
system.
modulename
The name of the module.
elementlist
List of constant definitions and function prototypes for each function in the DLL. Any number of
function definitions can appear in the function list. A function in the function list has the following
form:

[attributes] returntype [calling convention] funcname(params);


[attributes] const constname = constval;
Only the attributes helpstring and helpcontext are accepted for a const. The following
attributes are accepted on a function in a module: helpstring, helpcontext, string,
entry, propget, propput, propputref, vararg. If vararg is specified, the last
parameter must be a safe array of VARIANT type.
The optional calling convention can be one of __pascal/_pascal/pascal,
__cdecl/_cdecl/cdecl, or stdcall/stdcall. The calling convention can include up to
two leading underscores.
The parameter list is a comma-delimited list.

[attributes] type paramname

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

typedef [attributes] basetype aliasname;


Syntax Elements
attributes
Any attribute specifications must follow the typedef keyword. If no attributes and no other type
(for example, enum, struct, or union) are specified, the alias is treated as a #define and does
not appear in the type library. If no other attribute is desired, public can be used to explicitly include
the alias in the type library. The helpstring, helpcontext, and uuid attributes are accepted
before a typedef. For more information, see “Attribute Descriptions” earlier in this appendix. If
uuid is omitted, the typedef is not uniquely specified in the system.
basetype
The type for which the alias is defined.
aliasname
Name by which the type will be known in the type library.
Comments
The typedef keyword must also be used whenever a struct or enum is defined. The name
recorded for the enum or struct is the typedef name, and not the tag for the enumeration. No
attributes are required to make sure the alias appears in the type library.
Enumerations, structures, and unions must be defined with the typedef keyword. The attributes
for a type defined with typedef are enclosed in brackets following the typedef keyword. If a
simple alias typedef has no attributes, it is treated like a #define, and the aliasname does not
appear in the library. Any attribute (public can be used if no others are desired) specified between
the typedef keyword and the rest of a simple alias definition causes the alias to appear explicitly
in the type library. The attributes typically include such items as a Help string and Help context.
Examples

typedef [public] long DWORD;

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

typedef [attributes] union [tag]


{
memberlist
} unionname;
Syntax Elements
attributes
The attributes helpstring, helpcontext, uuid, hidden, and version are accepted
before a union. The helpstring, helpcontext, and string attributes are accepted on a
union member. For more information about the attributes accepted before a union definition, see
“Attribute Descriptions” earlier in this appendix. Attributes (including the square brackets) can be
omitted. If uuid is omitted, the union is not uniquely specified in the system.
tag
An optional tag, as with a C union.
memberlist
List of union members defined with C syntax.
unionname
Name by which the union is known in the type library.
Comments
The union keyword must be preceded with a typedef. The union description must precede
other references to the structure in the library. Members of a union can be of any built-in type, or
any type defined lexically as a typedef before the union. For a description of how strings and
arrays can be entered, see the sections “String Definitions” and “Array Definitions” earlier in this
appendix.
Example

[
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]

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.

DCOM: Microsoft Distributed Component Object Model


(Publisher: IDG Books Worldwide, Inc.)
Author(s): Frank E. Redmond III
Go! ISBN: 0764580442
Publication Date: 09/01/97
Keyword
Brief Full
Advanced
Search Search this book:
Search Tips
Go!

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

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

You might also like