6935316 DCOM Microsoft Distributed Component Object Model
-
Upload
stevenronald3136 -
Category
Documents
-
view
310 -
download
6
Transcript of 6935316 DCOM Microsoft Distributed Component Object Model
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Preface
About the Author
Part 1—Introduction to COM
Chapter 1—A COM OverviewCOM: 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 ServersThe 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 ServersThe 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 ObjectsUnderstanding Containment
Understanding Aggregation
Summary
Chapter 5—Building Automation ObjectsAn 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 ControllersBuilding 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 HierarchiesDefining 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/ServerOrder-Entry Application
Understanding the Order-Entry Application
Understanding the Client/Server ApplicationArchitecture
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 ApplicationArchitecture
Summary
Chapter 9—Building the Web Order-EntryApplication
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 DCOMDCOM Security
Using DCOMCNFG
Providing System-Wide ConfigurationInformation
Providing Object-Specific ConfigurationInformation
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 inMIDL
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Table of Contents
PrefaceMicrosoft’s Distributed Component Object Model (DCOM) provides thesoftware infrastructure you need to build your next-generation distributedapplication. However, you will need to know more than just “how to buildCOM objects” to successfully develop that next-generation distributedapplication. While learning to build COM objects is important, for corporateapplications developers it is only the beginning. After building the necessaryCOM objects, the corporate applications developers must then assemble theminto a complete application, which is no trivial matter. A poorly writtencomponent-based application will perform as equally unsatisfactorily as apoorly written monolithic application. DCOM: Microsoft® DistributedComponent Object Model not only teaches you the fundamentals of COM andbuilding COM objects, but also goes the extra mile to show you how to buildcomponentized 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 usingcontainment and aggregation, COM’s implementation inheritancemechanisms
• Create COM objects that support custom interfaces as well as COMobjects 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-basedapplication architectures.
Who This Book Is For
If you are responsible for the architecture, development, or deployment of acorporate enterprise application, then this book is for you! This book willprovide you with a firm understanding of COM through a combination ofclear, concise explanations and related samples. In addition to providing youwith a firm understanding of COM, DCOM: Microsoft® DistributedComponent Object Model will also teach you how to create both client/serverand web-based applications using COM. Finally, this book will show you howDCOM can be used to improve both the client/server and web-basedapplication 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 anoperating system that supports DCOM, a C++ compiler that supports thedevelopment of COM objects, Microsoft Internet Explorer 3.0.2, and VisualBasic. Even though Windows NT 4.0 Workstation and Server have built-insupport for DCOM, you should install the latest service pack from theMicrosoft web site () DCOM support for Windows 95 is freely downloadablefrom the Microsoft web site as well. Information regarding DCOM support forother 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 ofMicrosoft 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 freelydownloaded from the Microsoft web site.
The sample DCOM source code provided throughout this book and on theaccompanying CD-ROM were developed using Microsoft Visual C++ 5.0 andtested 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 usingMicrosoft Internet Explorer version 3.0.2.
What’s in This Book
For corporate application developers, building COM objects is just half thestory; the other half is actually using those COM objects to develop their finalapplication. DCOM: Microsoft® Distributed Component Object Model takesexactly this approach. DCOM: Microsoft® Distributed Component ObjectModel is divided into two parts. The first part begins with a conceptualoverview of what COM is and how COM works, by describing the variouspieces of COM and how they interact, and goes on to illustrate thefundamentals of COM development. This is the “how to build COM objects”
part. The second part describes how to encapsulate application functionalityinto a single tightly integrated group of COM objects called an objecthierarchy and ultimately describes how to use the object hierarchy to buildboth 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 COMinfrastructure, everything from the System Registry to the Service ControlManager (SCM). You learn how to build COM interfaces, COM objects, COMservers, and COM clients. You also learn how to reuse existing COM objectsthrough aggregation and containment, COM’s mechanisms for implementationinheritance. Finally, you learn how to implement COM objects that supportAutomation 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 understandingof COM by creating a tightly integrated group of COM objects called an objecthierarchy. You then learn how to use this object hierarchy to develop an orderentry application using both the client/server and web-based applicationarchitectures. Finally, you learn how DCOM can be used to improve both ofthese application architectures.
How to Reach Me
If you have any questions, comments, or suggestions, you can reach methrough my CompuServe account at [email protected].
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Table of Contents
About the AuthorFrank E. Redmond III is a Software Design Engineer in Microsoft’s DeveloperRelations Group (DRG), where he assists strategic Independent SoftwareVendors (ISVs) by providing them with technical expertise on a wide range ofMicrosoft COM-based technologies. Frank has authored several articles forvarious 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 helpedme formulate my original ideas into this final book, and Matt Lusher, who hadthe thankless task of keeping me on schedule throughout the developmentprocess. I would also like to thank Luann Rouff, Anne Friedman, and SusanParini 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, andeveryone else who helped review the various technical aspects of this book.Special thanks to Brian Staples for taking time out to not only review thisbook, but to also provide valuable insight during this book’s earlydevelopmental stages. I would also like to thank Ted Hase, Morris Beton, andthe 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 theirenthusiasm, encouragement, and support. Without you this booksimply 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Part 1Introduction to COM
Chapter 1A COM OverviewIN 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 beenreferred to as many things, it is essentially only two: a programming modeland a set of related system services. In this chapter, I describe COM, theprogramming model, and how it relates to COM, the system services. By theend of this chapter, you should have a clear understanding of COMfundamentals and the advantages of adopting COM as the foundation fordeveloping 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 ofCOM 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 achievethis level of interoperability, COM defines a binary standard, which specifieshow an object is laid out in memory at run time. By defining how an object islaid out in memory, COM allows any language that is capable of reproducingthe required memory layout to create a COM object. We look at the memorylayout of a COM object later in this chapter.
A Word About Interoperability
There are many reasons why two or more applications may need tointeroperate, such as to exchange data or to programmatically control oneanother. However, COM doesn’t define the underlying purpose forapplications to communicate. COM exists to provide a single standardizedway for two or more applications to interoperate regardless of the purposefor their interaction. OLE and ActiveX are examples of two industryspecifications that define specific purposes for applications to interact. OLEis a specification that describes how COM objects can be used to create andmanipulate compound documents. ActiveX is a specification that describeshow COM objects can be used on the Internet. In addition, COM has beenused as the foundation for many industry-specific initiatives in such verticalmarket segments as retailing, banking, and insurance. For a complete list ofthe industry-specific initiatives that rely on COM as their foundation, checkMicrosoft’s Web site at www. microsoft.com.
While COM’s primary objective is to provide basic interoperability betweenobject clients and servers at a binary level, COM also has several otherobjectives:
• 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’sfinish our discussion of COM’s basic interoperability.
In COM, programming model, COM clients connect to one or more COMobjects, which are themselves contained in COM servers. Here, a client is anypiece of software that makes use of the services provided by a COM object.Each COM object exposes its services through one or more interfaces, whichare essentially groupings of semantically related functions. The compiledimplementation of each COM object is contained within a binary module(EXE or DLL) called a COM server. A single COM server is capable ofcontaining the compiled implementations of several different COM objects.The COM programming model defines what a COM server must do to exposeCOM objects, what a COM object must do to expose its services, and what aCOM client must do to use a COM object’s services. Part 1 of this bookfocuses on these topics exclusively, and provides the background information
necessary to understand the material covered in Part 2. As a corporateapplications developer, I assume that ultimately you want to know how COMwill help you build better applications. Rather than tell you how, Part 2 of thisbook shows you how. I illustrate how COM can be used to create traditionalclient/server applications, as well as next-generation Web-based applications. Ialso show you how COM’s distributed aspect (DCOM) can be used to enhancethese 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
COM Objects
A COM object, like any other object, is a run-time instantiation of a particulardefining class. However, unlike most other objects, which are identified by ahuman-readable name, COM objects are identified by a unique Class Identifier(CLSID). CLSIDs are part of a special group of identifiers called Globally UniqueIdentifiers, or GUIDs. GUIDs are 128-bit values that are statistically guaranteedto be unique across time and space. The following illustrates the internal structureof 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 betweenmachines with different byte orders.
To understand why it’s imperative that COM use CLSIDs to uniquely identifyobject classes, consider the following scenario. Imagine that you’ve justdeveloped a COM object and identified it using a traditional human-readablename, such as “MyObject.” You then ship your object in binary form to thousandsof anxious developers who quickly install your component. If one of thesedevelopers already has an object named “MyObject” installed on his or hersystem, there is no way to resolve the naming conflict because both objects are inbinary form. Therefore, to prevent this type of naming conflict, COM usesCLSIDs to uniquely identify each individual object class. Instead of having a
central authority that is responsible for issuing GUIDs, COM provides theCoCreateGuid API, which is used by various GUID generation tools, such asGUIDGEN.EXE and UUIDGEN.EXE, which both ship as part of MicrosoftVisual C++. Internally, CoCreateGuid calls the RPC function UuidCreateto 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 verydeveloper-friendly. To make dealing with CLSIDs more developer-friendly andmore like traditional object-based development, you can assign them to traditionalhuman-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 isredistributed — along with the object itself — for consumption by otherdevelopers. However, the header file is not redistributed with any resultantapplications created using the object. The header file is only required by otherdevelopers who want to use the object as part of their development efforts. WhileCoCreateGuid guarantees the uniqueness of each CLSID, it is the developer’sresponsibility to make sure that each human-readable name is unique within thescope of its definition. And like traditional object-based development, any namingconflicts 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, withthree 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, andReverseAll; and ISwapInfo contains SwapName, SwapAge, SwapSex,and SwapAll.
Figure 1-1 The ICopyInfo, IReverseInfo, and ISwapInfo interfaces of theUserInfoHandler 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 uniqueCLSID. Like CLSIDs, IIDs are also GUIDs, which means that they are createdlike any other GUID using the COM API CoCreateGuid or some GUIDgeneration tool such as GUIDGEN.EXE or UUIDGEN.EXE. Again, the IIDinformation is typically included as part of the same header file that includes the
object’s CLSID, which is redistributed — along with the object itself — forconsumption 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 youwere 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 tointeract with a COM object. Instead of obtaining a pointer to an entire COMobject, a COM client must obtain a pointer to a particular interface, which is thenused to access the functions defined as part of that particular interface. The onlyway to access the functions of a particular interface is through a pointer to thatinterface. So if you have a pointer to the ICopyInfo interface, you will only beable to access the CopyName, CopyAge, CopySex, and CopyAll memberfunctions. In order to access SwapName, SwapAge, SwapSex, or SwapAll,you must first obtain a pointer to the ISwapInfo interface. The fact thatinterfaces are the only way to interact with COM objects should help explain whyeach interface must be uniquely identifiable. The process of moving from oneinterface 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
INTERFACE NAVIGATION
To support interface navigation, every interface must implement a special function namedQueryInterface. QueryInterface takes two parameters, one to specify the desiredinterface’s IID, and the other to receive the actual interface pointer. If the COM objectimplements the interface identified by the IID, the QueryInterface call will succeed andreturn a pointer to the interface in the second parameter; otherwise, the QueryInterfacecall will fail, and a NULL value will be returned in the second parameter. The followingsnippet shows how a client would obtain a pointer to the ISwapInfo interface, assumingthat it already had an IReverseInfo interface pointer. We’ll uncover the process of howthe client obtained the original interface in the section on COM’s implementation locatorservice.
HRESULT hr;ISwapInfo *pISwapInfo; //Declare a pointer to an ISwapInfo //interface
//pIReverseInfo is a pointer to the IReverseInfo interfacehr = 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 abilityto 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 toobtain a pointer to it, and QueryInterface will always fail when asked to retrieve thatparticular 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 theonly way to manipulate a COM object, once an interface is exposed for client usage, it mustnever 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 theCopyName and CopyAge functions of the ICopyInfo interface. If the definition of theICopyInfo interface or any of its functions is altered or removed, my application will ceaseworking properly. Therefore, to preserve client compatibility, COM stipulates that aninterface 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 “LifetimeManagement” below). Together, QueryInterface, AddRef, and Release defineCOM’s most fundamental interface, IUnknown. Because each interface must support thesethree fundamental functions, every interface must inherit from IUnknown.
LIFETIME MANAGEMENT
We have already seen how QueryInterface is used for interface navigation; now we willlook 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. Theclient 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. Toprevent one client from destroying a COM object and leaving the others with invalid interfacepointer references, both the client and the COM object share the responsibility of lifetimemanagement. A COM object’s lifetime is managed through a process called referencecounting. 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 theCOM object issues an interface pointer — as a result of a QueryInterface call, forexample — 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 callRelease 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 todestroy 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
OBJECT VERSIONING AND EVOLUTION
While COM strictly prohibits modifying an object’s interfaces, COM doesprovide a simple yet effective strategy for introducing new features in a COMobject without modifying existing interfaces and without breaking existingclient applications. The solution is to simply add new interfaces. For example,suppose that you’ve created a ReferenceMaterials COM object, andthat version 1 has one interface, IDictionary, which has only one function,CheckWord. Clients can call CheckWord with a single argument containingthe spelling of a particular word that they would like to validate. CheckWordreturns TRUE or FALSE depending on whether the word is spelled correctly ornot. However, in version 2, the ReferenceMaterials COM object addsan additional interface, IDictionary2, which has two functions,CheckWord, and GetDefinition. Since the original IDictionaryinterface hasn’t changed, older clients are completely unaware that theReferenceMaterials COM object has changed at all. However, newerclients can obtain a pointer to the new IDictionary2 interface and use the newGetDefinition function (see Figure 1-2). By simply adding newinterfaces, COM objects are able to add new features and functionality whilesimultaneously maintaining backward compatibility with existing clientapplications.
Figure 1-2 By adding a new IDictionary2 interface, version 2 of theReferenceMaterials COM object is able to provide increasedfunctionality to version 2 clients and still provide support for legacy version 1
clients.
While an interface defines the function prototypes for each of its supportedfunctions, the implementation of each function is left totally to the developer’sdiscretion. Each function must be implemented, but the implementationspecifics are fully encapsulated within the COM object. This, plus the fact thatinterfaces are immutable, allows COM objects to change implementationspecifics without breaking existing clients. Consider the following example.Suppose that version 1 of the ReferenceMaterials COM object from theprevious example maintains its list of known words in memory. Based oncustomer feedback, you later decide that the memory requirements are toodemanding, and you therefore decide to maintain the list of known words ondisk in version 2. Even though you’ve changed the implementation of theCheckWord function, you haven’t changed the interface, and existing clientapplications are none the wiser! By separating definition from implementation,COM allows object developers to expose proprietary implementations in anopen and standard way, which is important for protecting intellectual propertyand sensitive corporate information.
COM Servers
The class for each COM object is implemented in a binary code module (DLLor EXE) called a COM server. COM servers implemented as DLLs are loadeddirectly into the client process’s address space, and are commonly referred toas in-process servers. The nature of a Win32 DLL is such that a copy of it ismapped directly into each client application’s own private address space. Thismeans that each client application owns any resources allocated by thein-process server. Since in-process servers don’t own their resources, theycannot maintain global resources that are accessible by multiple clients (seeFigure 1-3).
Figure 1-3 Because DLLs do not maintain their own address space, theirclients 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 toown their resources, in-process servers do have a major advantage … speed.Because the in-process server is already mapped onto the client’s addressspace, there is no need for the operating system to perform a context switch inorder to access the code contained in the DLL. As a result, there is very littleoverhead associated with invoking the interface functions of a COM objectimplemented in an in-process server.
COM servers can also be created as stand-alone EXEs, in which case theymaintain an address space apart from that of the client. COM servers created asEXEs are commonly referred to as out-of-process servers. Since EXEsmaintain their own address space, out-of-process servers are also capable ofowning 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-processservers are also capable of owning their resources, which may be sharedamong their clients.
An out-of-process server running on the same machine as its client(s) isreferred 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 amachine other than its client(s) is referred to as a remote server and is said toserve the client(s) remote objects. In the case where a remote server is anin-process server, COM automatically creates a separate surrogate process andloads the in-process server into its address space (see Figure 1-5).
Figure 1-5 When an in-process server is used remotely, COM automaticallycreates a surrogate process and loads the in-process server into its addressspace.
However, the benefit of resource ownership is not without its drawbacks —one of which, as you may have guessed, is reduced speed. Whenever a clientaccesses code or resources located within the out-of-process server, theoperating system is forced to perform a context switch, and you must pay aperformance penalty. On the other hand, accessing code or resources locatedwithin an in-process server is extremely fast. However, in-process servers areincapable of owning their own resources. Clearly, there are benefits anddrawbacks to both in-process and out-of-process servers. Ultimately, the typeof COM server you create will depend on the overall architecture of yourapplication.
COM: The System Services
In order for an operating system to support the COM programming model, itmust include a set of COM system services, commonly referred to as the COMLibrary. 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 COMservers
• Transparent Local Procedure Calls (LPCs) and Remote ProcedureCalls (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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
COM’s APIs
The COM Application Programming Interfaces (APIs) are used to access theservices offered by the COM Library. The COM APIs are similar to most otherWin32 APIs in the sense that they are ordinary function calls, not methods ofan interface. For easy identification, COM API functions typically begin withthe prefix “Co,” such as CoCreateInstance. When you look at thedefinitions of the COM APIs, you may notice that many of them return astrange HRESULT data type. An HRESULT is not a handle to a result, as itsname might imply. An HRESULT is used to return status informationregarding the success or failure of an operation. By dividing the 32-bitHRESULT value into an internal structure containing four fields, it is possibleto return information regarding not only the success or failure of an operation,but also detailed information regarding the source of the failure and the reasonfor the failure. The internal structure of an HRESULT is described in Table1-1.
Table 1-1 The Internal Structure of an HRESULT
Field Name Bit Positions Description
S 31 Severity field.0: Success. The function completedsuccessfully.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, andtherefore is issued by Microsoft. (See Table2-2 for a description of the currently definedfacility codes.)
Error Code 0–15A number describing the reason the erroroccurred.
Table 1-2 Currently Defined Facility Codes
Facility Name Value Description
FACILITY_NULL 0 Used for broadly applicable error codesFACILITY_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 codesFACILITY_STORAGE 3 Used to report status codes that relate to
persistent storageFACILITY_ITF 4 Used to report an error from an interface
member functionFACILITY_WIN32 7 Used to map an error code from a Win32
API function onto an HRESULTFACILITY_WINDOWS 8 Used to report error codes from
Microsoft-defined interfaces
FACILITY_SSPI 9Used to report error codes that relate tosecurity
FACILITY_CONTROL 10Used to report OLE-Control-related errorcodes
FACILITY_CERT 11 Used to report error codes that relate topublic key certificates and Authenticode
FACILITY_INTERNET 12 Used to report error codes that relate toInternet APIs
FACILITY_MSMQ 14 Used to report error codes that relate toMicrosoft Message Que
FACILITY_SETUPAPI 15 Used to report error codes that relate tothe Win32 setup APIs
Constants defining HRESULT return values typically use the following namingconvention:
<Facility>_<Sev>_<Reason>
where <Facility> is the facility name, <Sev> is either S or E, indicatingsuccess or error, and <Reason> is a short description of the reason the erroroccurred. In cases where the <Facility> value is FACILITY_NULL, thenaming convention is shortened to
<Sev>_<Reason>
as in E_UNEXPECTED or E_NOMEMORY. Table 1-3 shows some of the morecommonly 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 TRUES_FALSE Function completed and the result is FALSENOERROR Function completed with no return valueE_UNEXPECTED An unexpected error has occurredE_INVALIDARG One of the user-supplied arguments is invalidE_OUTOFMEMORY Sufficient memory could not be allocatedE_NOINTERFACE The requested interface is not supported
Because an HRESULT returns not only success or failure, but other detailedinformation as well, COM defines several macros that allow you to probe theinternal structure of an HRESULT value. Table 1-4 describes several of themore commonly used macros.
Table 1-4 HRESULT Macros
Syntax Description
SUCCEEDED(HRESULT status) If the severity field of the HRESULT is0, returns TRUE; otherwise, returnsFALSE
FAILED(HRESULT status) If the severity field of the HRESULT is1, returns TRUE; otherwise, returnsFALSE
HRESULT_CODE(HRESULT hr)Returns the error code field of theHRESULT
HRESULT_FACILITY(HRESULThr)
Returns the facility field of theHRESULT
HRESULT_SEVERITY(HRESULThr)
Returns the severity field of theHRESULT
HRESULT MAKE_HRESULT(SEVERITY sev, FACILITY fac,CODE code)
Creates a new HRESULT given aseverity, a facility, and a status code
User-defined error codes should have a code value between 0x0200 and0xFFFF, as values 0x0000 and 0x01FF are used by the COM-definedFACILITY_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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
COM’s Implementation Locator Service
In order to provide a system view of COM objects, COM maintains a system-widedatabase called the system registry, which is essentially a lookup table mappingCLSIDs to COM server filenames. The system registry is composed of a hierarchyof keys. Each key may have an associated value and may also define othersubordinate keys, or subkeys (see Figure 1-6).
Figure 1-6 In order to provide a system view of COM objects, COM maintains asystem-wide database called the system registry, which is essentially a lookup tablemapping CLSIDs to COM server filenames.
All interactions with the system registry are done through the Win32 APIs listed inTable 1-5.
Table 1-5 Win32 APIs for Manipulating the System Registry
RegCloseKey RegConnectRegistry
RegCreateKey RegCreateKeyExRegDeleteKey RegDeleteValueRegEnumKey RegEnumKeyExRegEnumValue RegFlushKeyRegGetKeySecurity RegLoadKeyRegNotifyChangeKeyValue RegOpenKey
RegOpenKeyEx RegQueryInfoKeyRegQueryMultipleValues RegQueryValueRegQueryValueEx RegReplaceKeyRegRestoreKey RegSaveKeyRegSetKeySecurity RegSetValueRegSetValueEx RegUnLoadKey
Top-level keys in the hierarchy are called root keys. COM-specific information ismaintained under the root key HKEY_CLASSES_ROOT. UnderHKEY_CLASSES_ROOT is another key called “CLSID,” under which each COMserver is responsible for creating its own key composed of a string representation ofthe CLSID enclosed in curly braces, along with an optional string description as theassociated value:
HKEY_CLASSES_ROOT CLSID {12345678-ABCD-1234-5678-9ABCDEF00000} = Description
Under each COM object’s CLSID key, you will find one or more additionalsubkeys that define the types of servers present for serving COM objects with thatCLSID. In-process servers must add the “InprocServer32” key and set its valueequal to the string representation of the DLL server’s pathname. Local servers mustadd the “LocalServer32” key and set its value equal to the string representation ofthe EXE server’s pathname. In order to provide a wide range of flexibility, a COMobject may be available from both in-process and out-of-process servers, in whichcase both the “InprocServer32” and “LocalServer32” keys would be defined underthe 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 multiplecontexts 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 aregistered COM object. The COM API provides the CoCreateInstancefunction, which client applications can use to create an instance of a particularclass. 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 aclient-supplied CLSID
• Launching the COM server
The SCM uses the system registry to locate and launch the appropriate COMserver.
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 anin-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 differencesbetween accessing code in a DLL, accessing code in a separate EXE, andaccessing code on an entirely different machine. Rather than burden the clientapplication developer with the headache of three different programmingmodels depending on where a particular COM object is located, COMprovides a single programming model for accessing COM objects regardless ofwhere they are located. COM’s single programming model provides locationtransparency to the client: the client has no idea where a particular COMobject is actually running. This is not to say that the client has no control overwhere a particular COM object runs, for as I mentioned earlier, by usingCoCreateInstance, a client can specify the context in which a particularCOM object should run.
The secret to COM’s singular programming model lies in the interface. As youalready know, conceptually, an interface is a group of semantically relatedfunctions. Architecturally, an interface is a pointer to a virtual function table,known as a VTBL. This VTBL contains pointers to functions that provide theactual implementation defined by the interface. As Figure 1-7 shows, whenyou obtain an interface pointer, you are actually receiving a pointer to a pointerthat is pointing to a VTBL of function pointers! (Try saying that very fast threetimes!)
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 requiredfor COM to transparently provide you with a single location-independentprogramming model. When a server is in-process, the pointers in the VTBLpoint directly to methods that are also located in the same process space. Sincethe method implementations are in the same process space as the client, verylittle overhead is associated with invoking the method through multiple-pointerindirection. However, pointers are only able to access information within asingle process space. So when a server is out-of-process, client interfacepointers are not allowed to access information in the server’s process space. Tosolve this problem, COM relies on a special piece of in-process software calleda proxy. When you receive an interface pointer to an out-of-process object, youare actually receiving a pointer to a proxy. The proxy exists to take the place ofthe object and to forward any client requests to another special piece ofsoftware called a stub. Since the proxy is in-process, the client’s interfacepointer can access it. To the client, the proxy is the object. The proxy is alsoresponsible for packaging any parameters that are needed to invoke aparticular method, a process known as marshaling.
Like the proxy, the stub is also in-process; however, the stub is located in theserver’s process space. The stub receives requests from the proxy andunmarshals any parameters before actually invoking the method of theinterface. 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 passesit to the client. All of this takes place behind the scenes, and the client andserver are none the wiser.
When the client is accessing a local server, and the proxy and stub are locatedon the same machine, they communicate via Local Procedure Calls (LPCs).An LPC is a form of interprocess communication specifically designed for oneprocess to invoke the methods of a different process. LPCs work fine as longas the proxy and stub are located on the same computer. When the proxy andstub are located on different computers, they communicate via RemoteProcedure Calls (RPCs). Like LPCs, RPCs are also a form of interprocesscommunication; however, RPCs are designed to allow a process on onemachine 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 LPCsand RPCs is diagrammed in Figure 1-8.
Figure 1-8 Clients of local out-of-process objects actually communicate withan in-process proxy, which communicates with a stub loaded into the addressspace of the object. The proxy then communicates with the stub via LocalProcedure Calls (LPCs). Clients of remote objects, either in-process orout-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 inmemory and how this allows COM to maintain language independence.
• The significance of interfaces in general, and the significance of theIUnknown interface specifically, for interface navigation and objectlifetime management.
• How adding additional interfaces allows a COM object to introducenew functionality while simultaneously supporting existing clientapplications.
• The advantages and disadvantages of implementing a COM object inan in-process server as opposed to an out-of-process server.
• How the implementation locator service of the COM Library makesuse of the system registry to provide a system-wide view of registeredCOM 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 locationtransparency.
In the next chapter, you learn the responsibilities required of both the COMclient and the COM server by building an in-process COM server as well as aclient 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Chapter 2Building In-Process ServersIN 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 divein for a closer look. The first target area is in-process COM servers. To betterillustrate 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, ourexample UserInfo in-process server only supports one, the UserInfoCOM object. The UserInfo COM object is used to maintain informationabout an individual person. Each piece of information that is maintained isexposed as a property of the UserInfo COM object (see Table 2-1).
Table 2-1 UserInfo Object Properties
Property Name Data Type
Age shortName LPSTRSex unsigned char
While the UserInfo server is admittedly simple, its purpose is todemonstrate the fundamental elements that every COM server is responsiblefor implementing. The UserInfo server also demonstrates what a typicalin-process server must do to fulfill these responsibilities. Every COM server isresponsible for the following:
• Allocating GUIDs for each supported object, interface, and typelibrary
• Defining the interfaces supported by each object
• Implementing the functions defined by each interface
• Implementing a class factory capable of creating each supportedobject
• 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 havea unique IID. As we discovered in the last chapter, CLSIDs and IIDs are bothGUIDs. You also know that COM supplies the CoCreateGuid API functionto facilitate the creation of GUIDs. However, Microsoft Visual C++ includestwo applications, GUIDGEN.EXE and UUIDGEN.EXE, that both rely onCoCreateGuid internally. These two applications help to further expeditethe creation of GUIDs. GUIDGEN is a Windows-based application thatgenerates GUIDs in a couple of different formats, and allows you to copy themto the Windows clipboard so that you can paste them directly into your code.The UUIDGEN application is a command-line application that allows you tocreate a series of consecutive GUIDs with a single call; it also allows you tosave them to a text file. Creating multiple consecutive GUIDs can be reallyhelpful if you ever need to locate information regarding a specific COM serverin the registry. While you could use GUIDGEN several times to generateenough GUIDs for the UserInfo server, I used UUIDGEN to generate threeconsecutive GUIDs and have them written out to an ASCII text file namedguids.txt:
C:>uuidgen -n3 -oguids.txt
acceeb00-86c7-11d0-94ab-0080c74c7e95acceeb01-86c7-11d0-94ab-0080c74c7e95acceeb02-86c7-11d0-94ab-0080c74c7e95
The newly allocated GUIDs will be used for the CLSID in the definition of theUserInfo COM object and also as an IID for UserInfo’s IUserInfointerface.
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Defining Each Object’s Interfaces
Objects and their interfaces are defined using the Interface Definition Language (IDL). While acomplete IDL reference is beyond the scope of this book, you can find it on Microsoft’s Web siteat www.microsoft.com. IDL, with its C-style syntax, is a simple and easy-to-use language fordefining a number of COM elements such as objects, interfaces, and type libraries. A type libraryis essentially a language-neutral description of COM elements. Type libraries are typically usedduring automation to perform parameter type checking. Automation is the process of manipulatingan 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 todefine several different types of elements, the syntax to describe objects and interfaces isessentially the same: [attributes] elementname typename {memberdescriptions};
The attributes section is used to define the element’s characteristics. Elementname is a keywordthat indicates the type of element being defined (coclass, interface, library, etc.). The typenameassigns a name to the element. The memberdescriptions section contains definitions for one ormore additional elements contained within the element being defined. Listing 2-1 shows the IDLdefinition 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 interfaceinterface 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 theIUserInfo interface. The object attribute is used to indicate that the interface is a customCOM interface. The uuid attribute is used to assign a GUID to the interface. The helpstringattribute is used to provide helpful information about the IUserInfo interface. Inside theinterface definition are definitions of each of the functions supported by the interface. SinceIUserInfo inherits from IUnknown, we don’t need to define the IUnknown functions in thememberdescriptions of IUserInfo, but we do have to include IUnknown’s definition. Toinclude the definition of an existing element, IDL provides the import statement. By importingunknwn.idl in the first line of IDL code in Listing 2-1, we have included the definition of theIUnknown interface.
As you look at the IUserInfo function definitions, notice that there are two functions for eachproperty: 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, thepropput function would have been missing. Likewise, had any of the UserInfo propertiesbeen write-only, the propget function would have been missing. Because access to some COMobjects may require marshaling, you must provide as much information as possible about theparameters required by each interface function. To help provide this information, IDL defines thein and out attributes, which are used to describe the purpose of each individual functionparameter. The in attribute is used to signal parameters responsible for bringing information intoa particular function, while the out attribute is used to signal parameters responsible for returninginformation to the caller. These attributes can be combined to indicate a parameter that isresponsible for transferring information into and out of a function.
Each interface function is required to return an HRESULT so that the client can receive statusinformation regarding the success or failure of an operation. Therefore, to facilitate traditionalfunction-specific return values, a function must declare a pointer to memory that will receive thereturn value, and have the function return information via the pointer. This pointer must includeboth 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
While these attributes identify the purpose of a parameter, each parameter still needs adata type. Like all languages, IDL has a set of intrinsic data types it supports, which canbe seen in Table 2-2. In addition, IDL provides the typedef, enum, union, andstruct 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 valuechar 8-bit, signed data itemdouble 64-bit IEEE floating-point numberint System-dependent signed integerfloat 32-bit IEEE floating-point numberlong 32-bit signed integershort 16-bit signed integerwchar_t Unicode character accepted only for 32-bit type librariesBSTR Length-prefixed stringCURRENCY 8-byte, fixed-point numberDATE 64-bit, floating-point fractional number of days since
December 30, 1899DECIMAL 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 containthe additional error information provided by anHRESULT.
VARIANT One of the variant data types described in Chapter 5IDispatch* Pointer to an IDispatch interface
IUnknown* Pointer to an IUnknown interfaceSAFEARRAY(TypeName) TypeName is any of the above types. An array of these
typesTypeName* TypeName is any of the above types. A pointer to a typevoid Allowed only as a function return type or in a parameter
list to indicate no argumentsHRESULT Return type used for reporting error information in
interfaces as described in Chapter 1LPWSTR Unicode string accepted only for 32-bit type librariesLPSTR Zero-terminated string
Once the IUserInfo interface is defined, we can use it in the definition of theUserInfo object. The UserInfo object is defined as an element of the UserInfolibrary element. The library element represents the type library as a whole, and isidentified 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 librarylibrary 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 thedefault attribute to signal macro languages that IUserInfo is the interface to usefor programmatic control. Had the UserInfo COM object supported additionalinterfaces, 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, whichultimately defines the object itself. Once we have defined the type library and all of theobjects and interfaces, we can compile the file using the Microsoft Interface Definition
Language (MIDL) compiler. The MIDL compiler takes UserInfo.idl and generates fivefiles: 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 alanguage-independent header file. We’ll get to UserInfo_i.c and UserInfo_i.h in just aminute. The UserInfo_p.c and dlldata.c files contain code that can be used to generate aproxy/stub pair for marshaling and unmarshaling UserInfo function calls. Sincein-process servers are located in the process space of their client and thus don’t requiremarshaling, 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 referto 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The UserInfo_i.h file contains the C and C++ definitions for the IUserInfo interface; wewill, however, focus our attention on the C++ definition. The IUserInfo interface isdeclared as a C++ abstract class, which means that at least one of its member functions is apure virtual function. Typically, whenever a C++ object is used to define an interface, all ofthe interface functions will be represented as pure virtual functions. By declaring eachinterface function as a pure virtual function, we force each object supporting the interface toprovide an implementation for each interface function. This is important, as we don’t wantclients 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 functionaldefinition from implementation. For example, one developer implementing IUserInfo maychoose to store the values of each property in a database, while another developerimplementing IUserInfo may choose to store the values of each property in a table inmemory. A developer creating an IUserInfo client is shielded from these implementationspecifics and only knows that calling a particular function with the appropriate parameterswill result in a predictable outcome. By shielding clients from implementation specifics, asingle application can be developed to work with any number of COM objects in aplug-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 forthem. Implementation is provided in the C++ classes that subsequently inherit from theabstract base class. In our case, we will create the CUserInfo C++ class, which will inheritfrom the IUserInfo abstract base class. We will then provide the implementation ofIUserInfo via the CUserInfo C++ class. Had the UserInfo COM object supportedmultiple interfaces, we would’ve just defined CUserInfo such that it multiply inheritedfrom the abstract base class of each supported interface, supplying the implementations ofeach interface as part of the same CUserInfo C++ class. You will see an example ofmultiple-interface inheritance in the next chapter. In the class declaration for CUserInfothat follows, notice that the first three functions are QueryInterface, AddRef, andRelease, 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 everyinterface 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 callsQueryInterface with the IID of a supported interface, the object should respond byreturning a pointer to that particular interface. In the case of the UserInfo COM object, if aclient calls QueryInterface with the IID for either IUnknown or IUserInfo,UserInfo should return a pointer to that interface. In theCUserInfo::QueryInterface function listed below, notice how the CUserInfoobject casts itself into either a IUserInfo pointer or an IUnknown pointer, depending onthe requested IID. If the requested interface is supported, QueryInterface calls AddRefto increase the reference count in accordance with COM’s reference counting rules. If all goeswell, QueryInterface reports NOERROR to the client. If a client requests an unsupportedinterface, which in this case would be any interface other than IUnknown or IUserInfo,QueryInterface returns E_NOINTERFACE to notify the client that the requestedinterface is not supported. NOERROR and E_NOINTERFACE may look familiar, as they wereintroduced in the last chapter in Table 1-3: Commonly Used HRESULT Return ValueConstants.
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 thenumber of outstanding references to an object is decreased, Release is calledto decrease the object’s internal reference counter. When there are nooutstanding references, the object deletes itself. Once the object has deleteditself, the global object counter g_cObjects is decremented to reflect the factthat there is now one less object in existence. After all of this, theServerCanUnloadNow function is consulted to determine if it is all right forthe entire COM server to unload itself from memory, a feat that would beaccomplished by the UnloadModule function. But, because DLLs aren’tresponsible for unloading themselves, the UnloadModule function simplyreturns. However, if the UserInfo COM object were being implemented in anEXE server, UnloadModule would actually unload the EXE server frommemory. (You learn more about EXE servers in the next chapter.) Suffice it tosay that I have provided the UnloadModule function as a way to shield theobject from the differences between unloading a DLL server and unloading anEXE server, should you decide on your own to implement the UserInfo COMobject in an EXE server. (See the sidebar “Encapsulating Server PackagingSpecifics.”)
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 notvery COM-specific. However, the full implementation of the CUserInfoobject is provided in Listing 2-8 if you are so inclined. Now let’s turn ourattention to creating the class factory that is ultimately responsible for creatingindividual UserInfo COM objects.
Implementing a Class Factory
So far, we have seen how to define and implement the UserInfo COM, butwe don’t have a way to actually instantiate an instance of the object. Sure, wecould just instantiate a single instance of the object when the DLL server is firstloaded, but what if the client needs to create more than just a single instance ofthe UserInfo object? In order to provide the client with a mechanism forcontrolling the object instantiation process, COM employs the concept of a classfactory, and has defined the IClassFactory interface. A class factory is anobject that implements the IClassFactory interface and is ultimatelyresponsible for creating other COM objects. The IClassFactory interfacehas only two methods, LockServer and CreateInstance. We will look atthe LockServer function in the section “Server Unloading.” TheCreateInstance function is called whenever a client wants to instantiate aninstance 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. TheCreateInstance method of the CUserInfoFactory object can be seenin Listing 2-3. Notice how once a CUserInfo object is created, CUserInfoClassFactory calls the newly created CUserInfo object’sQueryInterface function. This allows clients using CreateInstance tocreate an instance of a COM object and receive a pointer to a specific interfaceall in one call. Since an object has an initial reference count of zero uponinstantiation, the call to QueryInterface also serves to increment theobject’s reference count to one. If all goes well, CreateInstance alsoincrements a global object counter, which is used to keep track of the totalnumber of objects being served. Since in-process servers cannot maintain theirown global memory, the global object counter is really the total number ofobjects 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Registering Class Information
In order for COM’s implementation locator services to locate, load, and launch yourserver, you must add each COM object’s class information to the system registry, whichis typically done once, as part of the installation process. Therefore, every server mustprovide a mechanism through which it can be notified to either register or unregister classinformation. COM requires that in-process servers provide this capability through theDllRegisterServer and DllUnregisterServer functions, which are definedin <olectl.h>. Applications like REGSVR32.EXE that are used to register and unregisterin-process COM servers simply call the Win32 API function LoadLibrary to load aparticular server, and then call the Win32 API function GetProcAddress to obtain apointer to either the DllRegisterServer or DllUnregisterServer function.
When a UserInfo client invokes the DllRegisterServer function, the servermust add the UserInfo CLSID as a subkey under theHKEY_CLASSES_ROOT\CLSID key and optionally provide a textual description of theUserInfo COM object. The server must also add the “InprocServer32” subkey andprovide the path of the UserInfo.dll server. The following is a representation of theinformation 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 thesystem registry (see Listing 2-4). SetRegKeyValue uses the Win32 API functionsRegCreateKeyEx, RegSetValueEx, and RegCloseKey to actually add or updateinformation 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 usesSetRegKeyValue 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 removeits information from the system registry. The following code snippet shows howDllUnregisterServer uses the Win32 API function RegDeleteKey to removeinformation 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 callto the COM API function CoGetClassObject to retrieve a pointer to theIClassFactory interface responsible for creating objects of the desired CLSID,which is supplied as the first parameter to CoCreateInstance. To obtain theappropriate IClassFactory pointer from an in-process server, COM checks theregistry to retrieve the pathname of the appropriate server. After obtaining the server’spathname, COM calls the COM API function CoLoadLibrary to load the server intomemory. Once the server is loaded into memory, COM calls the Win32 API functionGetProcAddress to request the address of the DllGetClassObject function.COM requires that every in-process server implement and expose aDllGetClassObject function. Based on a CLSID that is passed to it,DllGetClassObject is responsible for creating the appropriate class factory. Afterobtaining the appropriate IClassFactory interface pointer, the COM system servicescall 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 bythe CLSID parameter can be created by the server; if it can’t, DllGetClassObjectreturns CLASS_E_CLASSNOTAVAILABLE. However, if the server is capable ofcreating the object, DllGetClassObject creates an instance of the class factoryresponsible 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 servesto 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Server Unloading
Typically, when the last reference on the last object being served by a server is released, theserver is unloaded. However, if the server is responsible for creating objects that typicallyhave a short life span, it may be desirable to keep the server loaded in memory even when itisn’t serving any objects, thus eliminating the overhead associated with loading andunloading the server. To accommodate this functionality, the IClassFactory interfacesupports the LockServer function. LockServer takes a single boolean parameter thatdetermines whether or not the server should be “locked” in memory. If the value is true, theglobal reference counter is incremented. If the value is false, the global reference counter isdecremented. 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 ifit can be unloaded by calling the DllCanUnloadNow function. This means that everyin-process server is responsible for implementing and exposing the DllCanUnloadNowfunction. This is a relatively simple function that returns S-OK if there are no existinginstances 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 anin-process server and an out-of-process server. I have tried to encapsulate theimplementation differences between in-process servers and out-of-process servers as muchas possible in order to shield the individual COM objects from these server packagingspecifics. As you will see in Chapter 3, the biggest differences between in-process andout-of-process servers are in the areas of registering class information and serverunloading. Both of these differences occur as a result of the in-process server’s ability todirectly export functions for client consumption. Since out-of-process servers maintainan address space apart from the client, they do not have the ability to simply exportfunctions for use by other processes. However, all COM servers are responsible forproviding mechanisms for registering class information and server unloading.
As you know, DLL servers are responsible for exporting the two functionsDLLRegisterServer and DLLUnregisterServer for the explicit purpose ofregistering and unregistering class information. (Source code for the UserInfo object’simplementation of DLLRegisterServer and DLLUnregisterServer can be seenin Listing 2.6.) EXE servers, on the other hand, register their class information in responseto the /REGSERVER or -REGSERVER command-line arguments and unregister their classinformation in response to the /UNREGSERVER or -UNREGSERVER command-linearguments. In the spirit of the DLLRegisterServer and DLLUnregisterServerfunctions used by DLL servers to manage registration information, I created theRegisterServer and UnregisterServer functions, which are used by the variousEXE servers created throughout this book to manage their registration information. As partof their WinMain entry point, each EXE server searches for the defined registrationcommand-line arguments, and calls either RegisterServer or UnregisterServeras appropriate (see Listing 3-4). (Implementations of the RegisterServer andUnregisterServer functions can be seen in Listing 3-5.)
When it comes to server unloading, DLL servers again have the luxury of simply exportingthe DllCanUnloadNow function, which is called automatically by the COM systemservices to determine whether or not a particular COM server can be unloaded frommemory. EXE servers, on the other hand, are responsible for unloading themselves. In bothcases, a server is only unloaded when there are no outstanding objects and no class factorylocks. (Class factory locks are explained in Chapter 3.) In both cases, I have created theServerCanUnloadNow function, which is used to determine when it is appropriate tounload 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 lastoutstanding interface reference count of a supported COM object is released, and alsowhenever 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 beunloaded 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 isreleased, or an object class factory lock is released, the implementation callsServerCanUnloadNow to determine whether there are any outstanding objects or classfactory locks. If there aren’t, the implementation calls UnloadServer to unload theserver 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 EXEversion of UnloadServer unloads the server by posting the WM_QUIT message to theserver’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 tophysically encapsulate the DLL-specific code in a file called DLLMain.cpp. Similarly, forEXE servers, the EXE-specific code is physically encapsulated in a file calledEXEMain.cpp. By encapsulating the packaging-specific code, the actual objectimplementation files can be included as part of a totally separate project, perhaps toimplement 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 DllCanUnloadNowfunctions from the DLL. The module definition file for the UserInfo server looks likethe following:
LIBRARY UserInfoDESCRIPTION "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; theIUserInfo interface; and the UserInfo COM object. UserInfo.idl is compiledusing 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 areused. UserInfo_i.c contains the declarations of the CLSID, IID, and LIBID, and theUserInfo_i.h file contains C/C++ definitions for the IUserInfo interface.UserInfo_p.c and dlldata.c are automatically generated by the MIDL compiler incase you need to create a proxy/stub pair. Proxy/stub pairs are covered in more detailin Chapter 3. However, because in-process servers are loaded directly into theirclients’ address space and thus don’t require proxies or stubs, we don’t need theUserInfo-p.c and dlldata.c files.
• UserInfo.h contains the definition of the CUserInfo C++ object that inheritsfrom the IUserInfo interface. The UserInfo.h file also contains the definition ofthe CUserInfoFactory class factory that is responsible for creating theUserInfo COM object.
• UserInfo.cpp contains the implementation for both the CUserInfo andCUserInfoFactory C++ objects.
• DllMain.cpp contains functions specific to implementing and registeringin-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 inListing 2-8. As you look at the source code, notice how the DLL-specific code has beenconfined to just the DllMain.cpp source file, making it easier to implement UserInfo asan 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Before you can use the UserInfo server, you must register it. To register the UserInfoserver, use REGSVR32.EXE, which should be in either your Windows\System directory oryour Windows\System32 directory, depending on whether you’re running Win95 or WindowsNT. REGSVR32.EXE works in the manner described in the section “Registering ClassInformation.” The following shows how to use REGSVR32.EXE to register the UserInfoserver:
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. TheUserInfoClient application will create and use the UserInfo COM object that we’ve justdeveloped. Once the UserInfo object is created, we will use QueryInterface to change tothe IUserInfo interface, which we will then use to set each of the UserInfo object’sproperties. After setting each property, we will retrieve, format, and display their values via theWin32 API function MessageBox, which simply creates and displays a small windowcontaining a message. Before we build the UserInfoClient application, note theresponsibilities 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 COMAPI function CoInitialize. The CoInitialize function is provided by COM toinitialize the COM Library, and it must be called before any other COM Library calls, except theCoGetMalloc function and memory allocation calls. Usage of CoInitialize is prettystraightforward.
//Initialize the COM Libraryhr = CoInitialize(NULL);if ( SUCCEEDED(hr) ){ ...}
Obtaining an Initial Interface
Once the COM Library is initialized, we have to obtain an initial interface pointer to theUserInfo object. You can obtain an initial interface pointer in several different ways, the mostpopular of which is to call CoCreateInstance. The UserInfoClient application callsCoCreateInstance with the CLSID of the UserInfo COM object and requests a pointerto its IUnknown interface. However, in order to resolve both the CLSID_UserInfo constantand the definition of the IUserInfo interface, the UserInfoClient application mustinclude 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 thatwe 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 definesthe additional class execution contexts in Table 2-3:
Table 2-3 Defined Class Execution Contexts (CLSCTX)
Execution Context Meaning
CLSCTX_INPROC_SERVER In-Process serverCLSCTX_INPROC_HANDLER In-Process proxyCLSCTX_LOCAL_SERVER Local serverCLSCTX_REMOTE_SERVER Remote serverCLSM_SERVER CLSCTX_INPROC_SERVER,
CLSCTX_LOCAL_SERVER, orCLSCTX_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 beginto 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 allit takes to obtain a pointer to the IUserInfo interface. Once we have obtained an IUserInfointerface pointer, we can set any of the available properties. The UserInfoClientapplication sets the Age, Name, and Sex properties. UserInfoClient then retrieves eachproperty value back, formats their values, and displays them.
Releasing the COM Object
When the UserInfoClient application has finished manipulating the UserInfo object, itcalls the Release function on all of its outstanding interface pointers, which includes oneIUnknown pointer and one IUserInfo pointer. The UserInfo object then destroys itself:
//Release the IUserInfo interfacepIUserInfo->Release();//Release the IUnknown interfacepIUnknown->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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Uninitializing the COM Library
After releasing the UserInfo COM object, the UserInfoClient prepares to terminateitself by uninitializing the COM Library with a call to CoUninitialize. Every successfulcall to CoInitialize must be matched with a call to CoUninitialize. OnceCoUninitialize has been called, no other COM APIs should be called, with the exceptionof the CoGetMalloc function and the memory allocation calls.
//Shut down the COM LibraryCoUninitialize();
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 exemplifiesthe fundamental steps required by all COM clients.
Summary
We covered a lot of ground in this chapter, primarily the responsibilities of the COM server andthe 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 differsfrom 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Chapter 3Building Out-of-Process ServersIN THIS CHAPTER
• The similarities and differences between in-process and out-of-processCOM 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 buildingthe UserInfo in-process server and the UserInfoClientapplication. In this chapter you will further your understanding of COMclients and servers by building the UserInfoHandler out-of-processserver.
AS YOU NOW know, an out-of-process server is a COM server implementedas an EXE. By being packaged within an EXE, a COM object actually exists inits own process space apart from its client. Maintaining a separate processspace can have significant advantages:
• Process isolation. This is the ability to isolate the effects of oneprocess from another related process. Consider a case in which a COMclient relies on a COM object that’s implemented as part of anin-process server. If the COM object crashes for some unknown reason,the entire client application will also crash, because the in-process serverand the client share the same address space. However, if the COMobject in question were created as part of an out-of-process server, thenthe client application would be afforded process isolation, and thuswould be shielded from the effects of the crashing COM object.
• Better security. On secure systems such as Windows NT, theresources of each process can be guarded against unauthorized usage byrogue processes. The same level of security is not possible with anin-process server, because the client and the server share the sameprocess space.
• The ability to maintain shared global resources. Since EXEsmaintain ownership of their own resources, an out-of-process server iseasily capable of allocating global resources and sharing them among itsvarious clients. The same is not true for DLLs, because an independentcopy 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 thateach method invocation requires marshaling, which is definitely slower thanin-process execution. Separate address spaces also means that out-of-processservers must use different techniques to provide functionality that wouldotherwise be exported by in-process servers. Learning the functional andarchitectural differences between in-process servers and out-of-process serversis the focus of this chapter.
The UserInfoHandler Server
The UserInfoHandler server is an out-of-process server that supportsboth the UserInfo object from the last chapter and theUserInfoHandler object. The UserInfo object is implemented usingthe same code introduced in Chapter 2, except for the DLL-specific sectionscontained in DllMain.cpp. The UserInfo COM object maintains informationabout an individual user and exposes this information through the propertiesgiven in Table 3-1.
Table 3-1 UserInfo Object Properties
Property Name Data Type
Age shortName LPSTRSex unsigned char
The UserInfoHandler object is used to perform operations on UserInfoobjects. UserInfoHandler has three interfaces: ICopyInfo,IReverseInfo, and ISwapInfo. The ICopyInfo interface is used tocopy information from one UserInfo object to another. TheIReverseInfo interface is used to reverse a UserInfo object’sinformation. The ISwapInfo interface is used to exchange informationbetween two UserInfo objects. Each interface has four functions, one foreach UserInfo property and one that affects all of the properties. TheUserInfoHandler 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)
ISwapInfoSwapName(IUserInfo *pIUserInfo, IUserInfo*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 supportedobject
• 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 GUIDsfor the UserInfoHandler CLSID, the type library, and each of the threesupported interfaces. If you are using GUIDGEN, this will require severalsteps. However, UUIDGEN can be used to accomplish this in one single step:
C:>uuidgen -n5 -oguids.txt
b04faa80-8bef-11d0-94ab-00a024a85a21b04faa81-8bef-11d0-94ab-00a024a85a21b04faa82-8bef-11d0-94ab-00a024a85a21b04faa83-8bef-11d0-94ab-00a024a85a21b04faa84-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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 andUserInfoHandler COM objects. Notice that the ICopyInfo, IReverseInfo, andISwapInfo 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 interfaceinterface 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 interfaceinterface 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 interfaceinterface 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 interfaceinterface 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 librarylibrary 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The UserInfoHandler.idl file is compiled using the MIDL compiler to generate fiveadditional files: UserInfoHandler.tlb, UserInfoHandler_i.c, UserInfoHandler_i.h,UserInfoHandler_p.c, and dlldata.c. You should be familiar with the UserInfoHandler_i.cand UserInfoHandler_i.h files, as they are similar to the UserInfo_i.c and UserInfo_i.h filesused in Chapter 2 to create the UserInfo in-process server. The UserInfoHandler_i.c filecontains 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 theUserInfoHandler_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 lastchapter. 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, whichrepresents the UserInfoHandler COM object. Notice that even though each interfacedefines the required IUnknown functions QueryInterface, AddRef, and Release asits first three functions, there is only one implementation of IUnknown for the entireCUserInfoHandler 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 memberfunction. Because the UserInfoHandler object supports four interfaces, itsQueryInterface implementation must be capable of returning each one. The following
listing is of CUserInfoHandler::QueryInterface. Notice that because theCUserInfoHandler C++ class doesn’t directly inherit from IUnknown, it must cast itselfinto an ICopyInfo interface pointer before casting to an IUnknown interface pointer. AC++ class can be cast to any interface that it inherits from, regardless of whether theinheritance 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 theIUserInfo interface to manipulate the information maintained by the UserInfo object.The following code snippet shows how the CopyAge function uses the IUserInfointerface 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Implementing a Class Factory
The UserInfoHandler server has two class factories: one for creating UserInfoobjects and one for creating UserInfoHandler objects. The UserInfo class factory islifted directly from the code in Chapter 2, and the implementation of theUserInfoHandler class factory is very similar. Implementation of theUserInfoHandler class factory’s CreateInstance method can be seen in Listing3-3.
Listing 3-3. Implementation of the UserInfoHandler class factory’sCreateInstance 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 beenidentical to developing the UserInfo in-process server presented in the last chapter. Butfrom this point on, you will notice that things will be slightly different. Becauseout-of-process servers are in a process space apart from their clients, they cannot exposefunctions and have clients access them with the Win32 API function GetProcAddress.Therefore, out-of-process servers cannot expose DllRegister or DllUnregister toupdate registry information. To signal an out-of-process server to update information in theregistry, simply run the server and specify either “/RegServer” or “-RegServer” on thecommand 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 twoarguments. Because an out-of-process server is an executable, it is possible for a user toexecute it directly from the command line without using a client. To determine the contextin 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 showshow the UserInfoHandler object searches the command line to gather and process anycommand-line arguments as part of the WinMain entry-point function, which is called bythe operating system to actually start the application. Out-of-process servers also writeslightly 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 thatUserInfoHandler server must devise a different mechanism for exposing classfactories. The COM Library provides the CoRegisterClassObject andCoRevokeClassObject API functions for exposing object class factories. When anout-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 witha 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, imaginethat you’re a client of the UserInfoHandler server, and you callCoCreateInstance to instantiate a UserInfoHandler object. COM responds bycalling CoGetClassObject to actually load the UserInfoHandler server. OnceCoGetClassObject has loaded the server, COM then begins searching through asystem-wide table of registered class factories, based on the CLSID parameter specified inthe call to CoCreateInstance. If COM finds the entry, life is good! However, if COMdoesn’t find the object’s class factory entry in the system-wide table of registered classfactories, the call to CoGetClassObject will be suspended. But COM won’t waitforever, and if the UserInfoHandler server doesn’t register its class factory within acertain amount of time, the call to CoGetClassObject will time-out with theCO_E_APPDIDNTREG error code. Thus, for an out-of-process server, it is important toregister the object’s class factories as soon after application start-up as possible in order toprevent calls to CoGetClassObject and ultimately CoCreateInstance fromtiming 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
If the call to CoRegisterClassObject is successful, the server’s supported class factorieswill be exposed for client consumption. Every successful call toCoRegisterClassObject must be paired with a call to CoRevokeClassObject aspart of the server’s shutdown procedure in order to remove registration information exposed byCoRegisterClassObject. Listing 3-4 shows UserInfoHandler’s WinMainentry-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 objectand one for the UserInfoHandler object. Also notice the calls to CoInitialize andCoUninitialize. 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 successfulcall to CoInitialize must be matched with a call to CoUninitialize, so at applicationshutdown, every out-of-process server is responsible for calling CoUninitialize. OnceCoUninitialize has been called, no other COM APIs should be called, with the exceptionof 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 unloadthemselves by posting a WM_QUIT message to their primary thread’s message queue using theWin32 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 communicateacross process boundaries. Whenever a client invokes an interface function of anout-of-process server, the proxy — which runs in the client’s address space — packages therequired parameters and communicates them to the corresponding stub running in theserver’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 thenunpackages the return values and forwards them to the actual client. When a proxy and stubare located on the same physical machine, they communicate using Local Procedure Calls(LPCs). However, when they are located on separate machines, proxies and stubscommunicate using Remote Procedure Calls (RPCs). The entire process, including thedetermination of when to use LPCs as opposed to RPCs, is totally transparent to thedeveloper. 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 oneproxy/stub pair. To build a proxy/stub pair for the UserInfoHandler server, we mustfirst 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 youmay recall, all of these files were generated by the MIDL compiler when it processed ourUserInfoHandler.idl file. You are already familiar with UserInfoHandler_i.c andUserInfoHandler_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 responsiblefor creating the DLL itself. The code responsible for creating the DLL is contained indlldata.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 thecompiler to automatically include default implementations for DllGetClassObject,DllCanUnloadNow, GetProxyDllInfo, DllRegisterServer, andDllUnregisterServer. This additional code is necessary in order for the proxy/stubpair to be able to register itself.
As you can probably imagine, proxy/stub pairs require slightly different registry settingsthan COM servers. Under the registry key HKEY_CLASSES_ROOT\Interface, theproxy/stub will add the IID of each supported interface as a subkey. Under each of thesesubkeys, the proxy/stub will add the ProxyStubClsid32 subkey, setting its value equal to theCLSID for the proxy/stub. This same CLSID is then registered under theHKEY_CLASSES_ROOT\CLSID key, and it has the InprocServer32 subkey, with a valueequal 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 UserInfoHandlerProxyDESCRIPTION "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 writeeight lines of code in a .def file!
The UserInfoHandlerProxy.dll is registered just like any other DLL, usingREGSVR32.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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Whenever a client invokes a function on the IUserInfo, ICopyInfo, IReverseInfo, orISwapInfo interfaces, your new proxy/stub pair will be invoked automatically toappropriately marshal and unmarshal any required data. Listings 3-5, 3-6, and 3-7 contain thesource code for EXEMain.cpp, UserInfoHandler.h, and UserInfoHandler.cpp. As you lookat the source code, notice how the EXE-specific code has been confined to just theEXEMain.cpp source file, making it easier to implement the UserInfoHandler COMobject 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The UserInfoHandlerClient application is, as its name suggests, asample UserInfoHandler client. The UserInfoHandlerClient putsthe UserInfoHandler server through its paces by using several functionsfrom each of the supported interfaces. I have included the source code for theUserInfoHandlerClient application on the CD-ROM for your viewingpleasure, so enjoy!
Summary
In this chapter, we learned:
• That because out-of-process servers don’t share the same addressspace as their clients, they must rely on proxy/stub pairs to marshal andunmarshal data.
• How to use the MIDL-generated files to create proxy/stub pairs.
• The different techniques that out-of-process servers must use toexpose COM’s required registration and class factory information, asthey 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-processserver.
In the next chapter you learn about containment and aggregation, COM’sobject-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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Chapter 4Reusing COM ObjectsIN 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 thefunctionality that you desire; when that happens, you must develop new components thatfully implement the desired level of functionality. However, COM provides two techniquesfor reusing and extending the functionality of existing COM objects. These twomechanisms, containment and aggregation, are the focus of this chapter.
Understanding Containment
Containment is the simplest form of COM object reuse. In containment, the new COMobject, 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 ownset of interfaces. However, instead of being solely responsible for providing theimplementation for each of its interface functions, the outer object relies on thefunctionality 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 theUserInfo object that we developed in Chapter 2. While the UserInfo object providessuch basic information as the user’s name and age, the MemberInfo object providesadditional information such as the user’s mailing address and telephone number.
The MemberInfo object begins with the usual routine of allocating GUIDs and defininginterfaces. The MemberInfo object has only one interface, IMemberInfo, whichdefines the properties of the MemberInfo object (see Table 4-1).
Table 4-1 Memberinfo Object Properties
Property Name Data Type Implemented In
Name LPSTR IUserInfoSex unsigned char IUserInfoAddress LPSTR IMemberInfoCity LPSTR IMemberInfoState LPSTR IMemberInfoZip LPSTR IMemberInfoPhone LPSTR IMemberInfo
The MemberInfo object and the IMemberInfo interface are then defined in theMemberInfo.idl file using IDL (see Listing 4-1). The MemberInfo.idl file is thencompiled using MIDL to generate a type library and the support files necessary to create anIMemberInfo 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 interfaceinterface 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 librarylibrary 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The next step is to define the CMemberInfo C++ class and provideimplementations for the IMemberInfo interface. The containment process beginswith the creation of the inner object, which in this case is the UserInfo object. TheCOM API CoCreateInstance is used to create the inner UserInfo object aspart of the CMemberInfo::Initialize function. Notice that theCMemberInfo member variable m_pIUserInfo is used to maintain thereference to the IUserInfo interface returned by the call toCoCreateInstance:
HRESULT CMemberInfo::Initialize(void){ return CoCreateInstance(CLSID_UserInfo, NULL, CLSCTX_INPROC_SERVER, IID_IUserInfo, (LPVOID *)&m_pIUserInfo);}//Initialize
CMemberInfo::Initialize is called withinCMemberInfoFactory::CreateInstance, the class factory functionresponsible 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 inthe m_pIUserInfo member variable to manipulate the inner UserInfo objectduring the MemberInfo object’s implementation of both the Name and Sexproperties:
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 usingthe 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 clientperforms the required calls to CoInitialize and CoUninitialize, relievingthe MemberInfo object of that responsibility. Definitions of the CMemberInfoand CMemberInfoFactory C++ classes can be seen in MemberInfo.h, which isprovided 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 innerobject provide some, but not all, of the functionality you desire, there are instances whenan object’s interface provides the exact level of functionality that you desire. So ratherthan duplicate and delegate each interface function from the outer object to the innerobject, 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Understanding Aggregation
In aggregation, interfaces of the inner object are exposed directly, as if theywere implemented on the outer object itself (see Figure 4-2).
Figure 4-2 In aggregation, the outer object exposes the interfaces of the innerobject as its own.
Unlike containment, in which the inner object has no idea that it is being usedas part of another object, in aggregation, the inner object is not only aware thatit is being used as part of an aggregate, it must be developed specifically tosupport aggregation. To understand why a COM object must be specificallydeveloped to support aggregation, imagine that you have a pointer to interfacex, which is implemented by the inner object, and you call QueryInterfacerequesting interface y, which is implemented by the outer object. Since theinner object has no knowledge of interface y, it has no recourse but to fail theQueryInterface call with an E_NOINTERFACE error code. However,from the perspective of the outer object’s client, this clearly violates COM’srules of interface navigation, which state that a client must be able to get to anyinterface defined by an object from any other interface defined on that sameobject. As a client of the outer object, you should be able toQueryInterface on interface x and receive interface y, as well asQueryInterface on interface y and receive interface x. Solving thisproblem requires cooperation from both the inner and outer objects. When theouter object creates the inner object using CoCreateInstance, the twoobjects exchange IUnknown interfaces. The outer object supplies a pointer toits IUnknown interface to the inner object as the second parameter to
CoCreateInstance, while the inner object returns a pointer to itsIUnknown interface to the outer object as the final parameter toCoCreateInstance. If a client has a reference to an interface of the outerobject and performs a QueryInterface call for an interface supported bythe inner object, the outer object simply delegates the QueryInterface callto the inner object using its reference to the inner object’s IUnknowninterface. Likewise, if a client has a reference to an interface of the innerobject, and performs a QueryInterface call for an interface supported bythe outer object, the inner object simply delegates the QueryInterface callto the outer object using its reference to the outer object’s IUnknowninterface (see Figure 4-3).
Figure 4-3 By exchanging IUnknown interface pointers, the outer object candelegate to the inner object for further processing requests for interfacessupported by the inner object. Likewise, the inner object can delegate to theouter object for further processing requests for interfaces supported by theouter object.
To illustrate COM’s aggregation technique, we will build the AccountInfoCOM object. The AccountInfo object defines two interfaces —IAccountInfo and IMemberInfo — for maintaining informationregarding individual credit card accounts. The properties of theAccountInfo object are listed in Table 4-2.
Table 4-2 AccountInfo Object Interfaces and Properties
Property Data Type Implemented In
AccountNumber long IAccountInfoAccountBalance float IAccountInfoAccountLimit long IAccountInfoName LPSTR IMemberInfoSex unsigned char IMemberInfoAddress LPSTR IMemberInfoCity LPSTR IMemberInfoState LPSTR IMemberInfoZip LPSTR IMemberInfoPhone LPSTR IMemberInfo
As you may have noticed, the AccountInfo object’s IMemberInfointerface is exactly the same as the IMemberInfo interface defined earlierby the MemberInfo object. That’s because we are going to use theMemberInfo object as the inner object. The AccountInfo object will
implement the IAccountInfo interface itself, but will aggregate theMemberInfo object in order to expose the IMemberInfo interface. I thinkit’s worth mentioning that containment and aggregation aren’t mutuallyexclusive. Interfaces created through containment can be aggregated later and,likewise, aggregate interfaces can be contained later. For example, thischapter’s AccountInfo object exposes the IMemberInfo interfacedirectly through aggregation, and it just so happens that the IMemberInfointerface was created through containment of Chapter 2’s IUserInfointerface.
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Creation of the AccountInfo object begins with the same routine as always — allocatingGUIDs and defining the object using IDL. Even though the AccountInfo object itself doesn’tdirectly implement IMemberInfo, it must still define it as a supported interface in the objectdefinition. But rather than redefine the IMemberInfo interface, we can just import itsdefinition from the MemberInfo.idl file using the import directive:
import "MemberInfo.idl";
Once the IMemberInfo interface definition has been imported, and the IAccountInfointerface defined, the AccountInfo object can be defined (see Listing 4-4).
Listing 4-4. AccountInfo.idl
////AccountInfo.idl//import "unknwn.idl";//Import declaration for IMemberInfoimport "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 interfaceinterface 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 librarylibrary 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 IDLfile using the MIDL compiler and defining the CAccountInfo C++ class. Notice that theCAccountInfo class doesn’t inherit from IMemberInfo; nor does it define any of theIMemberInfo interface functions. This is because the AccountInfo object will aggregatethe MemberInfo object in order to provide the implementation for the IMemberInfointerface. AccountInfo aggregates MemberInfo by exchanging IUnknown interfacepointers with MemberInfo in a call to CoCreateInstance, which is called as part ofCAccountInfo::Initialize. AccountInfo offers its IUnknown pointer toMemberInfo in the second parameter to CoCreateInstance, and at the same time requestsMemberInfo’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::CreateInstance, the class factory function responsible for creating AccountInfo objects. Noticethat even though the AccountInfo object aggregates the MemberInfo object, theAccountInfo 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 CreateInstanceon the MemberInfo object’s class factory. The MemberInfo object knows that it is beingcreated as part of an aggregate whenever it detects a nonNULL value in the second parameter toCreateInstance. In an aggregate situation, the second parameter to CreateInstance
contains the IUnknown interface pointer of the outer object, which in this case is theAccountInfo object. The MemberInfo object stores the outer object’s IUnknown interfacepointer, also called the controlling unknown, in the m_pControllingIUnknown membervariable, 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 maintainthe reference count of the outer object. For example, if a client calls AddRef on theIMemberInfo interface, the reference count of the outer AccountInfo object should beincremented; likewise, if a client calls Release on the IMemberInfo interface, the referencecount of the outer object should be decremented. This explains why AddRef and Release aredelegated to the controlling unknown, but what about QueryInterface? TheQueryInterface 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 aresupported by the outer object. The outer AccountInfo object’s QueryInterface is thenslightly 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 ifm_pMemberInfoIUnknown is a pointer to the inner object’s IUnknown, and the innerobject’s IUnknown simply delegates to the outer object’s IUnknown, then we’ve just created acircular reference! You are right, except that m_pMemberInfoIUnknown doesn’t point to theinner object’s IUnknown. The MemberInfo object actually implements two IUnknowns: thedelegating unknown, which simply delegates to the controlling unknown, and the nondelegatingunknown, which actually implements IUnknown as we know it. To avoid a circular reference,CAccountInfo’s m_pMemberInfoIUnknown points to the nondelegating unknown, whichis used by the outer object to control the lifetime of the inner object and access its interfaces. TheMemberInfo object’s nondelegating unknown interface is created by first defining its VTBLlayout, 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 suchthat 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 thefunctions 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The MemberInfo object’s class factory is then modified to use the nondelegating unknown toobtain the initial IUnknown interface pointer in response to the aggregating object’s call toCoCreateInstance:
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 destroysitself, it releases the last outstanding call on the aggregated object, forcing it to destroy itself aswell:
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 todo is use the INonDelegatingUnknown as the controlling unknown. This way, any calls to thedelegating unknown will simply be forwarded to the object’s own INonDelegatingUnknowninterface, which contains the traditional IUnknown implementation. The decision of whether ornot the MemberInfo object is actually being created as part of an aggregate is made in theCMemberInfo 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 aninner COM object as if the outer object implemented them directly. As a potential outer object, youcan determine if an object supports aggregation by simply calling CoCreateInstance andattempting to aggregate it. If the call succeeds, the object supports aggregation; otherwise, itdoesn’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 andnondelegating IUnknown.
For those COM objects that don’t support aggregation, your only opportunity for reuse is throughthe containment process. For this reason, I strongly suggest that you take the extra step and supportaggregation when creating your own COM objects. Source code for the AccountInfoClientapplication can be found on the accompanying CD-ROM. The AccountInfoClient applicationputs the AccountInfo object through its paces by using various interface functions from each ofthe 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 objectsimply acting as a client of the inner object.
• Containment is best suited for reusing interfaces that implement some, but not all, of thedesired level of functionality.
• Aggregation requires cooperation from both the outer object and the inner object, andtherefore 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 inthe same process space.
• Both methods of component reuse are transparent to the client of the outer object, in thesense that the client has no idea that the outer object contains or aggregates other innerobjects.
In the next chapter you learn about the benefits of Automation and how to implement COM objectsthat 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Chapter 5Building Automation ObjectsIN 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 andAutomation
UNTIL THIS POINT, all the interfaces we have defined have been custominterfaces, which means that we defined them ourselves; they were not definedby COM, OLE, or ActiveX. Clients use the VTBL definitions of COMinterfaces to perform type checking at compile time in a process known asVTBL binding. VTBL binding is extremely fast because all type checking isdone at compile time. However, scripting languages like VBScript (VBS) andJava Script (JScript) are interpreted, not compiled, and their ever-increasingpopularity cannot be ignored. To accommodate the run-time type checking thatis required by these and other scripting and macro languages, Microsoftdefined Automation, the subject of this chapter.
An Introduction to Automation
Automation is a technology, built on top of COM, that is designed to provide astandard way of exposing COM objects to macro languages, programmingtools, and other COM clients. It was originally created as a way forapplications developed in Visual Basic to control other applications, such asMicrosoft Excel. COM objects that are programmatically controllable viaAutomation are called Automation objects. COM clients that are capable ofcontrolling such Automation objects are called Automation controllers.
Automation controllers communicate with Automation objects via theAutomation-defined IDispatch interface.
Understanding IDispatch
Like all COM interfaces, IDispatch inherits from IUnknown, butIDispatch 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 anAutomation object
GetIDsOfNames Maps a single member name and an optional set ofargument names to a corresponding set of DISPIDs (seebelow), which may then be used in subsequent calls toInvoke
GetTypeInfo Retrieves type information about an Automation objectGetTypeInfoCount 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 addressesof each supported interface function. However, unlike a VTBL, thedispinterface identifies each function using a special dispatch identifier, orDISPID. A DISPID is not a GUID, but simply a long integer that uniquelyidentifies a function within a particular dispinterface. Clients callIDispatch::GetIDsOfNames with the name of the property or methodthat they are interested in accessing. The Automation object’s implementationof IDispatch::GetIDsOfNames uses the name to perform a lookupagainst the type library to obtain the appropriate DISPID, which it returns tothe client. The client may then call IDispatch::GetTypeInfoCount todetermine if the Automation object supplies a type library. If the Automationobject supplies a type library for the dispinterface in question, the client canobtain a pointer to it by calling IDispatch::GetTypeInfo. The clientcan use this type information to perform any necessary type checking. At thispoint, the client can call IDispatch::Invoke with the DISPID andappropriate function parameters to actually access the desired property ormethod. 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 checkingrequired by popular script and macro languages, it doesn’t come for free.Instead of directly accessing an interface function through a VTBL (VTBLbinding), Automation controllers are required to use IDispatch::Invoke
with DISPIDs that are obtained at run time, either via calls toIDispatch::GetIDsOfNames, in a process known as late binding, or atcompile time via access to the Automation object’s type library, in a processknown as early binding.
Late binding is an expensive operation because the DISPID for each propertyor method used must be retrieved at run time usingIDispatch::GetIDsOfNames before the property or method can beaccessed by IDispatch::Invoke. However, some Automation controllersare capable of obtaining and caching the various property and methodDISPIDs of an Automation object from the object’s type library at compiletime by using early binding. By obtaining and caching the DISPIDs from thetype library, the Automation controller can avoid callingIDispatch::GetIDsOfNames every time it needs to access a particularproperty or method. By avoiding the additional call toIDispatch::GetIDsOfNames, the Automation controller is able toincrease its overall performance. However, to support early binding, theAutomation controller must have access to the Automation object’s typelibrary at compile time, unlike late binding, in which the Automationcontroller must have access to the object’s type library at run time.
In either case, supporting IDispatch requires additional programming effortfor the programmer when creating the Automation controller, due in large partto 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 popularscripting and macro languages, without penalizing C/C++ developers who aremost interested in the speed and ease of development offered by VTBLbinding, 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Understanding Dual Interfaces
A dual interface is an interface whose methods can be called either directly through the VTBL orindirectly through IDispatch. While implementing dual interfaces does require additionalwork, it’s not as complicated as it may seem. There are several requirements for implementing adual 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, whileFigure 5-3 shows the memory layout for a dual interface that inherits from IDispatch. Noticethat even though the dual interface inherits from IDispatch, the first three member functionsare those of IUnknownQueryinterface, AddRef, and Release. This is because, like allCOM interfaces, IDispatch itself inherits from IUnknown. Automation controllers use theIDispatch side of the dual interface, while C/C++ programmers can use the VTBL side of thedual 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 isin fact a dual interface:
[ object,
uuid(01234567-89ab-cdef-0123-456789abcdef), helpstring("ISomeDualInterface interface."), dual]//declaration of the ISomeDualInterface interfaceinterface ISomeDualInterface : IDispatch{...}
In Chapter 2, we saw the wide range of IDL-supported datatypes useful for defining COMinterfaces (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 inTable 5-2.
Table 5-2 Intrinsic IDL Datatypes Supported by Automation
Datatype Description
VARIANT_BOOL Data item having either a TRUE or FALSE valuechar 8-bit signed data itemdouble 64-bit IEEE floating-point numberint System-dependent signed integerfloat 32-bit IEEE floating-point numberlong 32-bit signed integershort 16-bit signed integerBSTR Length-prefixed stringCURRENCY 8-byte, fixed-point numberDATE 64-bit floating-point fractional number of days since December
30, 1899SCODE Built-in error type that corresponds to VT_ERROR. An
SCODE (used on 16-bit systems only) does not contain theadditional error information provided by an HRESULT.
IDispatch* Pointer to an IDispatch interfaceIUnknown* Pointer to an IUnknown interfaceSAFEARRAY(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 typessupported 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 asa way to store different types of data in a common manner. As Listing 5-1 illustrates, a variant isessentially a structure that consists of a union of each supported datatype and an indicator variableused 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
When retrieving information from a variant, you first need to determine what type of datais being stored in it. This is done by referring to the variant’s vt member. Table 5-3 listsand describes the meanings of the various vt values. The vt value signals which memberof 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 isidentify the type of data that you’re storing in the variant by assigning the appropriatevalue 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 Valmember. The following code snippet demonstrates how a variant can be used to storevarious 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 data1 VT_NULL VARIANT contains NULL2 VT_I2 A 2-byte integer is in iVal3 VT_I4 A 4-byte integer is in lVal4 VT_R4 An IEEE 4-byte real is in fltVal5 VT_R8 An IEEE 8-byte real is in dblVal6 VT_CY An 8-byte two’s complement currency value is
in cyVal7 VT_DATE A double-precision date is in date8 VT_BSTR A BSTR is in bstrVal9 VT_DISPATCH An IDispatch pointer is in pdispVal10 VT_ERROR An SCODE is in scode11 VT_BOOL A Boolean (True = 0xFFFF/False = 0) value is
in bool12 VT_VARIANT Must be combined with VT_BYREF. A pointer
to a VARIANTARG is in pvarVal.13 VT_UNKNOWN An Iunknown pointer is in punkVal0×800 VT_RESERVED Reserved0×400 VT_BYREF Can be combined with other VT values to
indicate values being passed by reference0×200 VT_ARRAY Can be combined with other VT values, except
VT_EMPTY and VT_NULL, to indicate an arrayof that datatype. The array descriptor is inpByrefVal.
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 dispinterfacefunctions. However, as variants only support a limited number of datatypes, andAutomation relies on variants, Automation is limited to supporting only those datatypessupported by variants. Table 5-4 lists Win32 API functions that are useful for working withvariants.
Table 5-4 Win32 API Functions Useful for Working with Variants
Function Purpose
VariantChangeType Converts a variant from one type to anotherVariantChangeTypeEx Converts a variant from one type to another according
to a locale identifier (LCID), an identifier used todetermine the text and data formatting conventions of aparticular geographical region
VariantClear Releases resources associated with a particular variantand sets the variant to VT_EMPTY
VariantCopy Copies a variantVariantCopyInd Copies a variant that may contain a pointer
VariantInit Initializes a variantDosDateTimeToVariantTime Converts MS-DOS date and time representations to a
variant timeVariantTimeToDosDateTime Converts a variant time to MS-DOS date and time
representationsVariantTimeToSystemTime Converts a variant time to system date and time
representationsSystemTimeToVariantTime 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 SAFEARRAYdatatypes. A BSTR, or binary string, is a pointer used to refer toNULL-terminated string data. However, unlike traditional char*s, the lengthof the BSTR is stored as an integer value that precedes any actual data (seeFigure 5-4).
Figure 5-4 Unlike a traditional char*, the length of a BSTR is stored as aninteger that precedes any actual data.
Even though the length of a BSTR precedes any actual data, note that theBSTR pointer itself points to the memory location of the first byte of data.Because BSTRs include their length, they are capable of containing binarydata that may include multiple NULL characters. On 16-bit systems, BSTRscontain ANSI characters, while on 32-bit systems, BSTRs contain Unicodecharacters. (See the sidebar “Understanding Character Sets” for moreinformation on different character sets.) Table 5-5 lists Win32 API functionsthat are useful for working with BSTRs.
Table 5-5 Win32 API Functions Useful for Working with BSTRs
Function Purpose
SysAllocString Creates and initializes a BSTRSysAllocStringByteLen Creates a NULL-terminated BSTR of a specified
length (32-bit only)
SysAllocStringLen Creates a BSTR of a specified lengthSysFreeString Releases BSTR resources previously allocated with
SysAllocStringSysReAllocString Changes the size and value of an existing BSTRSysReAllocStringLen Changes the size of an existing BSTRSysStringByteLen 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 inthe set is associated with a unique code that serves as its identifier. Charactersets are typically categorized according to the number of bytes they use torepresent 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 characterset (DBCS) can use up to two bytes (16-bits) of storage to represent eachcharacter. Because some of the characters of a DBCS only require one byteof storage while others require two, DBCSs are often referred to as multibytecharacter sets (MBCSs). The more bytes a particular character set uses foreach of its IDs, the more unique IDs are possible, and thus the more possiblecharacters the set can represent. For example, most of us are familiar withthe American National Standards Institute (ANSI) SBCS, which is afixed-width character set that uses eight bits for each character ID, whichmeans that it is only capable of representing 256 different character IDs:0–255. Furthermore, there is more than one ANSI character set — WesternEuropean, Eastern European, Baltic, Arabic, Hebrew, Greek, Turkish, etc.Windows tracks the current ANSI character set in a code page; whicheverANSI code page is currently loaded determines the character set used todecode the character IDs. This not only makes it tough for developers, butthe numerous ANSI character sets also make it very difficult to createmultilingual documents. While room for 256 different characters is morethan adequate to represent the 26 characters of the English language, somelanguages — 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- andmultibyte character sets, a group of vendors united to form the UnicodeConsortium, and they created the Unicode standard. Unicode is a fixed-widthcharacter set that uses 16 bits for each character ID. The 16 bits of storagemeans that Unicode has room for up to 65,536 unique character IDs.Currently, Windows NT is the only version of Windows that uses Unicode asits base character-encoding mechanism. However, because the Win32 API issupported on Windows 3.x and Windows 95 as well as Windows NT, theWin32 APIs provide two different entry points for each system function thatexpects a string parameter: an ANSI version and a Unicode version, called awide-character version. Which entry point is used depends on whether or notthe compile-time UNICODE symbol is defined. If UNICODE is defined,then the Unicode version of each Win32 API is used; otherwise, the ANSIversion is used.
This all works thanks to macro-magic in the Windows.h file. Windows.hprovides a macro for each Win32 API function, which expands to theappropriate version, whether or not UNICODE is defined. Windows.h alsodefines several useful generic text-mapping macros such as TEXT and T, aswell as several generic datatypes, such as TCHAR and LPTSTR. The TEXTand T macros should be used to ensure that string and character literals areappropriately defined as either ANSI or Unicode, depending on whether ornot UNICODE is defined. TCHAR and LPTSTR are generic datatypes thatshould be used to replace references to the char and LPSTR datatypes. IfUNICODE 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 pointerto a Unicode string, depending on whether or not UNICODE is defined.These and other text-mapping macros and generic datatypes help developerswrite a single source code base to the Win32 APIs, and still easily targetUnicode systems like Windows NT by simply defining the UNICODEsymbol 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Understanding SAFEARRAYs
The other datatype that you may not have recognized is the SAFEARRAY. ASAFEARRAY is exactly what its name implies, an array that incorporates itsown safety mechanisms to prevent writing beyond the bounds of the array. ASAFEARRAY accomplishes this by maintaining information regarding thefollowing:
• The number of dimensions, stored in the cDims member variable
• The upper and lower bounds of each dimension of the array, stored inthe rgsabound array member variable
• The size of each element in the array, stored in the cbElementsmember 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 datatypesexcept VT_ARRAY, VT_EMPTY, VT_NULL, and those types that have theirVT_BYREF flag set. Table 5-6 lists Win32 API functions that are useful forworking with SAFEARRAYs.
Table 5-6 Win32 API Functions Useful for Working with SAFEARRAYs
Function Purpose
SafeArrayAccessData Increments the lock count of an array and returnsa pointer to array data
SafeArrayAllocData Allocates memory for a safe array based on adescriptor created withSafeArrayAllocDescriptor
SafeArrayAllocDescriptor Allocates memory for a safe array descriptorSafeArrayCopy Copies an existing safe arraySafeArrayCopyData Copies a source array to a target array after
releasing source resourcesSafeArrayCreate Creates a new array descriptorSafeArrayCreateVector Creates a one-dimensional array whose lower
bound is always zeroSafeArrayDestroy Destroys an array descriptorSafeArrayDestroyData Frees memory used by the data elements in a
safe arraySafeArrayDestroyDescriptor Frees memory used by a safe array descriptorSafeArrayGetDim Returns the number of dimensions in an arraySafeArrayGetElement Retrieves an element of an arraySafeArrayGetElemsize Returns the size of an elementSafeArrayGetLBound Retrieves the lower bound for a given dimensionSafeArrayGetUBound Retrieves the upper bound for a given dimensionSafeArrayLock Increments the lock count of an arraySafeArrayPtrOflndex Returns a pointer to an array elementSafeArrayPutElement Assigns an element to an arraySafeArrayRedim Resizes a safe arraySafeArrayUaccessData Frees a pointer to array data and decrements the
lock count of the arraySafeArrayUnlock Decrements the lock count of an array
Building an Automation Object
We will now expand on our introductory knowledge of Automation by takingthe AccountInfo COM object that we created in Chapter 4 and recreating itas the AccountInfoAuto Automation object. However, to reduce anypotential confusion, AccountInfoAuto fully implements each of theinterfaces that it exposes, and doesn’t rely on any other COM objects.
Isolating Automation Specifics
The AccountInfoAuto object will provide the same basic informationregarding individual credit card accounts as the AccountInfo object, but inan Automation-compatible manner. The AccountInfoAuto object exposesthe exact same IAccountInfo and IMemberInfo custom interfaces as theAccountInfo object. However, the AccountInfoAuto object alsoexposes the IAccountInfoDispatch dual interface. Table 5-7 lists theproperties defined by each AccountInfoAuto object-supported interface.As you look at the interface definitions, you will notice that theIAccountInfoDispatch dual interface is an Automation-compatiblecombination of the IAccountInfo and IMemberInfo custom interfaces.However, because IAccountInfoDispatch is a dual interface, it isrestricted to the Automation-compatible datatypes listed in Table 5-2 andreplaces the LPSTR datatype of the properties originally defined by theIMemberInfo 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The IDL definition of the IAccountInfoDispatch interface can be seen in Listing 5-2. Asyou look at the definition, notice the use of the dual IDL keyword in the Attributes sectionof the interface definition, signaling that the interface is in fact a dual interface that must beAutomation-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 interfaceinterface 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 keyworddefault in the definition of the AccountInfoAuto coclass, to indicateIAccountInfoDispatch 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The IAccountInfoDispatch dual interface allows Automation controllers to manipulate theAccountInfoAuto object. The custom IAccountInfo and IMemberInfo interfaces allowC++ programmers to manipulate the AccountInfoAuto object without the burden of beingAutomation controllers. While it is very beneficial to support both C++ andAutomation-compatible clients, it can be a very time-consuming task to implement specialinterfaces for each. To reduce the amount of additional code necessary to support both C++ andAutomation-compatible clients, the IAccountInfoDispatch dual interface will delegate itsimplementation to either the IAccountInfo or IMemberInfo custom interfaces, after somebasic preprocessing (see Listing 5-3). As you look at the listing, notice the use of theSysAllocString and SysStringLen Win32 API functions to manipulate BSTR data.
Listing 5-3. The properties of the IAccountInfoDispatch dual interface are implemented bydelegating to either the IAccountInfo or IMemberInfo custom interfaces, after some basicpreprocessing.
////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 MIDLcompiler whenever you compile your IDL file. The library IDL keyword signals to the MIDLcompiler 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 librarylibrary 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 theAccountInfoAuto object. Unlike interface definitions, object definitions cannot exist outsideof a library definition. Once the MIDL compiler has compiled the IDL file and generated atype library, you need to bind it to your compiled executable (DLL or EXE). To bind the typelibrary to your executable, you need to include the type library as a resource to your application’sproject. Because you can bind multiple type libraries to a single executable (possibly to supportmultiple 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 bindthe 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 beginwith IDispatch::GetTypeInfoCount. Automation controllers interested in obtaininga pointer to a type library will call IDispatch::GetTypeInfoCount to determine if theAutomation object exposes type information for the IDispatch interface. The Automationcontroller uses the type information to perform any required run-time syntax checking. If theobject does in fact expose type information, IDispatch::GetTypeInfoCount willrespond 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 todo is call IDispatch::GetTypeInfo to obtain a pointer to the type information for theIDispatch interface. Before the object can hand out such a pointer, it must first load thetype information for the IDispatch pointer into memory. This is done as part of theinitialization 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 actualtype information of interest — a GUID identifying the type library itself and a GUIDidentifying 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 bythe incoming rguid parameter. If the call is successful, the type library was in fact alreadyloaded and registered. If the call is unsuccessful, the type library is registered in a call toLoadTypeLib. LoadTypeLib will only register the type library if the filename passed toit is not a fully qualified filename. To ensure that the type library is registered, a call toRegisterTypeLib 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 orinterface identified by the incoming CLSID parameter. Finally, the pointer to the type libraryis 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 iscapable 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 bedelegated to member functions of the m_pTypeInfo IDispatch type informationinterface 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 castIAccountInfoDispatch 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 theAccountInfoAuto object, and fill out DLLMain.cpp. Therefore, we will turn our attentionto 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 followingregistry 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 withhuman-readable names. ProgIDs are of two different types: version-dependent andversion-independent. Unlike CLSIDs, which are generated by a COM API call, you as adeveloper are ultimately responsible for creating your own ProgIDs, which typically use thefollowing 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 aversion-dependent ProgID to create a version 6 Word document and a version-independentProgID 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’sCLSID 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 eitherexplicitly or through calls to LoadTypeLib or RegisterTypeLib, as we have done inLoadTypeInfo (see Listing 5-4). Regardless of which technique is used, the Automationserver is responsible for adding the following registry entries under the HKEY_CLASSES_ROOTkey:
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 theLANG_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 supportedinterface. Luckily, the Automation library supplies a default Automation proxy/stub pair that canbe used by any Automation-compatible interface. The CLSID for the default Automationproxy/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 vanillaCOM object, it is not necessarily harder. Also, by implementing dual interfaces such that theydelegate to custom interfaces, you can create COM objects that appeal to a larger developeraudience. In the next chapter, we will build two client applications that make use of theAccountInfoAuto Automation object. One uses the VTBL side of the dual interface; theother is an Automation controller that uses the IDispatch side of the dual interface. The fullsource code for the AccountInfoAuto Automation object can be found on the companionCD-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 toimplement than VTBL binding, and they are somewhat slower because of their additionaloverhead.
• Supporting dual interfaces allows clients to bind using either the VTBL side or theIDispatch side of an interface.
• In order to support IDispatch, interfaces are restricted to using only the datatypesallowed by variants.
In Chapter 6, you learn how to build client applications that use the VTBL and IDispatch sides ofthe 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Chapter 6Building Automation ControllersIN THIS CHAPTER
• How to access the properties and methods of an Automation object from the VTBL sideof a dual interface
• How to access the properties and methods of an Automation object from theIDispatch 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. TheAccountInfoAuto object supports the IAccountInfoDispatch dual interface, whichmeans that IAccountInfoDispatch can be accessed both at compile time via VTBLbinding as well as at run time via IDispatch. In addition, AccountInfoAuto also supportsthe IAccountInfo and IMemberInfo custom interfaces, which may only be bound to atcompile time via VTBL binding. You already learned how to access custom interfaces via VTBLbinding by building the UserInfoClient application in Chapter 2. The UserInfoClientwas 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 theIAccountInfoDispatch dual interface, by building the AccountInfoAutoVTBLapplication. You will also learn how to access the IDispatch side of theIAccountInfoDispatch dual interface, by building the AccountInfoAutoDispapplication. 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 theAccountInfoAuto object that we developed in the last chapter. Once theAccountInfoAuto object is created, we will use the VTBL-side of theIAccountInfoDispatch 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 dualinterface to retrieve the various properties of the AccountInfoAuto object. Once all of theproperties 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 theAccountInfoAuto object can be seen below in Table 6-1.
Table 6-1 AccountInfoAuto Object Properties
Property Name Datatype
Number longBalance floatLimit longName BSTRSex unsigned charAddress BSTRCity BSTRState BSTRZip BSTRPhone BSTR
We will build the AccountInfoAutoVTBL application by following the responsibilities of aCOM 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 theWinMain application entry-point function. Once inside WinMain, theAccountInfoAutoVTBL application’s first responsibility is to initialize the COM librarywith 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 AccountInfoAutoVTBLapplication calls CoCreateInstance to create a new AccountInfoAuto object andrequest an initial pointer to its IUnknown interface. Notice the use of theCLSCTX_INPROC_SERVER class execution context constant to specify that we are onlyinterested in an in-process server for the AccountInfoAuto object, which is identified by theCLSID_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, COMdefines 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Manipulating the COM Object
Once the AccountInfoAuto object has been successfully instantiated, and we haveobtained an initial IUnknown interface, we can use QueryInterface to navigate to theIAccountInfoDispatch interface. Ultimately, we will use theIAccountInfoDispatch interface to set each property defined by theIAccountInfoDispatch interface. (A complete list of the properties defined by theIAccountInfoDispatch 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 dualinterface from the VTBL side is the same as setting the properties of a custom interface, theonly difference being that dual interfaces are restricted to Automation-compatible datatypesonly. (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 theBSTR and the SAFEARRARY. Table 5-5 lists Win32 API functions that are useful forworking with BSTRs, while Table 5-6 lists Win32 API functions that are useful for workingwith SAFEARRAYs.
Before we can set each property of the IAccountInfoDispatch interface, we need toinitialize the various BSTR variables that will be used to transfer the actual value of eachBSTR property. The BSTR transfer variable for each BSTR property is initialized using theSysAllocString Win32 API function. SysAllocString takes a pointer to anOLESTR as its only parameter, and returns a BSTR. OLESTRs are created using the OLESTRmacro, and are essentially wide-character strings. (See the sidebar “Understanding CharacterSets” 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 theIAccountInfoDispatch interface, and frees the resources allocated to the various BSTRvariables 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 toretrieve, format, and display the values of the various properties defined by theIAccountInfoDispatch interface. DisplayAccountInfoDispatch takes twoparameters: pIAccountInfoDispatch — an IAccountInfoDispatch pointer; andlpszRetreivedMsg — a string pointer to a message to be displayed viaDisplayMessage. DisplayAccountInfoDispatch begins by retrieving the variousproperties of the incoming pIAccountInfoDispatch parameter, and displaying theincoming message pointed to by the lpszRetrievedMsg parameter. Next,DisplayAccountInfoDispatch builds a single string consisting of the name of eachproperty, followed by the value of that particular property. A return character (\ r) separateseach property, name/property value pair, and all string values are converted to the appropriatecharacter format, as part of the property, formatting process. Once all of the properties havebeen retrieved and formatted, the entire resulting string is displayed usingDisplayMessage. The source code for the DisplayAccountInfoDispatch functioncan 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Releasing the COM Object
After DisplayAccountInfoDispatch is called to retrieve, format, and display the variousIAccountInfoDispatch properties, WinMain continues by calling the Release functionon all of its outstanding interface pointers, which includes one IUnknown pointer and oneIAccountInfoDispatch pointer. At that point, the AccountInfoAuto object destroysitself:
//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 isuninitialize 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 custominterfaces. However, because dual interfaces are restricted to Automation-compatible datatypesonly, you will have to get acquainted with the various Win32 APIs useful for working with someof 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 bybuilding the AccountInfoAutoVTBL application. The AccountInfoAutoVTBL applicationwas a simple application that created the AccountInfoAuto object developed in Chapter 5, andused the VTBL side of the object's AccountInfoDispatch dual interface to set and retrieve thevarious 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 sectionyou learn how to access the IDispatch side of a dual interface by building theAccountInfoAutoDisp application. Like the AccountInfoAutoVTBL application, theAccountInfoAutoDisp application is a simple application that creates theAccountInfoAuto object developed in Chapter 5, and uses the object’s IDispatch interface toset and retrieve the various properties defined by the object before formatting and displaying theirrespective values. To better enable you to compare and contrast the differences between VTBL andIDispatch access of a dual interface, we use the source code from the AccountInfoAutoVTBLapplication as our boilerplate, and modify it as necessary to create the AccountInfoAutoDispapplication.
Setting Property Values Using IDispatch
Like the AccountInfoAutoVTBL application, the AccountInfoAutoDisp application beginsby initializing the COM library, creating the AccountInfoAuto object, and obtaining an initialinterface pointer to the AccountInfoAuto object’s IUnknown interface. However, instead ofusing IUnknown::QueryInterface to obtain an IAccountInfoDispatch interfacepointer, the AccountInfoAutoDisp application uses IUknown::QueryInterface to obtainan IDispatch interface pointer. After retrieving the IDispatch interface pointer, theAccountInfoAutoDisp application uses it to set each of the properties defined by theAccountInfoAuto object. However, before we can set any of the AccountInfoAuto object’sproperties using IDispatch, we must have the dispatch identifier (DISPID) for each individualproperty. Each DISPID can be obtained by calling IDispatch::GetIDsOfNames with the nameof each property. In order to automate the process of obtaining the DISPID for each property, I havedefined 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 byIDispatch::GetIDsOfNames to retrieve the DISPID for each property, which is then stored ina global array of DISPIDs. As you look at the following code snippet, notice the use of theLOCALE_SYSTEM_DEFAULT to signify that the default system locale should be used to interpretthe various property names:
//Get the DISPID for the each propertyfor (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 eachproperty. Reading and writing an individual property is a two-step process:
• Fill in a DISPPARAMS structure with information regarding any parameters expected by theproperty 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 thenumber of arguments supplied in the rgvarg array. Arguments may be passed by position and/or byname. To pass arguments by position, package each argument into a VARIANT and store theVARIANTs in the rgvarg array in the reverse order of their positions, such that the last positionalargument is in rgvarg[0] and the first positional argument is in rgvarg [cArgs - 1]. (See Figure6-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 thergdispidNamedArgs array element. DISPIDs for property or method parameters can be retrievedby supplying the names of each parameter after the property or method name in a call toIDispatch::GetIDsOfNames. However, don’t forget to changeIDispatch::GetIDs0fNames’s third parameter to reflect the number of DISPIDs that you areexpecting to receive in the return DISPIDs array. Update cNamedArgs to reflect the number ofnamed arguments being passed in rgdispidNamedArgs. While the ordering of named argumentsshould be irrelevant, named arguments are typically supplied in reverse order, just like positionalarguments. Automation defines the DISPID_PROPERTYPUT DISPID, which is required whensetting 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
As all of the AccountInfoAuto properties require only one parameter — the new valueto assign to the property — the DISPPARAMS structure is initialized once and then usedrepeatedly in subsequent calls to IDispatch::Invoke, changing only the VARIANTarguments 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 parametersdispparams.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 isto reflect the one DISPID-PROPERTYPUT DISPID that is being supplied as the firstelement in the rgdispidNamedArgs array, signaling that we are attempting to set aproperty. The only thing left to do before calling IDispatch::Invoke to actuallyupdate each property’s value is to provide the new value for each property. The value ofeach property must be supplied as a VARIANT in the rgvarg array. As you look at thefollowing code snippet, notice how the VARIANT’s vt value is used to reflect the type ofdata 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 toinitialize the various BSTR variables that will contain the actual data. These BSTR variablesare then assigned to the rgvarg VARIANT array like all the other property values. Theresources 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 actuallyupdate its value. The first parameter expected by IDispatch::Invoke is the DISPID ofthe property or method being accessed. The second parameter is reserved, and must beIID_NULL, while the third parameter specifies the ID of the locale to be used to interpretany arguments supplied in the call. To avoid naming conflicts between properties andmethods with the same name on the same Automation object, the fourth parameter toIDispatch::Invoke provides a way for the Automation controller to specify thecontext 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 withDISPATCH_PROPERTYGET to indicate a property withthe same name.
DISPATCH_PROPERTYGET Returns the value of a propertyDISPATCH_PROPERTYPUT Assigns a new value to a propertyDISPATCH_PROPERTYPUTREF Assigns a valid object reference, rather than a value, to a
property.
The fifth parameter expected by IDispatch::Invoke is the address of theDISPPARAMS structure containing the parameter information. The sixth parameter is used
to store any expected return results. The seventh parameter is used to gather any exceptioninformation that may be returned and, finally, the eighth parameter is used to returninformation about the first invalid argument. Because all arguments are supplied asVARIANTs, the Automation client is responsible for converting data from oneVARIANT-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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Retrieving Property Values Using IDispatch
IDispatch::Invoke is also used to read property values. The AccountInfoAutoDispapplication’s implementation of the DisplayAccountInfoDispatch function has beenmodified to use IDispatch::Invoke to read each property value before formatting anddisplaying them. DISPATCH_PROPERTYGET is used to indicate that we are interested in receivingthe value of a property. Notice how dispparamsNoArgs is configured, with its pointer elementsset to NULL and its numeric elements set to zero, reflecting that fact that no arguments are requiredto read a property. The return value of each property is actually returned as a VARIANT invRetVal, the sixth parameter of IDispatch::Invoke:
.
.
.DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};VARIANT vRetVal;
//Retrieve each property//NumberpIDispatch->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];
////DisplayMessagevoid 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
While working with the IDispatch side of a dual interface is not terriblycomplex, it does require more effort on the part of the client. This extra workstems from the process of obtaining the DISPIDs of the various properties andmethods supported by an Automation object and using them to actuallyexecute a particular function. Clients can obtain an Automation object’ssupported DISPIDs at compile time (early binding), or at run time (latebinding); both processes are discussed in Chapter 5. Late binding gives anAutomation controller the ability to query the services of an Automation objectat run time. After obtaining the DISPID for a particular property or method, anAutomation controller can execute the function by simply calling the object’sIDispatch::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 acustom COM interface, except that dual interfaces are restricted toAutomation-compatible datatypes.
• That while the IDispatch side of a dual interface allows forrun-time binding, it requires more work (from the Automationcontroller’s perspective).
• That obtaining an Automation object’s DISPIDs from its type libraryat compile time can significantly increase the performance of executinga function through IDispatch::Invoke.
• That while Automation objects are restricted to using only thedatatypes supported by VARIANTs, the Automation library providesintrinsic 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Part IIBuilding Componentized Applications
Chapter 7Building Object HierarchiesIN THIS CHAPTER
• How to define and build an object hierarchy, a collection of highlyinteroperable 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 ODBCdata source
IN THE FIRST part of this book, you learned the fundamentals of COM andeven built several different COM servers. In this part, you will learn how todevelop component-based applications by using COM objects to create twothree-tiered versions of a simple order-entry system. One version of the systemfollows the traditional client/server architecture; the other follows aWeb-based architecture.
While the two applications follow different architectural designs, both arerepresentative of what might be used by a typical mail-order company, wherecustomers order products over the telephone and pay for their purchases usinga special credit card account. To be effective, the order-entry application mustallow the mail-order company to maintain information for each product beingsold, each customer’s account, and each purchase invoice describing whichcustomer bought which product(s). All of the information regarding customeraccounts, product inventory, and customer purchase invoices will be stored in
a central data source. Both versions of the application will use the OpenDataBase Connectivity (ODBC) API to provide a universal way to accessdifferent data sources. Although the two applications have differentarchitectures, they’re both required to perform many of the exact same tasks inorder to be successful. For example, both versions of the order-entryapplication must have the ability to add new customer accounts to theunderlying data source. These areas of common functionality are prime targetsfor componentization.
In this chapter, we build the OrderEntry object hierarchy, a collection ofhighly interoperable COM objects designed to provide basic order-entrycapabilities. By using the OrderEntry object hierarchy as the basis for bothversions of the order-entry application, you gain firsthand experience with theinteroperability and component reuse aspects of COM. By building thehierarchy itself in C++; the client/server version of the order-entry applicationin Microsoft Visual Basic; and the Web-based version of the order-entryapplication using HTML, Microsoft Active Server Pages (ASP), and VisualBasic Scripting Edition (VBScript); you will gain firsthand experience withthe language-independent aspects of COM. Finally, in the last chapter, yougain firsthand experience with the location-independent aspects of COM, byusing 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 twobroad categories: entry objects and collection objects. Entry objects are used toconvey relatively static information about an individual entry in the datasource and are typically named according to the information they convey. Forexample, the Account entry object would be used to convey informationabout a particular customer account in the underlying data source. Collectionobjects are used to manage and represent multiple data source entries of aparticular type and are typically given the plural version of the entry objectnames they represent. For example, the Accounts collection object would beused 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 theLineItem object; and four different types of collection objects: theAccounts object, the Products object, the Invoices object, and theLineItems object. Information for each type of entry object is stored in adifferent table in the underlying OrderEntry.mdb Microsoft Access database.Information for the Account object is stored in the Accounts table; theProduct object’s information is in the Products table; the Invoiceobject’s information is in the Invoices table; and the LineItem object’sinformation 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 databasetable 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The Account Object
The Account object is used to provide information regarding a particularcustomer account, similar to that provided by the AccountInfoAuto objectdeveloped in Chapter 5. Even though the Account object andAccountInfoAuto object share many resemblances, we will develop theAccount object from the ground up as opposed to reusing Chapter 5’sAccountInfoAuto object. The Account object maintains the informationgiven in Table 7-1 for each customer’s account.
Table 7-1 Properties of the Account Object
Property Datatype
Number longBalance floatLimit longName BSTRSex BSTRAddress BSTRCity BSTRState BSTRZip BSTRPhone BSTRInService VARIANT_BOOL
The InService property is used to determine if a particular account hasbeen removed from service. Individual accounts are removed from service, asopposed to being deleted from the system. By deactivating an account, the
company is not only able to maintain records of every customer it has everhad, it is also able to maintain data integrity between the Accounts andInvoices database tables. The InService property has aVARIANT_BOOL datatype, which, like a C/C++ Boolean, is used to reflectvalues that are either true or false. However, unlike C/C++ Booleans that usezero to represent FALSE and nonzero to represent TRUE, VARIANT_BOOLsuse negative one (-1) to represent TRUE and zero (0) to represent FALSE. Theproperties of the Account object are designed to reflect the columns of theunderlying Accounts database table as closely as possible (see Table 7-2).
Table 7-2 Columns of the Accounts Database Table
Column Name Datatype
Number longBalance singleLimit longName text(40)Sex byteAddress 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-3for each individual product in the system.
Table 7-3 Properties of the Product Object
Property Datatype
Number longDescription BSTRPrice floatStock longInService VARIANT_BOOL
Again, notice the InService property, which is used to determine if anindividual product has been removed from service. Products no longeravailable for purchase are removed from service, not deleted, which helps tomaintain data integrity between the Products and Invoices databasetables. Table 7-4 lists the columns of the Products database table. Again,notice how the columns of the Products database table reflect the propertiesof the Product object.
Table 7-4 Columns of the Products Database Table
Column Name Datatype
Number longDescription text(40)Price singleStock longInService Boolean
The Invoice Object
The Invoice object is used to provide the information shown in Table 7-5for each individual purchase invoice.
Table 7-5 Properties of the Invoice Object
Property Datatype
Number longEntryDate DateCustomerAccount longLineItems ILineItems*
The CustomerAccount property can be used in conjunction with theAccounts object to determine for a particular invoice the account of thecustomer making the purchase. The internal LineItems object, which isexposed through the LineItems property, provides a mechanism foriterating through the list of products that were purchased as part of an invoice,and also determining how many units of each product were purchased. Table7-6 lists the columns of the Invoices database table.
Table 7-6 Columns of the Invoices Database Table
Column Name Datatype
Number longEntryDate Date/TimeAccountNumber 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The LineItem Object
The LineItem object is used to provide the information shown in Table 7-7for each individual item purchased as part of an invoice.
Table 7-7 Properties of the LineItem Object
Property Datatype
Number longInvoiceNumber longProductNumber longUnitsPurchased 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 longInvoiceNumber longProductNumber longUnitsPurchased long
While each entry object is responsible for conveying information regarding anindividual entry in the data source, none of the entry objects actuallymanipulate the underlying data source. Instead, the various collection objectsare responsible for all data source manipulation. Each collection objectprovides the same basic functionality, allowing you to add new entries to the
underlying data source; update existing entries; remove specific entries; andlocate and retrieve specific entries. Whenever a collection object is used toreturn information regarding a specific entry, it first creates a blank entryobject and initializes it with information retrieved from the underlying datasource. The following sections briefly describe the various collection objectsof 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 todetermine whether the currentrecord pointer has gone beyondthe beginning of the record set.
EOF short Returns a Boolean value used todetermine whether the currentrecord pointer has gone beyondthe end of the record set.
Item(longAccountNumber)
IAccount* Returns a pointer to the Accountobject identified byAccountNumber, the account’sunique number. The Itemproperty also allows the user toaccess the Account identified bythe current record pointer, bycalling Item with -1.
Count long Returns the number of accounts inthe data source.
Method DescriptionAdd(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(longAccountNumber)
Used to remove the account identified byAccountNumber, the account’s unique number, fromservice, such that it can no longer be used.
Move(long INumRecs) Enables the user to reposition the current recordpointer INumRecs to the next record in the record set.
MoveFirst Enables the user to reposition the current recordpointer to the first record in the record set.
MoveLast Enables the user to reposition the current recordpointer to the last record in the record set.
MovePrev Enables the user to reposition the current recordpointer to the previous record in the record set.
MoveNext Allows the user to reposition the current recordpointer to the next record in the record set.
The Item property of the Accounts, Products, Invoices, andLineItems objects can be used in two different ways. In the first scenario,the Item property can be used to return information regarding a specificcustomer account identified by the customer’s account number. The followingcode shows how a client written in Visual Basic might use the Accountsobject’s Item property in the previously described manner:
'Return information about account #333Set objAccount = g_objAccounts.Item(333)
In the second scenario, the Item property is used in conjunction with the fivenavigation methods, Move, MoveFirst, MoveLast, MovePrev, orMoveNext, to retrieve information for the account identified by the currentrecord pointer. The following code illustrates this second scenario from aVisual Basic client’s perspective:
'Move to the first recordg_objAccounts.MoveFirstIf Not g_objAccounts.EOF Then 'Return the information about the first account Set objAccount = g_objAccounts.ItemEnd If
Regardless of which technique is used, whenever the Item property mustreturn an individual object, the Accounts object first creates a blankAccount object, then retrieves the account information from the underlyingdata 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The Products Object
The Products object is used to interact with the underlying Productsdatabase table in much the same way that an Accounts object is used tointeract with the underlying Accounts database table. The functionalityprovided 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 todetermine whether the currentrecord pointer has gone beyondthe beginning of the record set.
EOF short Returns a Boolean value used todetermine whether the currentrecord pointer has gone beyondthe end of the record set.
Item(longAccountNumber)
IProduct* Returns a pointer to the Productobject identified byProductNumber, the product’sunique number. The Itemproperty also allows the user toaccess the Product identified bythe current record pointer, bycalling Item with -1.
Count long Returns the number of products inthe data source.
Method Description
Add(IProduct*) Used to add the account defined by the incomingProduct object to the data source.
Update(IProduct*)Used to modify an existing product entry in the datasource.
Remove(longProductNumber)
Used to remove the product identified byProductNumber, the product’s unique number, fromservice, such that it can no longer be purchased.
Move(long INumRecs) Enables the user to reposition the current recordpointer INumRecs to the next record in the record set.
MoveFirst Enables the user to reposition the current recordpointer to the first record in the record set.
MoveLast Enables the user to reposition the current recordpointer to the last record in the record set.
MovePrev Enables the user to reposition the current recordpointer to the previous record in the record set.
MoveNext Enables the user to reposition the current recordpointer to the next record in the record set.
The Invoices Object
The Invoices object is used to interact with the underlying Invoicesdatabase table, in much the same way that the Accounts and Productsobjects 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 todetermine whether the currentrecord pointer has gone beyondthe beginning of the record set.
EOF short Returns a Boolean value used todetermine whether the currentrecord pointer has gone beyondthe end of the record set.
Item(long InvoiceNumber) IInvoice* Returns a pointer to the Invoiceobject identified byInvoiceNumber, the invoice’sunique number. The Itemproperty also allows the user toaccess the Invoice identified bythe current record pointer, bycalling Item with -1.
Count long Returns the number of invoices inthe data source.
Method Description
Add(IInvoice*) Used to add the invoice defined by the incomingInvoice object to the data source.
Update(IInvoice*)Used to modify an existing invoice entry in thedata source.
Remove(longInvoiceNumber)
Used to remove the invoice identified byInvoiceNumber, the invoice’s unique number,from the data source.
Move(long INumRecs) Enables the user to reposition the current recordpointer INumRecs to the next record in the recordset.
MoveFirst Enables the user to reposition the current recordpointer to the first record in the record set.
MoveLast Enables the user to reposition the current recordpointer to the last record in the record set.
MovePrev Enables the user to reposition the current recordpointer to the previous record in the record set.
MoveNext Enables the user to reposition the current recordpointer 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The LineItems Object
The LineItems object is used to interact with the underlying LineItemsdatabase table, in much the same way that the Accounts and Productsobjects 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 todetermine whether the currentrecord pointer has gone beyondthe beginning of the record set.
EOF short Returns a Boolean value used todetermine whether the currentrecord pointer has gone beyondthe end of the record set.
Item(longLineItemNumber)
ILineItem* Returns a pointer to theLineItem object identified byLineItemNumber, the line item’sunique number. The Itemproperty also allows the user toaccess the LineItem identifiedby the current record pointer, bycalling Item with -1.
Count long Returns the number of line itemsin the invoice.
Method Description
Add(ILineItemm*) Used to add the line item defined by the incomingLineItem object to the data source.
Update(ILineItem*)Used to modify an existing line item entry in thedata source.
Remove(longLineItemNumber)
Used to remove the line item identified byLineItemNumber, the line item’s unique number,from the data source.
Move(long INumRecs) Enables the user to reposition the current recordpointer INumRecs to the next record in the recordset.
MoveFirst Enables the user to reposition the current recordpointer to the first record in the record set.
MoveLast Enables the user to reposition the current recordpointer to the last record in the record set.
MovePrev Enables the user to reposition the current recordpointer to the previous record in the record set.
MoveNext Enables the user to reposition the current recordpointer 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 theorder entry object hierarchy, we’ll move on to the next step, which is toactually build the various entry and collection objects of the hierarchy! We’llstart 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 withtheir underlying database tables, which are accessed via ODBC. Each of thevarious objects of the hierarchy will be implemented as part of a singlein-process COM server (OrderEntry.DLL).
Building the Entry Objects
The basic design of the “entry” objects is simple: Each entry object maintainsa member variable for each property, which is then manipulated using variousGet/Put member functions. The Get functions are used to retrieve the valueof the member variable, while the Put functions are used to change the valueof 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 thevarious 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 alittle more complex because of their interaction with the underlying databasetables, which are accessed via ODBC. As each of these objects is developed inidentical 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 withan underlying database table, I think it makes sense to provide a quick anddirty overview of ODBC programming. For a more in-depth look at ODBCprogramming, you should pick up a copy of the Microsoft ODBC 3.0Programmer’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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
AN ODBC PROGRAMMING OVERVIEW
ODBC provides applications developers with a mechanism for offering database supportto their applications in a general, DataBase Management System (DBMS)-independentmanner. Instead of accessing a particular DBMS using its native protocol, applicationsdevelopers write their database manipulation routines using the ODBC API, which allowsthem to use any number of ODBC-compliant DBMSS. Architecturally, ODBC iscomprised of four components (see Figure 7-2):
• The Application is responsible for calling ODBC API functions to submit SQLstatements, retrieve the results of those statements, and react accordingly.
• The Driver Manager is responsible for loading the appropriate database driveron 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 aparticular 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 applicationis probably the easiest. The steps for building an application, or in this case an objecthierarchy, 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 handleand the current active connection handle. There is only one environment perapplication.
2. Allocate a new connection handle by calling SQLAllocConnect. Once aconnection handle has been created, you can establish an actual connection withthe data source by calling SQLConnect. An application can have multipleconnections to any number of data sources.
3. Establish a connection with the desired data source using SQLConnect and aconnection handle created using SQLAllocConnect.
4. Allocate a new statement handle by calling SQLAllocStmt and initialize itwith an SQL statement.
5. Use the statement handle created with SQLAllocStmt to execute your SQLstatement(s) and receive and process their results. SQL statements can be executedby calling either SQLExecDirect or SQLExecute. Use SQLExecDirect forSQL statements that you will only execute one time; otherwise, use SQLPrepareand SQLExecute.
6. Free each statement’s resources by calling SQLFreeStmt. Each successfulcall to SQLAllocStmt should have a matching SQLFreeStmt call.
7. Disconnect from the data source by calling SQLDisconnect. Each successfulcall to SQLConnect should have a matching SQLDisconnect call.
8. Free each connection’s resources by calling SQLFreeConnect. Eachsuccessful call to SQLAllocConnect should have a matchingSQLFreeConnect call.
9. Free the environment resources by calling SQLFreeEnv. Each successful callto SQLAlocEnv should have a matching SQLFreeEnv call.
While this is admittedly a highly oversimplified blueprint for building an ODBCapplication, it should serve the purpose of providing you with enough backgroundinformation for you to feel your way through the code presented throughout the rest ofthis chapter.
Before making any ODBC API calls, each application must allocate a new ODBCenvironment 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 attachesor detaches, so that we can allocate or free the application’s global ODBC environmenthandle 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 clientof the OrderEntry object hierarchy, let’s resume our discussion of how to build theAccounts collection object.
DATA ACCESS AND MANIPULATION PROPERTIES ANDMETHODS
We will begin our investigation of the Accounts object with its initialization. Theinitialization of the Accounts object occurs immediately after the object is first createdas 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 theLoadTypeInfo function that we created in Chapter 5.
• Allocating a connection handle using SQLAllocConnect and the environment handlethat was previously allocated as part of DllMain, the DLL entry point, and establishing aconnection to the OrderEntry data source using SQLConnect. Each connection handlemaintains information regarding a specific connection to the underlying data source. TheOrderEntry data source used by the OrderEntry object hierarchy should be configured as aSystem DSN, using the ODBC Data Source Administrator, which can be found in theWindows Control Panel. By configuring a data source as a System DSN, it will beavailable to all users of the machine, including Windows NT services. If you configureyour data source such that it requires an authorized user account and password, thisadditional information can be supplied along with the data source name in a later call toSQLConnect.
• Allocating statement handles using SQLAllocStmt for each of the five preparedstatements that will be used by the Accounts object. Each statement handle maintainsinformation regarding a specific SQL statement and associates it with a specificconnection.
• 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 initializationprocess 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 toaccomplish these objectives. We’ll begin by looking at the definitions of the various SQLstatements that will eventually be used to create prepared statements. As you look at the SQLstatement definitions, notice how the question mark (?) is used as a placeholder for values thatwill 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 aprepared statement. The next step is to allocate an ODBC statement handle and initialize it usingthe 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 functiononce all of the prepared statements are created. We will look into the MoveFirst method alittle later, when we discuss the Accounts object’s navigation properties and methods, but let’sfirst 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 followingcode shows how a client written in Visual Basic might use the Accounts object to add a newcustomer account to the data source. As you look at the source code, notice that the Numberproperty is not set when adding a new account. This is because the value for the Numberproperty is automatically generated by the data source, to prevent the possibility of two accountshaving the same account number:
'New accounts start off with zero balanceobjNewAccount.Balance = 0'New accounts start off with a $1200.00 limitobjNewAccount.Limit = 1200objNewAccount.Name = "Ken Lewis"objNewAccount.Sex = FobjNewAccount.Address = "9000 Essex"objNewAccount.City = "Chicago"objNewAccount.State = "IL"objNewAccount.Zip = "60617"objNewAccount.Phone = "(123) 456-7890"'New accounts start off in serviceobjNewAccount.InService = TRUE'Add the new account to the data sourceg_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 accountinformation. However, before the prepared insert statement can be executed, we must supplyvalues 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 ODBCSQLBindParameter function. The SQLBindParameter function uses C variables astransfer buffers to shuttle data into and out of SQL prepared statements. The definition of theSQLBindParameter 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 handleUWORD 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, orSQL_PARAM_OUTPUT.
SWORD fCType Input The C datatype of the parameter (seeTable 7-5).
SWORD fSqlType Input The SQL datatype of the parameter (seeTable 7-6).
UDWORD cbColDef Input For character and binary data, specifiesthe actual length of the rgbValuebuffer. For all other datatypes, this valueis ignored.
SWORD ibScale Input The maximum number of digits to theright of the decimal point. Ignored by alldatatypes except SQL_DECIMAL andSQL_NUMERIC.
PTR rgbValue Input/Output A pointer to a buffer containing the actualdata for the parameter.
SDWORD cbValueMax Input For character and binary data, specifiesthe maximum length of the rgbValuebuffer. For all other datatypes, this valueis ignored.
SDWORD FAR* pcbValue Input/Output A pointer to a buffer to receive the lengthof the character or binary data returned inrgbValue. For all other datatypes, thisvalue is ignored.
Table 7-14 C Datatypes Supported by SQLBindParameter
SQL_C_BINARY SQL_C_BIT SQL_C_CHARSQL_C_DATE SQL_C_TIME SQL_C_TIMESTAMPSQL_C_DEFAULT SQL_C_DOUBLE SQL_C_FLOATSQL_C_LONG SQL_C_SLONG SQL_C_ULONGSQL_C_SHORT SQL_C_SSHORT SQL_C_USHORTSQL_C_TINYINT SQL_C_STINYINT SQL_C_UTINYINT
Table 7-15 SQL Datatypes supported by SQLBindParameter
SQL_TINYINT SQL_SMALLINT SQL_BIGINTSQL_DATE SQL_TIME SQL_TIMESTAMPSQL_DECIMAL SQL_DOUBLE SQL_FLOATSQL_INTEGER SQL_NUMERIC SQL_REALSQL_BINARY SQL_VARBINARY SQL_LONGVARBINARYSQL_CHAR SQL_VARCHAR SQL_LONGVARCHARSQL_BIT
Once a C variable has been bound for each parameter, the prepared insert statement can beexecuted using SQLExecute:
//execute the prepared statement retcode = SQLExecute(m_hstmtInsert);
Finally, after the statement has been executed, the bound parameters are released by callingSQLFreeStmt 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Prepared SQL statements and bound parameters are also used to implement the Update andRemove methods as well as the Count property. The Update method exists to enable users tomake changes to existing account, product, and invoice entries:
'Update the user's account information to reflect a recent change of addressobjExistingAccount.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 sourceg_objAccounts.Update objExistingAccount
As you might imagine, implementation of the Update method is practically identical to that of theAdd method in Listing 7-1.
The Remove method of the Accounts and Products objects doesn’t actually remove an entryfrom the data source, but rather marks an entry as deactivated. The logic behind deactivating accountand product entries as opposed to deleting them is simple. Part of the process of displaying an invoiceis displaying the name of the purchasing customer, as well as a description of each purchasedproduct. However, if the customer’s account has been deleted, we have no way of knowing to whomthe invoice belongs. Likewise, if a product entry has been deleted, we have no way of knowing whatproducts were purchased on the invoice. In other words, if we were to delete customer accounts andproduct entries, we would have no way of knowing which customer purchased which products on aninvoice! The deactivation solution is just one of many possible solutions to this problem, and waschosen because it is the simplest. On the other hand, invoices themselves are actually deleted fromthe system. The Remove method of the Invoices object actually deletes invoice entries from theunderlying Invoices database table. However, deleting an invoice has no direct effect on either theAccounts 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 withimplementation 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 Accountsobject’s Item property. As you may recall, the Accounts object’s Item property can be used intwo different ways. In the first scenario, calling Item with the number of a specific account willcause Item to return an Account object complete with that account’s information. The followingcode 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 #333Set objAccount = g_objAccounts.Item(333)
To retrieve information regarding a specific customer account, we use the SQLBindParameterand SQLExecute functions to transfer the user-supplied account number to the prepared querystatement 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 generatedby the prepared query statement, we will use the SQLBindcol and SQLFetch functions. TheSQLBindcol function allows you to use variables as transfer buffers to retrieve data from specificcolumns of a record set, similar to the way that SQLBindParameter allows you to use variables toshuttle 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 handleUWORD 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 theactual data for the parameter.
SDWORD cbValueMax Input For character and binary data,specifies the maximum length of thergbValue buffer. For all otherdatatypes, this value is ignored.
SDWORD FAR* pcbValue Input/Output A pointer to a buffer to receive thelength of the character or binarydata returned in rgbValue. For allother datatypes, this value isignored.
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 callingSQLFreeStmt with the SQL_UNBIND option, and the record set is closed by callingSQLFreeStmt 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 andinitialized 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 accountidentified by the current record pointer, which is manipulated using the Move, MoveFirst,MoveLast, MovePrev, and MoveNext navigation methods. The following code snippet showshow a VB client might use the Item method in this second manner:
'Move to the first recordg_objAccounts.MoveFirstIf Not g_objAccounts.EOF Then 'Return the information about the first account Set objAccount = g_objAccounts.ItemEnd If
The key to the special behavior of the Item property is in its definition. As you look at the followingdefinition 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 andVARIANT * 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 usedwhenever the user doesn’t provide an argument for the parameter. In our implementation of the Itemproperty, we simply test the Key parameter for -1 to determine whether or not the user has supplied avalue 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 thefirst scenario, the Item property works in conjunction with the five navigation methods:MoveFirst, MoveLast, MovePrev, MoveNext, and Move to reposition the current recordpointer and retrieve customer account information. Internally, the navigation methods useSQLExtendedFetch to reposition the current record pointer as well as to transfer data toCAccounts member variables previously bound to record set columns using SQLBIndcol. Afterthe successful completion of a navigation method, you can retrieve information about the currentcustomer account by simply querying the Item property with no parameters. The Item propertyresponds by copying the data from the CAccounts member variables to local variables, which thenuses 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 navigationthrough 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
NAVIGATION PROPERTIES AND METHODS
As I described earlier, the Item property works in conjunction with the five navigationmethods: MoveFirst, MoveLast, MovePrev, MoveNext and Move to reposition thecurrent record pointer and retrieve customer account information. When an Accounts objectis first initialized, the MoveFirst method is called to position the current record pointer tothe first customer account in the record set. Our exploration of the MoveFirst methodbegins in CAccounts::Initialize with the definition of the navigationalprepared statement, which is used by all of the five navigation methods. Thenavigational statement generates a record set containing every record from the accountsdatabase 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 resourcesthat may have been previously allocated by an earlier call to MoveFirst, because adeveloper is free to call the MoveFirst method at any point in time. Once previouslyallocated resources have been freed, a statement handle is allocated using SQLAllocStmtand configured to create a keyset-driven record set using SQLSetStmtOption, after whichthe 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 areestablished 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 firstrecord of the navigational record set, and to retrieve the account information containedtherein. The last responsibility of the MoveFirst method is to determine whether or not thecurrent record pointer is before the beginning of the record set (BOF) or after the end of therecord set (EOF), and to adjust the member variables responsible for reporting theseconditions 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
MoveFirst is the most involved navigation method, because it is responsible for not onlyinitializing the transfer buffers for each column, but also for repositioning the current recordpointer and retrieving the appropriate data. However, the other four navigation methods useSQLExtendedFetch to reposition the current record pointer and update the contents of thebound 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 Accountsobject: 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 collectionobjects is implemented using these very same techniques. The OrderEntry object hierarchy
encapsulates not only the data access to the underlying ODBC data source, but alsofundamental business rules — for example, the concept of removing an account from service asopposed to physically deleting it from the data source. By encapsulating business rules, we canassure ourselves that every application using the OrderEntry object model will follow theappropriate business rules. Also, if we should decide to change our business rule logic, we canmake the change in one place (the object hierarchy) and have it affect existing applicationswithout 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Summary
In this chapter, you learned:
• That one of the best ways to ensure interoperability and promoteobject reuse between various COM objects is to develop an objecthierarchy.
• That object hierarchies should encapsulate the functionality necessaryfor one or more applications to perform their fundamental tasks.
• That by encapsulating functionality common to multiple applicationsinto a single object hierarchy, corporations can spread a good portion oftheir development and maintenance costs across the various applicationsthat are built on top of the hierarchy.
• How to build COM objects that encapsulate data access to an ODBCdata source.
Now that we have built the OrderEntry object hierarchy, the rest of this book isdedicated to showing you how to build applications with different architecturalstyles. So that you’ll be better able to compare and contrast these differentarchitectures, we will continue to use the order entry theme. In the nextchapter, we will use the OrderEntry object hierarchy to build an order-entryapplication 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Chapter 8Building the Client/Server Order-EntryApplicationIN 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 todevelop a client/server version of an order entry application
• The benefits and limitations of the client/server applicationarchitecture
IN THE LAST chapter you learned about object hierarchies. You learned thatwhile COM objects are inherently interoperable, one of the best ways topromote object reuse is to develop a group of interoperable COM objectsdesigned to solve a specific purpose, as part of a single object hierarchy. Youwere then able to design and build the OrderEntry object hierarchy, which ismade up of entry objects and collection objects. Entry objects are used toconvey relatively static information about an individual entry in the datasource, while collection objects are used to manage and represent multiple datasource entries of a particular type. In the case of the OrderEntry objecthierarchy, the Account, Product, Invoice, and LineItem objects are all entryobjects, while the Account, Product, Invoice, and LineItem objects are allcollection objects (see Figure 8-1). The collection objects use ODBC tointeract with an arbitrary backend data source, which in this case happens to bethe OrderEntry.mdb Access database. In this chapter, you use the OrderEntryobject hierarchy as the foundation for a simple order-entry application. As youdevelop the order-entry application, you will find that the object hierarchy
encapsulates most of the basic functionality required for the order-entryapplication to perform its duties. By encapsulating its basic functionality into asingle object hierarchy, we are free to implement the actual order-entryapplication using any number of application architectures. In this chapter, wecreate a client/server version of the order-entry application, and in Chapter 9we create a Web version of the order-entry application. However, bothversions 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 amail-order company to manage customer accounts that are in the form ofspecial credit cards, product inventory, and customer purchase invoices.Customers use special mail-order credit cards to purchase products from thecompany’s catalog. Telephone operators who work for the mail-ordercompany complete purchase invoices whenever customers call in to place anorder. The functionality offered by the order-entry application can be dividedinto three basic sections: customer account management, product inventorymanagement, and customer invoice management. Customer accountmanagement 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 newproduct entries, modifying existing product entries, removing products fromservice so that they cannot be purchased, and displaying a list of all theavailable product entries in the system. Customer invoice managementfunctions include adding new invoice entries, modifying existing invoiceentries, deleting invoice entries, and displaying a list of all the invoice entriesin the system. Table 8-1 is a quick reference guide detailing the functionalityoffered 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 customeraccount entry to the system.
Modify Existing Account Used to change some aspect of anexisting customer account entry.
Deactivate Account Used to remove a customeraccount from service.
List Accounts Used to present a list of all thecustomer account entries in thesystem.
Products New Product Used to add a new product entryto the system.
Modify Existing Product Used to change some aspect of anexisting product entry.
Deactivate Product Used to remove a product fromservice.
List Product Used to present a list of all theproduct entries in the system.
Invoices New Invoice Used to add a new invoice entryto the system.
Modify Existing Invoice Used to change some aspect of anexisting invoice entry.
Delete Invoice Used to remove an invoice entryfrom the system.
List Invoices Used to present a list of all theinvoice entries in the system.
Understanding the Client/Server ApplicationArchitecture
The order-entry application that we build in this chapter follows a client/serverarchitecture in which the client-side of the order-entry application isresponsible for gathering information from the user, formatting it, andforwarding it to the server for processing. The client is also responsible forreceiving processed information from the server, formatting it, and presentingit to the user. The server is responsible for receiving information from theclient, processing it, and returning the results back to the client.
Each client application maintains one global reference to an individualAccounts object, Products object, and Invoices object, which are usedwhenever the application needs to interact with the underlying data source foradding, 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Developing the Client/Server Application
Development of the client/server version of the order-entry system begins by creating theglobal references to the Accounts, Products, and Invoices objects that are used tointeract with the underlying data source. They are created as part of the MDIForm_Loadevent:
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 customeraccounts, product inventory, and customer purchase invoices, which are all very similar. Eachallows you to add new entries, as well as update, remove, and list existing entries. Sincemanaging accounts is similar to managing products and invoices, we will focus our discussionon the implementation of the account management functions with the understanding that thesame techniques used to implement the account management functions are used to implementthe product and invoice management functions. However, during our investigation, I willpoint 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 musthave an active account. Telephone operators use a window similar to the one in Figure 8-3 toadd new customer accounts. Notice how the purchasing privileges of an account can besuspended 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. TheNew Account window is displayed with blank entry fields, allowing the user to enterinformation regarding the new customer account into each field. Once the new accountinformation has been entered, the user presses the OK button to submit the new information tothe data source. Behind the scenes, when the user presses the OK button, a blank Accountobject is created and initialized using the data supplied in the various fields of the NewAccount window. Once the information has been transferred from the various fields to thenewly created Account object, the object is added to the data source using the globalAccounts object reference obtained when the application was first started. The source codefor this process can be seen in Listing 8-1, the OK button’s Click event. As you look at thelisting, notice that information for an account number is not supplied. This is because the datasource automatically generates an account number for each new entry to prevent thepossibility 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 MeEnd Sub
Retrieving Existing Accounts
The order-entry application has two management functions — Modify Account andDeactivate 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 toretrieve a specific customer’s account information, the easiest of which is to simply locate theaccount using the account number (see Figure 8-4).
Figure 8-4 The easiest way to retrieve a customer’s account information is to simply searchfor the account using the account number.
The LocateAccount function of the SearchForm exists to locate and retrieve a specificcustomer account using only the customer’s account number. The LocateAccountfunction displays the SearchForm and waits for the user to press either the OK button orthe Cancel button. The LocateAccount function returns TRUE if the user presses the OKbutton; otherwise, LocateAccount returns FALSE. Clicking on the OK button causes theSearchForm to disappear, and the account number that was entered into the txtNumberentry field is retrieved and used as a parameter in the retrieval of the global Accountsobject’s Item property. The Item property is responsible for actually locating and retrievingthe specified customer’s account information, which is ultimately returned in theLocateAccount function’s retAccount parameter. However, if the user presses theCancel button, or if the desired customer’s account cannot be located, LocateAccountreturns 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 MeEnd Function
Locating an account using the account number only works if the customer happens to have hisor her account number handy. If the customer doesn’t know his or her account number, theaccount 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
The FillListboxWithAccounts subroutine of the ListForm is responsible for displayingthe name of every customer in a listbox. FillListboxWithAccounts uses the MoveFirstnavigation method of the global Accounts object to reposition the current record pointer to thefirst 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 theglobal Accounts object to retrieve each account entry from the data source until there are nomore, an event signaled by the global Accounts object’s EOF property. As each customer’sname is added to the contents of the listbox, the customer’s account number is added to theItemData array of the listbox. By storing each account number in the ItemData array, detailedinformation regarding the currently selected listbox entry can be obtained immediately by simplyretrieving the account number of the selected listbox entry and supplying it to the Item propertyof 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 objectis repositioned to the next customer account in the data source using the object’s MoveNextmethod. 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 LoopEnd Sub
Updating Existing Accounts
Telephone operators use the Modify Account window to update information regarding existingcustomer accounts, in much the same way that they use the New Account window to add newcustomer accounts. Note that instead of displaying the New Account window with blank entryfields, the entry fields of the window are filled with information about the customer account beingmodified (see Figure 8-6).
Figure 8-6 The Modify Account window is used to update information regarding existingcustomer 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 anAccount object, it is sent to the AccountInfoForm’s ModifyAccount subroutine, whichformats and displays the account information in the appropriate fields of the Modify Accountwindow.
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.ShowEnd Sub
Once the information is displayed, it can be modified and propagated to the underlying data sourceby simply clicking on the OK button. The source code for the Click event of theAccountInfoForm’s OK button has logic to determine whether a new account is being createdor an existing account is being updated. If an existing account is being updated, the data suppliedin the various fields of the Modify Account window is transferred to the Account object beingupdated. 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. Thesource code for the Click event of the AccountInfoForm’s OK button can be seen in Listing8-1.
Removing Existing Accounts
Removing a customer’s account from service is probably the easiest management function toimplement. The first step is to locate the desired customer’s account using one of the two methodsdescribed in “Retrieving Existing Entries.” Once the desired account has been located, removing itfrom service is simply a matter of calling the Remove method of the global Accounts objectwith 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Adding and Updating Invoices
Ultimately, the purpose of the order-entry application is to allow telephone operators to completeonline 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, andthe 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 orproducts, primarily because each invoice maintains a list of line items, each consisting of aproduct 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 beingpurchased.
Adding invoices to the data source begins with the familiar pattern of displaying a window withblank entry fields and allowing the user to enter information. However, processing and storingthe data that’s provided in the various entry fields is slightly different for Invoice objects thanfor 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 ofproducts to be purchased, along with their respective quantities. Adding an invoice actuallyrequires two steps. In the first step, information about the invoice itself, such as its date ofcreation and the purchasing customer’s account number, is saved to the data source. Once theinvoice’s information has been saved using the global Invoices object, the second step is toadd the individual line items of the invoice. However, to prevent adding redundant line itementries to the data source, the order-entry application simply deletes all of the invoice’s existingline 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 datasource, other more sophisticated techniques can be used to minimize the total number of entriesthat must be deleted and then re-added. The source code responsible for adding and updatingindividual invoices can be seen in Listing 8-2, the Click event for the InvoiceInfoForm’s OKbutton.
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 systemservices such as the ODBC API as well as server-side resources such as DBMSs to createpowerful yet flexible applications. However, the benefits of client/server application architecturedo not come for free. One of the primary drawbacks of client/server design is that each clientmachine’s system services must be properly configured in order for the application to workproperly. In the case of the order-entry application, each client desktop must have the appropriateODBC 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-entryapplication on hundreds or thousands of desktops. Other marks against client/server applicationsare in the areas of deployment and support. Imagine that it’s your responsibility to deploy andsupport the order-entry application. As you make modifications and improvements, how wouldyou 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 ofthe 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 theseproblems, and more. That application architecture is the Web application architecture, and it isthe 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 ofthe application development process.
• Client/server applications allow you to make effective use of both client-side andserver-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 ofthe 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Chapter 9Building the Web Order-EntryApplicationIN THIS CHAPTER
• The architecture of the Web application
• How Active Server Pages (ASP) enhances the Web applicationarchitecture
• How to reuse the OrderEntry object hierarchy developed in Chapter 7to develop a Web version of the order-entry application developed inChapter 8
• The benefits and limitations of the Web application architecture
IN CHAPTER 8 you created a client/server order-entry application that allowsoperators to manage product inventory, individual customer accounts, andcustomer purchase invoices. The application is built on top of an objecthierarchy, which encapsulates the functionality necessary for the order-entryapplication to perform its required tasks of managing accounts, products, andinvoices. The object hierarchy itself is built on top of ODBC, which allows thehierarchy, and, ultimately the order-entry application, to beDBMS-independent. As you saw however, the client/server version of theorder entry application suffers from several significant limitations:
• ODBC must be properly installed and configured on each clientmachine.
• The order-entry application must be deployed to each client machine.
• Each client’s copy of the order-entry application must be updated asnewer versions are made available.
Recently, a new Web application, or weblication, architecture has presenteditself as a more effective alternative to the traditional client/server architecture.In this chapter, we explore the Web application architecture by building a Webversion of the order-entry application created in Chapter 8.
Understanding the Web Application Architecture
The Web application architecture has increased in popularity for manyreasons, which include its ability to address many of the shortcomings of theclient/server architecture. Ironically, the Web application architecture isactually a variation of the client/server architecture and is often referred to asthin client/server. One immediate distinction between Web applications andother client/server architectures is that the infrastructure of a Web applicationis, at least at this point, very well-defined. The anatomy of a Web applicationconsists of a Web server, the HyperText Transfer Protocol (HTTP), theHyperText 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 Webserver. Each HTML document can contain text, images and other forms ofmultimedia, executable or interpretable code, and links to other HTMLdocuments. The Web server delivers the HTML page using HTTP as itstransfer protocol. Once the document arrives at the client machine, the browserinterprets, formats, and displays the document’s contents to the user accordingto the HTML tags contained therein. At a simplistic level, Web publisherscreate static HTML documents and place them on the Web server, where theycan be served to client Web browsers. HTML documents of this type areconsidered static in the sense that they are created ahead of time, as opposed tobeing created dynamically, on-the-fly, in response to some outside influence.
While static HTML pages are extremely effective as pure communicationsmechanisms, they don’t lend themselves well to application development,primarily because applications require the ability to generate outputdynamically, in response to user or system demands. Microsoft’s ActiveServer Pages (ASP) is a server-side technology that works in conjunction withMicrosoft’s Internet Information Server (IIS) to generate HTML documentsdynamically in response to user request. Developers mix HTML andapplication script logic together in ASP files to control the dynamic HTML
document creation process. ASP scripts can be written in a wide variety ofscripting languages, including JavaScript (JScript) and a subset of Visual Basicknown as Visual Basic Scripting Edition (VBScript). If a client requests astatic HTML page, IIS fetches it and sends it to the client, just as you wouldexpect. However, if a client requests an ASP page, the ASP engine isautomatically invoked and proceeds to fetch the ASP page and process it fromtop 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 HTMLdocument is sent to the client just like any other HTML document, and theclient 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 HTMLdocuments in response to user requests.
In addition to providing scripting support, ASP also allows developers to useCOM components that support the IDispatch interface, which, as youlearned in Chapter 5, is the interface responsible for facilitating Automation.ASP’s combined support for various scripting languages and COMcomponents makes it an ideal platform for building Web applications. Webapplications created using ASP are referred to as ASP applications. An ASPapplication is essentially a collection of ASP files located under a commondirectory. An ASP application is allowed to have one global.asa file, which iswhere 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 informationabout the state of their current connection. In other words, each connection between a clientand the server is treated as a brand-new connection. Programming in such a statelessenvironment is extremely tedious and time-consuming. Thankfully, ASP maintains stateinformation for us, and presents us with what appears to be a stated environment. The firsttime that a client browser accesses any individual page of an ASP application, ASP creates anew server-side context called a Session for that particular client. Developers can use theSession context to save information on a client-by-client basis. Information that has beenstored in a Session may only be accessed from that particular session context. Informationthat 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 theapplication’s ASP files, or after a predetermined period of client nonactivity. The defaulttime-out period for a Session is initially set to 90 seconds, but can be easily reconfigured bythe ASP administrator. Whenever a new Session is created, ASP fires theSession_OnStart event. Likewise, whenever a Session is terminated, ASP fires theSession_OnEnd event. Before the very first client Session is created, ASP fires theApplication_OnStart event, and when the very last client Session is terminated, ASPfires the Application_OnEnd event. Any code that you write in response to these eventsmust be contained in the global.asa file.
Web applications present an interesting alternative to traditional client/server applications, thelimitations of which are discussed at the end of Chapter 8. Since the pages that ultimatelymake up the clients are stored on the server, the client is forced to go to the server for eachnew page. By maintaining a single version of the application on the Web server, clients areguaranteed to be running the latest version of the application. So as you can see, Webapplications provide an automatic solution to the application deployment problem. Lastly,more sophisticated Web applications are capable of automatically downloading and installingadditional resources to the client machine as necessary. (We’ll see an example of this in thenext 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 itshould 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 thelearning curve will be in terms of staff retraining. However, at the same time, you want theWeb version of the application to behave like other Web applications and pages so that youcan 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 isalmost identical to that of the client/server version, with telephone operators entering newaccount information into the various fields of the account information page and submittingthem 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 areunable to add the new account information to the data source directly. Web clients interactwith the data source by requesting ASP files that encapsulate the interaction with the datasource. For example, the user interface (UI) used by Web clients to add new customeraccounts is actually an HTML form comprised of multiple text-entry fields and a single OKbutton. The static NewAccount.htm page generates the UI that is used by Web clients toreceive new customer account information (see Figure 9-3a). Whereas an HTML page canhave multiple forms, each form can only be associated with a single action, which is theUniform 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 theSaveAccountLogic.asp file, which happens to be an ASP file. When the user clicks on theOK button, the browser requests the SaveAccountLogic.asp file from the Web server, whichresponds by invoking the ASP engine, which then loads the SaveAccountLogic.asp file andprocesses it. This two-phase data-access technique is used whenever the Web client needs tointeract with the data source. As its name suggests, SaveAccountLogic.asp contains the codelogic ultimately responsible for saving customer account information to the data source. TheSaveAccountLogic.asp page begins with a piece of code that you will see quite oftenthroughout the Web order-entry application. The following code snippet attempts to obtain areference to a Session-level Accounts object, which has been stored in order to minimizethe 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 Accountsobject 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Next, the page determines whether it has been called to add a new customer account or toupdate an existing customer’s account information. To determine its purpose, the pageretrieves the incoming AccountNumber parameter and attempts to retrieve the accountidentified 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 anadd, we create a blank Account object, call SetProperties to initialize its propertiesusing the data from the form variables, and use the Session-level Accounts object referenceto add the new account to the data source. If the caller is performing an update, we callSetProperties to modify the existing account, and use the Session-level Accountsobject reference to propagate the changes to the underlying data source. The source code forthis process can be seen in Listing 9-1.
Listing 9-1. Code from the SaveAccountLogic.asp ASP page that is used to add new accountinformation 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. Asyou saw, the client/server version of the order-entry application has functions that areresponsible for not only identifying accounts, but also for locating and retrieving them. Oncethe account is located and retrieved, the client/server version of the order-entry applicationcan pass the retrieved account information to another function for either updating orremoving. However, the nature of Web application architectures, specifically their inability tomaintain 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-entryapplication is capable of displaying UIs similar to those used by the client/server version foridentifying, locating, and retrieving customer account information (see Figures 9-4a and 9-4band 9-5a and 9-5b.)
Figure 9-4a The Web version of the order-entry application provides a UI for identifying anaccount 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 anaccount 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 alistbox 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, theListAccounts.asp file moves to the first record of the data source and iterates through eachrecord, adding each account name and account number to the listbox, which is generatedusing 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 andclicks on the Details button. When the user clicks on the Details button, the account numberof the selected account is sent to the ModifyAccount.asp file, which then locates, retrieves,and displays the account identified by the incoming AccountNumber parameter forupdating.
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 theclient/server version of the order-entry application provides two different mechanisms for locatingand retrieving existing customer accounts, the Web version of the order-entry application providestwo different mechanisms for identifying customer accounts. The Web order-entry applicationdoesn’t provide a single mechanism for actually locating and retrieving existing customeraccounts. This is a subtle but important difference between the implementations of the twoversions. For example, the SearchForm.LocateAccount function of the client/server order-entryapplication actually returns an Account object in one of its parameters. The returned Accountobject can then be updated or removed from service. While the Web order-entry applicationprovides an HTML form-based UI similar to that produced by calling theSearchForm.LocateAccount function of the client/server order-entry application, the formonly returns an account number. The ASP file that is the action of the form is ultimatelyresponsible for locating and retrieving the customer account information based on an accountnumber parameter. Once an account is identified using one of the two account identificationtechniques described in “Identifying Existing Accounts,” the Web order-entry application usestwo additional ASP files to update an existing account: ModifyAccount.asp, which is used tolocate, retrieve, and display the desired customer’s account information (see Figure 9-6) andSaveAccountLogic.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 anexisting customer’s account information.
ModifyAccount.asp begins with the process that was used in the SaveAccountLogic.asp file toobtain a reference to a Session-level Accounts object, after which ModifyAccount.asp uses anincoming AccountNumber parameter along with the Session-level Accounts object to locate
and retrieve the desired customer account information. If the account cannot be located, theModifyAccount.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 theuser with an HTML page.
If the account is located, its information is retrieved and used to fill the individual entry fields ofthe HTML form-based UI (see Listing 9-2).
Listing 9-2. Code from the ModifyAccount.asp file that is used to locate and retrieve customeraccount 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 twosupported account identification methods previously described. However, once an existingcustomer account has been identified, the DeactivateAccountLogic.asp file is used to actuallyremove the account from service. The DeactivateAccountLogic.asp file begins with the samelogic as the SaveAccountLogic.asp and ModifyAccount.asp files, which exist to obtain areference to a Session-level Accounts object. The Accounts object is then used to locate andretrieve the existing customer account identified by the incoming AccountNumber parameter. Ifthe account cannot be located, the DeactivateAccountLogic.asp will generate an HTML pagesimilar to that in Figure 9-6. If the account is located, its information is retrieved and used toremove the account from service (see Listing 9-3).
Listing 9-3. Code from the DeactivateAccountLogic.asp file that is used to locate, retrieve, andremove 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 problemsplaguing 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 fundamentalproblem with the basic Web application architecture is that the Web application is only able toaccess server-side resources. This also means that developers must use an entirely new, unfamiliarprogramming model to develop Web applications. For example, the client/server version of theorder-entry application simply maintains global references to the various objects that provideaccess to the underlying data source, using them whenever it needs to interact with the datasource. However, since the Web version of the application doesn’t have access to client-sidesystem resources, specifically ODBC, this technique doesn’t work. Instead, the Web version of theorder-entry application must take a two-step approach, in which data is gathered using one HTMLpage, 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 aWeb server, HTTP, HTML, and a Web browser.
• By encapsulating an application’s basic functionality inside an object hierarchy, you havethe freedom and flexibility to develop an application using any of today’s popularapplication architectures.
• Web applications can be used to solve many of the basic deployment, configuration, andsupport problems associated with client/server applications.
• Web applications introduce their own set of problems, including the need to learn aslightly different programming model that often only allows access to server-side systemresources.
In the final chapter, we create a slightly different version of the order-entry application based onthe Web application architecture developed in this chapter. The new version of the order-entryapplication 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Chapter 10Using DCOMIN THIS CHAPTER
• How DCOM extends the COM programming model beyond the boundariesof a single machine
• The two forms of security employed by DCOM: activation security and callsecurity
• The various levels of authentication and impersonation supported by DCOM
• How to use DCOMCNFG to provide both default system-wide andapplication-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/serverapplication architecture and the Web application architecture
DCOM CAN BE defined simply as the extension of the COM programming modelbeyond the boundaries of one physical machine. DCOM allows COM clients tomanipulate COM objects located on physically separate machines through whatamounts to a remote procedure call. In fact, DCOM is based on MS-RPC,Microsoft’s implementation of the Open Software Foundation’s (OSF) DistributedComputing Environment (DCE) Remote Procedure Call (RPC) system. By buildingon top of RPC, DCOM is shielded from the underlying network protocol, and is thuscapable 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 allcombinations of connectivity between in-process and out-of-process clients andremote servers. It is able to support remote in-process servers by loading them into aspecial surrogate process designed specifically for this purpose. The ability to
instantiate or connect to a remote COM object on a different machine raises somevery 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 accessprivileges. However, with the advent of DCOM, users are able to create objects onremote machines and gain access to the remote machine’s resources. To preventapplications 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 particularCOM server, and it is totally independent of call security, which regulates who isauthorized to access the various interface functions of a particular COM object. Sincethese two methods of security work independently of each other, it is possible for aclient 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 functionsprovided by the object itself.
Before a user can be granted or denied authorization to perform a specific task, he orshe must be authenticated. Authentication is the process of verifying that a user iswho they claim to be. Table 10-1 illustrates the various levels of authenticationsupported 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 andRPC_C_AUTHN_LEVEL_CONNECT levels of authentication.
Table 10-1 Authentication Levels Supported by DCOM
Name Level Description
RPC_C_AUTHN_LEVEL_NONE 1No authentication isperformed.
RPC_C_AUTHN_LEVEL_CONNECT 2 Used by connection-orientedprotocols to performauthentication whenever aclient establishes a connectionwith the server. Connectionlessprotocols useRPC_C_AUTHN_LEVEL_PKTinstead.
RPC_C_AUTHN_LEVEL_CALL 3 Used by connection-orientedprotocols to performauthentication whenever theserver receives an RPC call.Connectionless protocols useRPC_C_AUTHN_LEVEL_PKTinstead.
RPC_C_AUTHN_LEVEL_PKT 4 Authentication of all data isperformed on a per-packetbasis.
RPC_C_AUTHN_LEVEL_PKT_INTEGRITY 5 Same as above, with addedvalidation that no data hasbeen modified.
RPC_C_AUTHN_LEVEL_PKT_PRIVACY 6All of the above, plusencryption.
Once a user has been validated, DCOM allows you to decide the impersonation levelor security context of each call to the object, which ultimately determines theresources accessible by the object. For example, if the call is executed under thecaller’s security context, then the object will impersonate the caller and only haveaccess to those system resources accessible by the caller. Table 10-2 illustrates thevarious 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 theserver, preventing the serverfrom obtaining identificationinformation or impersonating theclient. (Currently unsupported)
RPC_C_IMPLEVEL_IDENTIFY 2 The server can impersonate theclient to ascertain accessprivileges, but not to actuallyaccess resources.
RPC_C_lMP_LEVEL_IMPERSONATE 3 The server can impersonate theclient to access resources.
RPC_C_IMP_LEVEL_DELEGATE 4 The server can impersonate theclient to not only accessresources, but also to make callsto other servers. (Currentlyunsupported)
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Ultimately, DCOM gains access to the security services of the underlyingoperating system via the Security Support Provider Interface (SSPI), whichallows DCOM to use a variety of different security systems, such as WindowsNT’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 specificallyfor the programmatic control and configuration of DCOM. (See Appendix Afor a complete list.) However, this does nothing to accommodate existingCOM applications. To accommodate existing COM applications, Microsoftdeveloped DCOMCNFG.
Using DCOMCNFG
DCOMCNFG ships as part of DCOM and provides a point-and-click way toconfigure it. DCOMCNFG is ultimately a way for all existing COM clientsand 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 DCOMconfiguration (see Figure 10-1).
Figure 10-1 DCOMCNFG not only enables you to configure DCOM forindividual COM objects, but also provides a system-wide default DCOMconfiguration.
Providing System-Wide Configuration Information
As Figure 10-1 illustrates, DCOMCNFG has two tabs for providingsystem-wide configuration information: Default Properties and DefaultSecurity. As you can see in Figure 10-2, the Default Properties tab allows youto:
• Enable/disable DCOM for the entire computer.Manipulates the EnableDCOM value under theHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registrykey to control whether or not client applications can launch or connectto any installed servers or classes. A Y means DCOM is enabled; an Nmeans it’s not.
• Specify the default authentication level.Manipulates the LegacyAuthenticationlevel value under theHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registrykey to control which level of authentication to use in the event that anobject doesn’t supply an authentication level of its own. See Table 10-1for legal values.
• Specify the default impersonation level.Manipulates the Legacy Impersonationlevel value under theHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registrykey to control which level of impersonation to use in the event that anobject doesn’t supply an impersonation level of its own. See Table 10-2for legal values.
• Request additional security for client calls to an object’s AddRefand Release methods. Manipulates theLegacySecureReferences value under theHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registrykey to control whether or not security should be applied to client callson an object’s AddRef and Release methods. A Y means addsecurity; an N means don’t.
Figure 10-2 System-wide DCOM configuration information is accessed fromthe Default Properties tab of DCOMCNFG.
The other DCOMCNFG tab useful for providing system-wide configurationinformation is the Default Security tab (see Figure 10-3). The Default Securitytab allows you to:
• Control which users are allowed access to registered COMobjects. Stores access control information under theDefaultAccessPermission value of theHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registry
key for those users authorized to access any of the system’s registeredCOM objects.
• Control which users are allowed to launch a COM server. Storesaccess control information under the DefaultLaunchPermissionvalue of theHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole registrykey for those users authorized to launch any of the system’s registeredCOM servers. (NT only)
• Control which users are allowed to modify OLE classconfiguration information in the system registry. Manipulates thesystem registry’s Access Control List (ACL). (NT only)
Figure 10-3 System-wide DCOM security information is accessed from theDefault Security tab of DCOMCNFG.
Because these values affect the entire machine, your applications typically willnever need to modify these values, which should only be modified by thesystem’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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
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 securityinformation of its own. However, you can also use DCOMCNFG to configure object-specificsecurity as well as other valuable object-specific information. On DCOMCNFG’sApplications tab, you will notice the names of the various COM servers that have registeredthemselves as being available for remote invocation (see Figure 10-1). COM servers exposethemselves 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 COMobject.
• servername is either the DNS or UNC name of the machine where the COM servershould be launched.
Following is the other registry setting required of COM servers that wish to exposethemselves 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 torequest the default (DllHost.exe) surrogate.
To configure the security and other application-specific information for a specific COMserver, highlight the server in the Applications tab of DCOMCNFG and click on theProperties button. DCOMCNFG will respond by displaying the current settings for theapplication (see Figure 10-4).
Figure 10-4 COM server-specific DCOM settings can be configured by highlighting theserver 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 hasapplication-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 executionlocation, launch, access, and configuration security, as well as impersonation identity.
While DCOMCNFG provides a way for existing applications to use DCOM without writing asingle line of code, these applications may achieve less than optimal performance, as theysimply were not designed to perform in a distributed manner. Due to latencies introduced bythe network, distributed applications must take special precautions to minimize the totalnumber of trips back and forth across the network required to execute a remote procedure andreturn a result, otherwise known as network round-trips (see Figure 10-6).
Figure 10-6 Each remote procedure call executed via DCOM requires one networkround-trip - one leg to the server that actually executes the call and one leg back to the clientwith the results of the call.
To understand the negative impact of too many network round-trips, consider the followingcode snippet, typical of a legacy VB application that retrieves each property from an Accountobject 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.NametxtSex.Text = localAccount.SextxtAddress.Text = localAccount.AddresstxtCity.Text = localAccount.CitytxtState.Text = localAccount.StatetxtZip.Text = localAccount.ZiptxtPhone.Text = localAccount.PhonechkInService.Value = Abs(localAccount.InService)
If localAccount is a remote object being accessed via DCOM, performance will be lessthan optimal to say the least, as the above code requires eleven trips back and forth across thenetwork, one for each property. Ideally, the Account object referenced in localAccountwould support a single method that could be used to retrieve all of the object’s properties atthe 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 retrievingseveral individual properties by implementing a single method that returns multiple propertyvalues.
Likewise, the localAccount Account object would support a single method, perhapswith optional named parameters, that would allow you to set multiple properties at the sametime:
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Another area where performance can be easily improved is in the retrieval of many differentinterface pointers. For example, imagine that you need to retrieve several different interfacepointers from a particular remote object. Each time you call QueryInterface, you arecharged one network round-trip. Among the special modifications that DCOM has added to theunderlying COM system services is the ability to query for multiple interfaces with a singlecall, using the IMultiQI interface. IMultiQI is a special interface that has been added tothe standard COM proxy, which means that you never have to actually implement it! All youhave to do to obtain multiple interfaces is call QueryInterface to obtain an IMultiQIinterface pointer, then call IMultiQI::QueryMultipleInterfaces with theappropriate arguments. How ’bout that! The function prototype forQueryMultipleInterfaces 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 onentry.
• hr is the result of the remote QueryInterface call used to actually retrieve theinterface pointer. Must be zero on entry.
Upon the successful completion of QueryMultipleInterfaces, each MULTI_QIstructure in the array will contain an interface pointer value (pItf) and a return value (hr) thatcan 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 DCOMprogrammatically, as opposed to using DCOMCNFG or mucking with the system registry. Youcan 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’ssee how we can use DCOM to improve the architectures of both the client/server and Webversions 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 OrderEntryobject hierarchy. Then, in Chapter 8, we used that object hierarchy to develop a client/serverorder-entry application. The client/server architecture afforded us the ability to use client-sidesystem resources, such as the ODBC API, as well as server-side resources, such as theODBC-compliant DBMS itself. However, we also discovered in Chapter 8 that the client/serverversion of the order-entry application has several significant limitations:
• It is time-consuming to properly install and configure client-side resources, such asODBC, 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 applicationacross every client machine.
While using DCOM won’t make it any easier to deploy the client side of the application to eachclient machine, and it won’t make it any easier to synchronize a single version of theclient/server application across every client machine, it can eliminate the need to install andconfigure ODBC on each client machine. If you recall, the OrderEntry object hierarchy iscomprised of two different types of objects: entry objects, which are used to maintain fairlystatic information; and collection objects, which are used to interact with the underlying ODBCdata source. It is the collection objects that ultimately require each client machine to haveODBC properly installed and configured. However, by moving the collection objects to theserver, and accessing them remotely from the client using DCOM, we eliminate the need toinstall and configure ODBC on each client machine. We then deploy the entry objects to eachclient machine as part of the order-entry weblication itself. Finally, we add theGetProperties and SetProperties methods described earlier to each entry object tohelp minimize any unnecessary network round-trips that may occur whenever a collectionobject needs to access multiple properties from a remote entry object. Following is the IDLdefinition 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Listing 10-1 shows the implementation of the Account object’s new GetProperties andSetProperties methods, which are used to minimize the amount of network round-tripsnecessary to get or set multiple properties of a remote Account object. As you look at theimplementation, 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 thesystem registry such that the execution location of each collection object points to the appropriateremote server machine. The remote server machine is responsible for having the new serverportion 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 theItem 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, atbest the collection objects can only return a remote reference to the client, which would force theclient to make unnecessary network round-trips whenever it needed to access the object. The othersolution is for the client to create a local blank object, then pass it as a remote reference to theItem property. The Item property could then use the SetProperties method of the remoteobject to define all of the object’s properties in a single network round-trip, and the client wouldmaintain a local reference to the object. Because the Item property no longer returns an interfacepointer, there is no need for it to be a property, so we’ll convert it to a method. Following is thenew 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Listing 10-2 shows the new implementation of the Accounts object’s Item method. Notice howthe remote object’s SetProperties method is used to minimize the total number of networkround-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 ofboth statically generated HTML pages and dynamically generated HTML pages. The dynamicallygenerated HTML pages are created using Microsoft’s Active Server Pages (ASP). Each ASP ismade up of a combination of HTML and application script logic. While ASP supports a variety ofscripting 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 thelimitations 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 developweblications.
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
Ideally, we would like to develop the Web version of the order-entry application using theCOM programming model, in which the application’s user interface is created from variousvisual objects that execute code in response to various events supported by the individualvisual objects. Microsoft Internet Explorer (IE) 3.0 provides us with exactly this level ofsupport, 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 theHTML file itself, enclosed within <%...%> tags; similar to the way in which server-sidescripts are included as part of an ASP file. When the browser loads an HTML file, it processesthe file line-by-line from the top to the bottom, locating and executing the various lines ofscript enclosed within the <%...%> tags. In addition, IE 3.0 supports many differentclient-side scripting languages, including VBScript, JScript, Perl, and Rexx. IE 3.0 is includedon the companion CD-ROM if you don’t already have IE 3.0 or greater installed on yoursystem.
We will use VBScript because of its close resemblance to VB, which we used to create theclient/server version of the order-entry application. By combining IE 3.0’s ability to executeclient-side scripts and access client-side system resources using the COM programmingmodel with DCOM’s ability to remotely access server-side resources in a secure manner, weget 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 bothclient-side and server-side system resources! Let’s look at how we can apply this new hybridapplication 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 toaccess server-side system resources. However, this means that the client must issue requestsfor new HTML pages in order to communicate with ASP. For example, the user interfacegenerated by NewAccount.htm relies on SaveAccountLogic.asp to provide the processinglogic necessary to actually add a new customer account to the underlying data source (seeFigure 10-8).
Figure 10-8 Currently, the user interface presented by NewAccount.htm relies onSaveAccountLogic.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 VBScriptdirectly within NewAccount.htm. The new hybrid architecture allows the user interfacepresented by NewAccount.htm to manipulate the Account and Accounts OrderEntryobjects directly, in the exact same manner as the client/server version of the order-entryapplication, 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 interfacepresented by NewAccount.htm to manipulate the Account and Accounts objects directly toadd a new customer account to the underlying data source.
The VBScript code used by NewAccount.htm to add a new customer account to the datasource can be seen in Listing 10-3, and is remarkably similar to the VBScript code used bythe AccountInfoForm form in the client/server version, which can be seen in Listing10-4.
Listing 10-3. VBScript code from the NewAccount.htm page responsible for actually addingnew 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 theorder-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 MeEnd 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Previous Table of Contents Next
While the hybrid architecture allows us to access server-side resources using DCOM, there areplaces where ASP is still the best solution. For example, consider how the ModifyAccount.aspfile is used to retrieve information about a specific customer account and display it on anHTML page. ASP provides a great facility for the entire page, complete with customer accountinformation, to be generated on the server and shipped to the client for display. However, oncethe account information has been edited via the ModifyAccount.asp user interface, theAccount and Accounts OrderEntry objects are manipulated using client-side VBScript tosave 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 savingmodified 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 usto create a Web DCOM version of the order-entry application using the same basicarchitectural design that we used earlier to create the client/server DCOM version of theapplication. In addition, because the DCOM Web version of the application is stillHTML-based, we can continue to use the Web as a way to distribute the application to eachclient’s machine.
The ideal application architecture seems to be a combination of DCOM and a hybridclient/server/weblication solution. What we want to do is use HTML to create the application’suser interface; program the HTML-generated UI in such a way that we can create the variousobjects 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 toensure on-demand delivery of the latest version of the order-entry application to eachclient desktop
• Eliminate the need to install and configure ODBC on each client machine
• Continue to develop applications using the familiar client/server COM programmingmodel
Summary
In this chapter, you learned:
• That DCOM extends the COM programming model beyond the physical machineboundaries by using MS-RPC, Microsoft’s implementation of the OSF DCE RPCsystem.
• That DCOM employs two independently operating forms of security: activationsecurity 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 functionsdefined by a particular COM object.
• That DCOM offers several levels of user authentication and impersonation to suit awide variety of needs.
• How to use DCOMCNFG to provide both default system-wide andapplication-specific DCOM configuration information.
• That DCOM relies on the HKEY_CLASSES_ROOT\APPID\ registry key and theAPPID named value to locate and launch remote COM servers.
• That DCOM can be used programmatically via the additional APIs that have beenadded to COM on DCOM’s behalf.
• How DCOM can be used to improve the architectures of both the traditionalclient/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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Table of Contents
Appendix AFrequently Used Interfaces and APIs
THE FOLLOWING IS a list of frequently used interfaces and APIs. For furtherinformation, consult Microsoft Developer Network (MSDN) or the on-linedocumentation provided by your development environment.
InterfacesIAdviseSink IBindCtxICatInformation ICatRegisterIClassActivator IClassFactoryIClassFactory2 IConnectionPointIConnectionPointContainer IDataAdviseHolderIDataObject IEnumXXXXIEnumConnectionPoints IEnumConnectionsIEnumFORMATETC IEnumMonikerIEnumSTATDATA IEnumSTATPROPSETSTGIEnumSTATSTG IEnumStringIEnumUnknown IErrorLogIExternalConnection ILockBytesIMalloc IMallocSpyIMarshal IMessageFilterIMoniker IOleItemContainerIOXIDResolver IParseDisplayNameIPersist IPersistFile
IPersistMemory IPersistMonikerIPersistPropertyBag IPersistStorageIPersistStream IPersistStreamInitIPropertyBag IPropertySetStorageIPropertyStorage IProvideClassInfoIProvideClassInfo2 IRootStorageIROTData IRunnableObjectIRunningObjectTable IStdMarshalInfoIStorage IStreamIUnknown
DCOMIClientSecurity IMultiQIIServerSecurity
AutomationICreateErrorInfo ICreateTypeInfoICreateTypeInfo2 ICreateTypeLibICreateTypeLib2 IErrorInfoIDispatch IProvideClassInfoIProvideClassInfo2 ISupportErrorInfoITypeComp ITypeInfoITypeInfo2 ITypeLibITypeLib2
APIs
GeneralBindMoniker CLSIDFromProgIDCLSIDFromString CoAddRefServerProcessCoCreateFreeThreadedMarshaler CoCreateGuidCoCreateInstance CoDisconnectObjectCoDosDateTimeToFileTime CoFileTimeNowCoFileTimeToDosDateTime CoFreeAllLibrariesCoFreeLibrary CoFreeUnusedLibrariesCoGetClassObject CoGetCurrentProcessCoGetInstanceFromFile CoGetInstanceFormIStorageCoGetInterfaceAndReleaseStream CoGetMallocCoGetMarshalSizeMax CoGetPSClsidCoGetStandardMarshal CoGetTreatAsClassCoInitialize CoInitializeExCoIsHandlerConnected CoLoadLibrary
CoLockObjectExternal CoMarshalIntefaceCoMarshalInterThreadInterfaceInStreamCoRegisterClassObject CoRegisterMallocSpyCoRegisterMessageFilter CoRegisterPSClsidCoReleaseMarshalData CoReleaseServerProcessCoResumeClassObjects CoRevokeClassObjectCoRevokeMallocSpy CoSuspendClassObjectsCoTaskMemAlloc CoTaskMemFreeCoTaskMemRealloc CoTreatAsClassCoUninitialize CoUnmarshalInterfaceCreateAntiMoniker CreateBindCtxCreateClassMoniker CreateDataAdviseHolderCreateGenericComposite CreateILockBytesOnHGlobalCreateItemMoniker CreatePointerMonikerCreateStreamOnHGlobal DllCanUnloadNowDllGetClassObject DllRegisterServerDllUnregisterServer FreePropVariantArrayGetClassFile GetConvertStgGetHGlobalFromILockBytes GetHGlobalFromStreamGetRunningObjectTable IIDFromStringIsEqualGUID IsEqualCLSIDIsEqualIID MkParseDisplayNameMonikerCommonPrefixWith MonikerRelativePathToProgIDFromCLSID PropStgNameToFmtIdPropVariantClear PropVariantCopyReadClassStg ReadClassStmReadFmtUserTypeStg ReleaseStgMediumSetConvertStg StgCreateDocfileStgCreateDocfileOnILockBytes StgCreatePropSetStgStgCreatePropStg StgIsStorageFileStgIsStorageILockBytes StgOpenPropStgStgOpenStorage StgOpenStorageOnILockBytesStgSetTimes StringFromCLSIDStringFromGUID2 StringFromIIDWriteClassStg WriteClassStmWriteFmtUserTypeStg
DCOMCoCopyProxy CoCreateInstanceExCoGetCallContext CoImpersonateClientCoInitializeSecurity CoQueryAuthenticationServicesCoQueryClientBlanket CoQueryProxyBlanket
CoRevertToSelf CoSetProxyBlanket
Automation
GENERAL
CreateErrorInfo CreateStdDispatchDispGetIDsOfNames DispGetParamDispInvoke GetActiveObjectGetAltMonthNames GetErrorInfoRegisterActiveObject RevokeActiveObjectSetErrorInfo
TYPE LIBRARY
CreateDispTypeInfo CreateTypeLibLHashValOfName LHashValOfNameSysLoadRegTypeLib LoadTypeLibLoadTypeLibEx RegisterTypeLibUnRegisterTypeLib
BSTR
BstrFromVector SysAllocStringSysAllocStringByteLen SysAllocStringLenSysFreeString SysReAllocStringSysReAllocStringLen SysStringByteLenSysStringLen VectorFromBstr
SAFEARRAY
SafeArrayAccessData SafeArrayAllocDataSafeArrayAllocDescriptor SafeArrayCopySafeArrayCopyData SafeArrayCreateSafeArrayCreateVector SafeArrayDestroySafeArrayDestroyData SafeArrayDestroyDescriptorSafeArrayGetDim SafeArrayGetElementSafeArrayGetElemsize SafeArrayGetLBoundSafeArrayGetUBound SafeArrayLockSafeArrayPtrOfIndex SafeArrayPutElementSafeArrayRedim SafeArrayUnaccessDataSafeArrayUnlock
VARIANT
DosDateTimeToVariantTime SystemTimeToVariantTimeVarDateFromUdate VariantChangeTypeVariantChangeTypeEx VariantClearVariantCopy VariantCopyInd
VariantInit VariantTimeToDosDateTimeVariantTimeToSystemTime VarNumFromParseNumVarParseNumFromStr VarUdateFromDate
National LanguageCompareString LCMapStringGetLocaleInfo GetStringTypeGetSystemDefaultLangID GetSystemDefaultLCIDGetUserDefaultLangID GetUserDefaultLCID
RegistryRegCloseKey RegConnectRegistryRegCreateKey RegCreateKeyExRegDeleteKey RegDeleteValueRegEnumKey RegEnumKeyExRegEnumValue RegFlushKeyRegGetKeySecurity RegLoadKeyRegNotifyChangeKeyValue RegOpenKeyRegOpenKeyEx RegQueryInfoKeyRegQueryMultipleValues RegQueryValueRegQueryValueEx RegReplaceKeyRegRestoreKey RegSaveKeyRegSetKeySecurity RegSetValueRegSetValueEx 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Table of Contents
Appendix BResources for COM Developers
BESIDES THE COM Specification that is included on the companionCD-ROM and the Microsoft Win32 Software Development Kit (SDK)documentation that is typically included as part of your compiler’sdocumentation, the following is a list of essential resources containingadditional COM programming information.
BooksKraig Brockschmidt, Inside OLE (Microsoft Press); ISBN1-55615-618-9
OLE 2 Programmer’s Reference, Volume 1 (Microsoft Press); ISBN1-55615-628-6
OLE 2 Programmer’s Reference, Volume 2 (Microsoft Press); ISBN1-55615-629-4
OLE Automation Programmer’s Reference (Microsoft Press); ISBN1-55615-851-3
The Microsoft WebsiteWeb Site URLThe 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 WebEdition
http://www.microsoft.com/technet/
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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Table of Contents
Appendix CWhat’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 andcan be copied directly to your hard drive. The C++ samples in this book werebuilt using Microsoft Visual C++5.0 and can be built using either an MSVC++5.0 project file or a standard makefile, both of which are included on theCD-ROM. Each project has four folders: Debug, Release, DebugU, andReleaseU, supporting both ANSI and Unicode debug and release builds. TheVisual 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 eachchapter 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 ComponentObject Model and is a very valuable resource. On the CD-ROM, you will finda folder named com_spec. This folder contains two additional folders: doc andrtf, which contain versions of the October 24, 1995 draft of the ComponentObject Model Specification. Printed out, the COM Specification is roughly270 pages. However, to facilitate on-line reading and printing, the document
exists in multiple formats. The doe folder contains each chapter of thespecification in a separate Word 6.0 document. The filenames for eachdocument reflect the contents of that chapter (e.g. CH08 Security.doc). TheThe COM Specification.DOC file is a Word 6.0 Master Document that holdsall of the individual chapters together as well as providing the title page, tableof 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 partof Windows 95. However, if you look in the DCOM95 folder on thecompanion CD-ROM, you will find two self-extracting executables:DCOM95.EXE and DCM95CFG.EXE. DCOM95.EXE will install version1.0 of DCOM for Windows 95, and DCM95CFG.EXE will install theWindows 95 version of DCOMCNFG.EXE. However, if you are currentlyrunning Internet Explorer 4.0 (IE 4.0) or Windows 98, you should not installeither of these two executables, as both of these products ship with morecurrent builds of DCOM. While there are several differences between DCOMon Windows 95 and DCOM on Windows NT, one of the biggest is that bydefault, DCOM for Windows 95 doesn’t allow COM servers to act as DCOMservers. Thank goodness this functionality is controlled by theEnableRemoteConnect named-value under theHKEY_LOCAL_MACHINE\Software\Microsoft\ OLE registry key. Thisvalue is set to “N” by default, which means that the machine is allowed toconnect 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 runningobject; unlike Windows NT, DCOM for Windows 95 only allows clients toconnect to running COM servers. DCOM for Windows 95 will not launch aCOM server. Additional information regarding DCOM for Windows 95including the latest builds can be obtained from the Microsoft Web site athttp://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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Table of Contents
Appendixes
Quick ReferenceODL Language Features in MIDL
THE MICROSOFT INTERFACE Definition Language (MIDL) compiler and the MkTypLib utility are bothcapable of compiling scripts that are written in the Object Description Language (ODL). Since Microsofthas expanded the Interface Definition Language (IDL) to contain the complete ODL syntax, you shoulduse the MIDL compiler in preference to MkTypLib, as MkTypLib is being phased out and will no longerbe supported.
For more information about the MIDL compiler, refer to the MIDL Programmer’s Guide and Reference inthe Win32 Software Development Kit (SDK).
Contents of a Type Library
Type libraries are compound document files (.tlb files) that include information about types and objectsexposed 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 toas 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 madeavailable to the users of the applications and programming tools. Type libraries can be shipped in any ofthe following forms:
• A resource in a dynamic link library (DLL). This resource should have the type TypeLib and aninteger identifier. It must be declared in the resource (.rc) file as follows:
1 typelib mylib1.tlb2 typelib mylib2.tlb
There can be multiple type library resources in a DLL. Application developers should use the resourcecompiler to add the .tlb file to their own DLL. A DLL with one or more type library resources typicallyhas 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 binaryfile.
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 theycan contain the same syntax elements as .odl files. The MIDL compiler and the MkTypLib utility bothcompile scripts written in ODL. However, MkTypLib is obsolete, and you should use the MIDL compilerinstead. The following sections describe the differences and special considerations for using MkTypLiband 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. TheMkTypLib utility parses files written in ODL syntax, generates the type libraries, and optionally createsC++ header files that contain the same definitions.
The top-level element of ODL syntax is the library statement (or library block). Every other ODLstatement (with the exception of the attributes that can be applied to the library statement) must bedefined in the library block.
The MIDL compiler generates a type library when it sees a library statement in the same way thatMkTypLib does. The statements found in the library block follow essentially the same syntax as earlierversions of ODL.
ODL attributes can be applied to an element both inside and outside of the library block. Outside theblock, they typically do nothing, unless the element is referenced from within the block by using it as abase 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 generatedtype 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 thesedifferences arise because MIDL is more C-syntax oriented than MkTypLib. All of the differencesdescribed here, with the exception of floating point constants and the enum scope, can be resolved byusing 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 optionis 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_RECORDfor 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 userinterface. If you expect a TKIND_RECORD to have a real name, there is a potential for unrecognizablenames to appear in the user interface. This behavior also applies to union and enum definitions, with theMIDL compiler generating TKIND_UNIONs and TKIND_ENUMs, respectively.
MIDL also allows C-style struct, union, and enum definitions. For example, the following definitionis 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, whichmaps to VARIANT_BOOL, and which is defined as a short. In MIDL, the Boolean base type is equivalentto VT_UI1, which is defined as an unsigned char, and the BOOL datatype is defined as a long. This leadsto difficulties if you mix IDL syntax and ODL syntax in the same file while still trying to maintaincompatibility with MkTypLib. Because the datatypes are different sizes, the marshaling code will notmatch what is described in the type information. If you want a VT_BOOL in your type library, you shoulduse 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 beconditionally compiled to generate either a GUID predefinition or an instantiated GUID. MIDL normallyputs GUID predefinitions in its generated header files and only puts GUID instantiations in the filegenerated 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 isglobal, as it is in C. For example, the following code will compile in MkTypLib, but will generate aduplicate 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. TheMIDL function gets its definitions for these base types by automatically importing oleauto.idl, andoleidl.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 INCLUDEpath. The OLE and Automation DLLs must also be in the system if the user compiles an .idl file thatcontains a library statement.
THE /MKTYPLIB203 OPTION
The MIDL compiler behaves differently from the MkTypLib utility. The /mktyplib203 optionremoves 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 useVARIANT_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, andonly puts GUID instantiations in the file generated by the /iid option. With the /mktyplib203option, MIDL defines GUIDs in the header files in the way that MkTypLib does. They are defined with amacro that can be compiled conditionally to generate either a predefined or an instantiated GUID. Withthe /mktyplib203 option enabled, it is invalid to put any statements outside of the library block. Apure 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. Forexample:
typedef struct foo { int i; } bar;
In this statement, MkTypLib generates a TKIND_RECORD named “bar.” Because the “foo” was notrecorded 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) aTKIND_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 requiredfor structures, unions, and enumerators. The behavior is the same as under MkTypLib (that is, “foo” is notincluded 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++ headerfile.
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 ITypeLiband 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 byODLfile. It can optionally produce a header (.h) file, which is a stripped version of the input file. This fileis 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 asinterface, 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 of1 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 optionalvalue. 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 forthe 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 andcolumn 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 uniqueidentifier (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 generalsyntax:
[attributes] elementname typename{memberdescriptions};
The attributes set characteristics for the element. The elementname is a keyword that indicates the kind ofitem (module, interface, dispinterface, or coclass), and the typename defines the name of the item. Thememberdescriptions 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 syntacticitems in an .odl file.
ARRAY DEFINITIONS
MkTypLib accepts both fixed-size arrays and arrays declared as SAFEARRAY. Use a C-style syntax for afixed 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 standard0x 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. Thebackslash 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 theclosing delimiter, it should be preceded with a backslash (\"). Similarly, a double backslash (\\) should beused 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 indirectories that are referenced by the type library registration entries, so partial path names are typicallyused.
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 byrunning the Guidgen.exe command line program. Guidgen.exe never produces the same number twice, nomatter how many times it is run or how many different machines it runs on. Every entity that needs to beuniquely 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 casesensitive, but it generates type libraries that are case insensitive. It is therefore possible to define auser-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 propertyaccessor 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 theBSTR data type, which indicates a length-prefixed string. In 32-bit type libraries, Unicode strings can bedefined with the LPWSTR data type.
Attributes List
The following is a list of the Object Description Language (ODL) attributes, statements, and directivesthat are now part of the Microsoft Interface Definition Language (MIDL).
appobject aggregatablebindable controlcustom defaultdefaultbind defaultcollelemdefaultvalue defaultvtbldisplaybind dllnamedual entryhelpcontext helpfilehelpstring helpstringcontexthelpstringdll hiddenid immediatebindin lcidlicensed nonbrowsablenoncreateable nonextensibleodl oleautomationoptional outpropget propputpropputref publicreadonly replaceablerequestedit restrictedretval sourcestring uidefaultusesgetlasterror uuidvararg version
Statements Listcoclass dispinterfaceenum interfacelibrary modulestruct typedefunion
Directives List
importlib
Attribute Descriptions
The following sections describe the ODL attributes and the types of objects that they apply to, along withthe equivalent flags set in the object’s type information.
appobjectDescriptionIdentifies the Application object.
Allowed onCoclass.
CommentsIndicates that the members of the class can be accessed without qualification when accessing thistype library.
FlagsTYPEFLAG_FAPPOBJECT
aggregatableDescriptionIndicates that the class supports aggregation.
Allowed onCoclass.
CommentsIndicates that the members of the class can be aggregated.
FlagsTYPEFLAG-FAGGREGATABLE
Example
[uuid(1e196b20-1f3c-1069-996b-00dd010fe676), aggregatable]coclass Form{ [default] interface IForm; [default, source] interface IFormEvents;}
bindableDescriptionIndicates that the property supports data binding.
Allowed onProperty.
Comments
Refers to the property as a whole, so it must be specified wherever the property is defined. Theattribute should be specified on both the property get description and the property set description.
FlagsFUNCFLAG_FBINDABLE
VARFLAG_FBINDABLE
controlDescriptionIndicates that the item represents a control from which a container site will derive additional typelibraries or coclasses.
Allowed onType library, coclass.
CommentsThis attribute allows type libraries that describe controls to be marked so that they are not displayedin type browsers intended for nonvisual objects.
FlagsTYPEFLAG_FCONTROL
LIBFLAG_FCONTROL
custom(guid, value)DescriptionIndicates a custom attribute (one not defined by Automation). This feature enables the independentdefinition 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 onLibrary, typeinfo, typlib, variable, function, parameter.
Not allowed onA member of a coclass (IMPLTYPE).
RepresentationCan be retrieved using:
ITypeLib2::GetCustDataITypeInfo2::GetCustDataITypeInfo2::GetAllCustDataITypeInfo2::GetFuncCustDataITypeInfo2::GetAllFuncCustDataITypeInfo2::GetVarCustDataITypeInfo2::GetAllVarCustDataITypeInfo2::GetParamCustDataITypeInfo2::GetAllParamCustDataITypeInfo2::GetImplTypeCustDataITypeInfo2::GetAllImplTypeCustData
ExampleThe 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;}
defaultDescriptionIndicates that the interface or dispinterface represents the default programmability interface.Intended for use by macro languages.
Allowed onCoclass member.
CommentsA coclass can have two default members at most. One represents the source interface ordispinterface, and the other represents the sink interface or dispinterface. If the default attributeis not specified for any member of the coclass or cotype, the first source and sink members that donot have the restricted attribute will be treated as the defaults.
FlagsIMPLTYPEFLAG_FDEFAULT
defaultbindDescriptionIndicates the single, bindable property that best represents the object.
Allowed onProperty.
CommentsProperties that have the defaultbind attribute must also have the bindable attribute. Thedefaultbind 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 ratherthan binding to a property of an object. An object can support data binding and not have thisattribute.
FlagsFUNCFLAG_FDEFAULTBIND
VARFLAG_FDEFAULTBIND
defaultcollelemDescriptionAllows for optimization of code.
Allowed onProperty, members in dispinterface and interface.
CommentsIn Visual Basic for Applications (VBA5.0), foo!bar is normally syntactic shorthand forfoo.defaultprop (“bar”). Because such a call is significantly slower than accessing a datamember of foo directly, an optimization has been added in which the compiler looks for a membernamed “bar” on the type of foo. If such a member is found and flagged as an accessor function foran element of the default collection, a call is generated to that member function. To allow vendorsto produce object servers that will be optimized in this way, the member flag should bedocumented.Because this optimization searches the type of item that precedes the ‘!’, it will optimize calls of theform MyForm!bar only if MyForm has a member named “bar,” and it will optimizeMyForm.Controls!bar only if the return type of Controls has a member named “bar.” Eventhough MyForm!bar and MyForm.Controls!bar both would normally generate the samecalls to the object server, optimizing these two forms requires that the object server add the barmethod in both places.Use of [defaultcollelem] must be consistent for a property. For example, if it is present on aGet, it must also be present on a Put.
FlagsFUNCFLAG_FDEFAULTCOLLELEM
VARFLAG_FDEFAULTCOLLELEM
ExampleA form has a button on it named Button1. User code can access the button using property syntaxor ! syntax, as shown below.
Sub Test()Dim f As Form1Dim b1 As ButtonDim b2 As Button
Set f = Form1
Set b1 = f.Button1 ' Property syntaxSet b = f!Button1 ' ! syntaxEnd 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)DescriptionEnables specification of a default value for a typed optional parameter.
Allowed onParameter.
CommentsThe expression value resolves to a constant that can be described in a variant. The ODL alreadyallows some expression forms, as when a constant is declared in a module. The same expressionsare 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 isinvalid to specify a default value for any type that cannot go in a variant, including structuresand arrays.
2. Parameters can be mixed. Optional parameters and default value parameters must followmandatory 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);}
defaultvtblDescriptionEnables an object to have two different source interfaces.
CommentsThe default interface is an interface or dispinterface that is the default source interface. If theinterface 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 adual, VTBL, or interface. If the interface is a dual interface, then sinks receive events throughthe VTBL.
An object can have both a default source and a default VTBL source interface with the sameinterface identifier (IID or GUID). In this case, a sink should advise using IID_IDISPATCH toreceive dispatch events and use the specific interface identifier to receive VTBL events.
Allowed onA member of a coclass.
CommentsFor normal (non-source) interfaces, an object can support a single interface that satisfies consumerswho want to use IDispatch access as well as VTBL access (a dual interface). Because of the waysource interfaces work, it is not possible to use dual interface for source interfaces. The object withthe source interface is in control of whether calls are made through IDispatch or through theVTBL. The sink does not provide any information about how it wants to receive the events. Theonly action that object-sourcing events can take would be to use the least common denominator, theIDispatch interface. This effectively reduces a dual interface to a dispatch interface with regardto sourcing events.
Interface Flag it translates intodefault IMPLTYPEFLAG_FDEFAULTdefault, source IMPLTYPEFLAG_FDEFAULT
IMPLTYPEFLAG_FSOURCEdefaultvtable,source
IMPLTYPEFLAG_FDEFAULT
IMPLTYPEFLAG_FDEFAULTVTABLEIMPLTYPEFLAG_FSOURCE
FlagsIMPLTYPEFLAG_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;}
displaybindDescriptionIndicates that a property should be displayed as bindable to the user.
Allowed onProperty.
CommentsProperties that have the displaybind attribute must also have the bindable attribute. Anobject can support data binding and not have this attribute.
FlagsFUNCFLAG_FDISPLAYBIND
VARFLAG_FDISPLAYBIND
dllname(str)DescriptionDefines the name of the DLL that contains the entry points for a module.
Allowed onModule (required).
CommentsThe str argument gives the file name of the DLL.
dual
DescriptionIdentifies an interface that exposes properties and methods through IDispatch and directlythrough the VTBL.
Allowed onInterface.
CommentsThe interface must be compatible with Automation and derive from IDispatch. Not allowed ondispinterfaces.The dual attribute creates an interface that is both a Dispatch interface and a COM interface. Thefirst seven entries of the VTBL for a dual interface are the seven members of IDispatch, and theremaining entries are COM entries for direct access to members of the dual interface. All of theparameters and return types specified for members of a dual interface must be compatible withAutomation types.All members of a dual interface must pass an HRESULT as the function’s return value. Membersthat need to return other values should specify the last parameter as [retval, out] indicating anoutput parameter that returns the value of the function. In addition, members that need to supportmultiple locales should pass an lcid parameter.A dual interface provides for both the speed of direct VTBL binding and the flexibility ofIDispatch binding. For this reason, dual interfaces are recommended whenever possible.Specifying dual on an interface implies that the interface is compatible with Automation, andtherefore causes both the TYPEFLAG_FDUAL and TYPEFLAG_FOLEAUTOMATION flags tobe set.
FlagsTYPEFLAG_FDUAL
TYPEFLAG_FOLEAUTOMATION
entry(entryid)DescriptionIdentifies the entry point in the DLL.
Allowed onFunctions in a module (required).
CommentsIf entryid is a string, this is a named entry point. If entryid is a number, the entry point is defined byan ordinal. This attribute provides a way to obtain the address of a function in a module.
helpcontext(numctxt)DescriptionSets the context in the Help file.
Allowed onLibrary, interface, dispinterface, struct, enum, union, module, typedef, method,struct member, enum value, property, coclass, const.
CommentsRetrieved 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)DescriptionSets the name of the Help file.
Allowed onLibrary.
CommentsRetrieved through the GetDocumentation functions in the ITypeLib and ITypeInfointerfaces.
All types in a library share the same Help file.
helpstring(string)DescriptionSets the Help string.
Allowed onLibrary, interface, dispinterface, struct, enum, union, module, typedef, method,struct member, enum value, property, coclass, const.
CommentsRetrieved through the GetDocumentation functions in the ITypeLib and ITypeInfointerfaces.
helpstringcontext(contextid)DescriptionSets the string context in the Help file.
Allowed onType library, type information (Typeinfo), function, and variable level.
CommentsRetrieved by the GetDocumentation2 functions in the ITypeLib2 and ITypeInfo2interfaces. The contextid is a 32-bit Help context identifier in the Help file.
helpstringdll(dllname)DescriptionSets the name of the DLL to use to perform the document string lookup (localization).
Allowed onType library.
CommentsRetrieved through the GetDocumentation2 functions in the ITypeLib2 and ITypeInfo2interfaces.
hiddenDescriptionIndicates that the item exists, but should not be displayed in a user-oriented browser.
Allowed onProperty, method, coclass, dispinterface, interface, library.
CommentsThis 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 isintended for use by controls. Hosts need to create a new type library that wraps the control withextended properties.
FlagsVARFLAG_FHIDDEN
FUNCFLAG_FHIDDEN
TYPEFLAG_FHIDDEN
id(num)DescriptionIdentifies the DISPID of the member.
Allowed onMethod or property in an interface or dispinterface.
Comments
The num is a 32-bit integral value in the following format:
Bits Value0-15 Offset. Any value is permissible.16-21 The nesting level of this type information in the inheritance
hierarchy. For example:interface mydisp : IDispatchThe nesting level of IUnknown is 0, IDispatch is 1, andMyDisp is 2.
22-25 Reserved. Must be zero.26-28 Dispatch identifier (DISPID) value.
29True if this is the member identifier for a FuncDesc; otherwiseFalse.
30-31 Must be 01.
Negative identifiers are reserved for use by Automation.
immediatebindDescriptionAllows individual bindable properties on a form to specify this behavior. When this bit is set, allchanges will be notified.
CommentsAllows controls to differentiate two different types of bindable properties. One type ofbindable property needs to notify every change to the database (for example, with a check boxcontrol where every change needs to be sent through to the underlying database, even though thecontrol has not lost the focus). However, controls such as a list box need to have the change of aproperty communicated to the database when the control loses focus, because the user may havechanged the selection with the arrow keys before finding the desired setting. If the changenotification was sent to the database every time the user pressed an arrow key, it would give anunacceptable performance.The bindable and requestedit attribute bits need to be set for this new bit to have an effect.
FlagsVARFLAG_FIMMEDIATEBIND
FUNCFLAG_FIMMEDIATEBIND
inDescriptionSpecifies an input parameter.
Allowed onParameter.
CommentsThe parameter can be a pointer (such as char*) but the value it refers to is not returned.
lcidDescriptionIndicates that the parameter is a locale ID (LCID).
Allowed onParameter in a member of an interface.
CommentsOnly one parameter can have this attribute. The parameter must have the in attribute and not theout 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 themember specifies propertyput or propertyputref, the lcid parameter must precede theparameter 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)DescriptionThis attribute identifies the locale for a type library.
Allowed onLibrary.
CommentsThe numid is a 32-bit LCID, as used in Win32 National Language Support. The LCID is typicallyentered in hexadecimal format.
licensedDescriptionIndicates that the class is licensed.
Allowed onCoclass.
FlagsTYPEFLAG_FLICENSED
nonbrowsableDescriptionIndicates that the property appears in an object browser (which does not show property values), butdoes not appear in a properties browser (which does show property values).
Allowed onProperty.
FlagsVARFLAG_FNONBROWSABLE
FUNCFLAG_FNONBROWSABLE
noncreatableDescriptionIndicates that the class does not support creation at the top level (for example, throughITypeInfo::CreateInstance or CoCreateInstance). An object of such a class isusually obtained through a method call on another object.
Allowed onCoclass.
Example
[uuid(1e196b20-1fc3-1069-996b-00dd010ef671),helpstring("This is Dynaset"),noncreatable]coclass Dynaset{ [default] interface IDynaset; [default, source] interface IDynasetEvents;}
Flags
TYPEFLAG_FCANCREATE
nonextensibleDescriptionIndicates that the IDispatch implementation includes only the properties and methods listed inthe interface description.
Allowed onDispinterface, interface.
CommentsThe interface must have the dual attribute.By default, Automation assumes that interfaces can add members at run time, meaning that itassumes the interfaces are extensible.
FlagsTYPEFLAG_FNONEXTENSIBLE
odlDescriptionIdentifies an interface as an Object Description Language (ODL) interface.
Allowed onInterface (required).
CommentsThis attribute must appear on all interfaces.
oleautomationDescriptionThe oleautomation attribute indicates that an interface is compatible with Automation.
Allowed onInterface.
CommentsNot allowed on dispinterfaces.The parameters and return types specified for its members must be compatible with Automation. Aparameter is compatible with Automation if its type is compatible with an Automation type, apointer to an Automation type, or a SAFEARRAY of an Automation type. A return type iscompatible with Automation if its type is an HRESULT or is void. Methods in Automation mustreturn either HRESULT or void. A member is compatible with Automation if its return type andall of its parameters are compatible with Automation. An interface is compatible with Automation ifit derives from IDispatch or IUnknown, if it has the oleautomation attribute, or if all of itsVTBL entries are compatible with Automation. For 32-bit systems, the calling convention for allmethods in the interface must be STDCALL. For 16-bit systems, all methods must have the CDECLcalling convention. Every dispinterface is compatible with Automation.
FlagsTYPEFLAG_FOLEAUTOMATION
optionalDescriptionSpecifies an optional parameter.
Allowed onParameter.
CommentsValid only if the parameter is of type VARIANT or VARIANT*. All subsequent parameters of thefunction must also be optional.
outDescriptionSpecifies an output parameter.
Allowed onParameter.
CommentsThe parameter must be a pointer to memory that will receive a result.
propgetDescriptionSpecifies a property-accessor function.
Allowed onFunction, method in interface, dispinterface.
CommentsThe property must have the same name as the function. At most, one of propget, propput, andpropputref can be specified for a function.
FlagsINVOKE_PROPERTYGET
propputDescriptionSpecifies a property-setting function.
Allowed onFunction, method in interface, dispinterface.
CommentsThe property must have the same name as the function. Only one propget, propput, andpropputref can be specified.
FlagsINVOKE_PROPERTYPUT
propputrefDescriptionSpecifies a property-setting function that uses a reference instead of a value.
Allowed onFunction, method in interface, dispinterface.
CommentsThe property must have the same name as the function. Only one propget, propput, andpropputref can be specified.
FlagsINVOKE_PROPERTYPUTREF
publicDescriptionIncludes an alias declared with the typedef keyword in the type library.
Allowed onAlias declared with typedef.
CommentsBy 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 aliasbecomes part of the type library.
readonlyDescriptionProhibits assignment to a variable.
Allowed onVariable.
FlagsVARFLAG_FREADONLY
replaceableDescriptionTags an interface as having default behaviors.
Allowed onMethods and properties of dispinterfaces and interfaces.
CommentsThe object supports IConnectionPointWithDefault.
FlagsTYPEFLAG_FREPLACEABLE
FUNCFLAG_FREPLACEABLE
VARFLAG_FREPLACEABLE
requesteditDescriptionIndicates that the property supports the OnRequestEdit notification.
Allowed onProperty.
CommentsThe property supports the OnRequestEdit notification, raised by a property before it is edited.An object can support data binding and not have this attribute.
FlagsFUNCFLAG_FREQUESTEDIT
VARFLAG_FREQUESTEDIT
restrictedDescriptionPrevents the item from being used by a macro programmer.
Allowed onType library, type information, coclass member, or member of a module or interface.
CommentsThis attribute is allowed on a member of a coclass, independent of whether the member is adispinterface or interface, and independent of whether the member is a sink or source. A member ofa coclass cannot have both the restricted and default attributes.
FlagsIMPLTYPEFLAG_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;}
retvalDescriptionDesignates the parameter that receives the return value of the member.
Allowed onParameters of interface members that describe methods or get properties.
CommentsThis attribute can be used only on the last parameter of the member. The parameter must have theout attribute and must be a pointer type.Parameters with this attribute are not displayed in user-oriented browsers.
FlagsIDLFLAG_FRETVAL
sourceDescriptionIndicates that a member is a source of events.
Allowed onMember of a coclass, property, or method.
CommentsFor a member of a coclass, this attribute indicates that the member is called rather thanimplemented.On a property or method, this attribute indicates that the member returns an object or VARIANTthat is a source of events. The object implements the interfaceIConnectionPointContainer.
FlagsIMPLTYPEFLAG_FSOURCE
VARFLAG_SOURCE
FUNCFLAG_SOURCE
stringDescriptionSpecifies a string.
Allowed onStructure, member, parameter, property.
CommentsIncluded only for compatibility with the Interface Definition Language (IDL). Use LPSTR for azero-terminated string.
uidefaultDescriptionIndicates that the type information member is the default member for display in the user interface.
Allowed onA member of an interface or dispinterface.
CommentsThis attribute is used to mark an event as the default (the first one created) or a property as thedefault (the one to select first in the properties browser). For example, Visual Basic uses thisattribute in the following ways:
• When an object is double-clicked at design time, Visual Basic jumps to the event in thedefault source interface that is marked as [uidefault]. If there is no such member, thenVisual 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 VisualBasic displays the property in the default interface that is marked as [uidefault]. If thereis no such member, then Visual Basic displays the first one listed in the default interface.
FlagsFUNCFLAG_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;}
usesgetlasterrorDescriptionTells the caller that, if there is an error when calling that function, the caller can then callGetLastError to retrieve the error code.
Allowed onMember of a module.
CommentsThe usesgetlasterror attribute can be set on a module entry point, if that entry point uses theWin32 function SetLastError to return error codes. The attribute tells the caller that, if there isan error when calling that function, the caller can then call GetLastError to retrieve the errorcode.
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)DescriptionSpecifies the universally unique ID (UUID) of the item.
Allowed onRequired for library, dispinterface, interface, and coclass. Optional for struct, enum, union,module, and typedef.
CommentsThe 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 structureretrieved by ITypeInfo::GetTypeAttr.
varargDescriptionIndicates a variable number of arguments.
Allowed onFunction.
CommentsIndicates that the last parameter is a safe array of VARIANT type, which contains all of theremaining parameters.
version(versionval)Description
Specifies a version number.
Allowed onLibrary, struct, module, dispinterface, interface, coclass, enum, union.
CommentsThe argument versionval is a real number in the format n.m, where n is a major version number andm is a minor version number.
Statement Descriptions
The following sections describe the statements and directives that make up the Object DescriptionLanguage (ODL).
coclass
This statement describes the globally unique ID (GUID) and the supported interfaces for a ComponentObject Model (COM).
Syntax
[attributes]coclass classname{[attributes2] [interface | dispinterface] interfacename;};
Syntax ElementsattributesThe uuid attribute is required on a coclass. This is the same uuid that is registered as a CLSID inthe system registration database. The helpstring, helpcontext, licensed, version,control, hidden, and appobject attributes are accepted, but not required, before a coclassdefinition. For more information about the attributes accepted before a coclass definition, see“Attribute Descriptions” earlier in this appendix. The appobject attribute makes the functionsand 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 restrictedattributes are accepted on an interface or dispinterface in a coclass.
interfacename
Either an interface declared with the interface keyword, or a dispinterface declared with thedispinterface keyword.
CommentsCOM defines a class as an implementation that allows QueryInterface between a set ofinterfaces.
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:proplistmethods:methlist};
Syntax 2
[attributes] dispinterface intfname{interface interfacename};
Syntax ElementsattributesThe helpstring, helpcontext, hidden, uuid, and version attributes are acceptedbefore dispinterface. For more information about the attributes accepted before a dispinterfacedefinition, see “Attribute Descriptions ” earlier in this appendix. Attributes (including the brackets)can be omitted, except for the uuid attribute, which is required.
intfnameThe name by which the dispinterface is known in the type library. This name must be unique withinthe 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 commentssection 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 followingform:
[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. Ifvararg 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 arein, out, optional, and string.If optional is specified, it must only be specified on the right-most parameters, and the types ofthose parameters must be VARIANT.
CommentsMethod functions are specified exactly as described in the “module ” statement except that theentry attribute is not allowed.Properties can be declared either in the properties or methods lists. Declaring properties in theproperties 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 propertyfunctions 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 oneach 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 valuein addition to an HRESULT error code. The lcid attribute is also invalid for dispinterfacesbecause IDispatch::Invoke passes a locale ID (LCID). However, it is possible to declare aninterface again that uses these attributes.Using Syntax 2, interfaces that support IDispatch and are declared earlier in an ODL script canbe 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 inheritsto support IDispatch. In this case, if Hello was declared earlier with lcid and retvalmembers that returned HRESULTs, MkTypLib would remove each lcid parameter andHRESULT 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 implementIDispatch::Invoke. The dispinterface is used when an application needs to expose existingnon-VTBL functions through Automation. These applications can implementIDispatch::Invoke by examining the dispidmember parameter and directly calling thecorresponding 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 ElementsattributesThe helpstring, helpcontext, hidden, and uuid attributes are accepted before an enumstatement. The helpstring and helpcontext attributes are accepted on an enumerationelement. 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.
CommentsThe enum keyword must be preceded by typedef. The enumeration description must precedeother references to the enumeration in the library. If value is not specified for enumerators, thenumbering progresses, as with enumerations in C. The type of the enum element is int, the systemdefault 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 fromany base interface.
Syntax
[attributes] interface interfacename [:baseinterface]{functionlist};
Syntax ElementsattributesThe attributes dual, helpstring, helpcontext, hidden, odl, oleautomation, uuid,and version are accepted before interface. If the interface is a member of a coclass, theattributes source, default, and restricted are also accepted. For more information aboutthe attributes that can be accepted before an interface definition, refer to the section “AttributeDescriptions” earlier in this appendix. The attributes odl and uuid are required on allinterface 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.
FunctionlistList of function prototypes for each function in the interface. Any number of function definitionscan 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 ofVARIANT type. The optional calling convention can be __pascal/_pascal/pascal,__cdecl/_cdecl/cdecl, or __stdcall/_stdcall/stdcall. The calling conventionspecification 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 abuilt-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 thoseparameters must be VARIANT.
CommentsBecause the functions described by the interface statement are in the VTBL, DispInvokeand CreateStdDispatch can be used to provide an implementation ofIDispatch::Invoke. For this reason, interface is more commonly used thandispinterface to describe the properties and methods of an object.Functions in interfaces are the same as described in the “module” statement except that the entryattribute is not allowed.Members of interfaces that need to raise exceptions should return an HRESULT and specify aretval parameter for the actual return value. The retval parameter is always the last parameterin the list.
ExamplesThe following example defines an interface named Hello with two member functions, Helloprocand 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 functionsfor 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 theHRESULT values and retval parameters, respectively. Tools that access the members can returnthe HRESULT to their users, or can simply expose the retval parameter as the return value, andhandle 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 MkTypLibinput file (ODL).
Syntax
[attributes] library libname{definitions};
Syntax ElementsattributesThe helpstring, helpcontext, lcid, restricted, hidden, control, and uuidattributes are accepted before a library statement. For more information about the attributesaccepted before a library definition, see “Attribute Descriptions” earlier in this appendix. Theuuid 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 coclassesrelevant to the object being exposed.
CommentsThe 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 ElementsattributesThe attributes uuid, version, helpstring, helpcontext, hidden, and dllname areaccepted before a module statement. For more information about the attributes that can beaccepted before a module definition, see “Attribute Descriptions” earlier in this appendix. Thedllname attribute is required. If uuid is omitted, the module is not uniquely specified in thesystem.
modulename
The name of the module.
elementlistList of constant definitions and function prototypes for each function in the DLL. Any number offunction definitions can appear in the function list. A function in the function list has the followingform:
[attributes] returntype [calling convention] funcname(params);[attributes] const constname = constval;
Only the attributes helpstring and helpcontext are accepted for a const. The followingattributes are accepted on a function in a module: helpstring, helpcontext, string,entry, propget, propput, propputref, vararg. If vararg is specified, the lastparameter 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 totwo 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 abuilt-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 ofthose parameters must be VARIANT.
CommentsThe header file (.h) output for modules is a series of function prototypes. The module keywordand surrounding brackets are stripped from the header file output, but a comment (\\ modulemodulename) is inserted before the prototypes. The keyword extern is inserted before thedeclarations.
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 ElementsattributesThe attributes helpstring, helpcontext, uuid, hidden, and version are acceptedbefore a struct statement. The attributes helpstring, helpcontext, and string areaccepted on a structure member. For more information about the attributes accepted before astructure definition, see “Attribute Descriptions” earlier in this appendix. Attributes (including thebrackets) 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.
CommentsThe struct keyword must be preceded with a typedef. The structure description must precedeother references to the structure in the library. Members of a struct can be of any built-in type, orany type defined lexically as a typedef before the struct. For a description of how strings andarrays can be entered, see the sections “String Definitions” and “Array Definitions” earlier in thisappendix.
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 ElementsattributesAny 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 doesnot appear in the type library. If no other attribute is desired, public can be used to explicitly includethe alias in the type library. The helpstring, helpcontext, and uuid attributes are acceptedbefore a typedef. For more information, see “Attribute Descriptions” earlier in this appendix. Ifuuid 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.
CommentsThe typedef keyword must also be used whenever a struct or enum is defined. The namerecorded for the enum or struct is the typedef name, and not the tag for the enumeration. Noattributes are required to make sure the alias appears in the type library.Enumerations, structures, and unions must be defined with the typedef keyword. The attributesfor a type defined with typedef are enclosed in brackets following the typedef keyword. If asimple alias typedef has no attributes, it is treated like a #define, and the aliasname does notappear in the library. Any attribute (public can be used if no others are desired) specified betweenthe typedef keyword and the rest of a simple alias definition causes the alias to appear explicitlyin 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 hasfour enumerator values.
union
This statement defines a C-style union.
Syntax
typedef [attributes] union [tag]{memberlist} unionname;
Syntax ElementsattributesThe attributes helpstring, helpcontext, uuid, hidden, and version are acceptedbefore a union. The helpstring, helpcontext, and string attributes are accepted on aunion 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 beomitted. 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.
CommentsThe union keyword must be preceded with a typedef. The union description must precedeother references to the structure in the library. Members of a union can be of any built-in type, orany type defined lexically as a typedef before the union. For a description of how strings andarrays can be entered, see the sections “String Definitions” and “Array Definitions” earlier in thisappendix.
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 thelibrary currently being created. All importlib directives must precede the other type descriptions in thelibrary.
Syntax
importlib(filename);
Syntax ElementfilenameThe location of the type library file when MkTypLib is executed.
CommentsThe importlib directive makes any type defined in the imported library accessible from withinthe library being compiled. Ambiguity is resolved as the current library is searched for the type. Ifthe type cannot be found, MkTypLib searches the imported library that is lexically first, and thenthe next, and so on. To import a type name in code, the name should be entered aslibname.typename, where libname is the library name as it appeared in the library statementwhen the library was compiled.The imported type library should be distributed with the library being compiled.
ExampleThe 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 isprohibited. Read EarthWeb's privacy statement.
Brief Full Advanced
Search Search Tips
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 IIIISBN: 0764580442Publication Date: 09/01/97
Search this book:
Table of Contents
Index
AAccount 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 dialogbox), 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
Bbinding. See VTBL binding
BSTRs (binary strings), 133-134
Ccall 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
Ddata 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 Propertiesdialog box), 272
Default Security tab (Distributed COM Configuration Properties dialogbox), 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
Eearly 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
Ffacility codes, 15-16
functionality
of Accounts object, 195-197
of Invoices object, 198-200
of LineItems object, 200-201
of Products object, 197-198
Gglobal.asa file, 250
GUIDs (Globally Unique Identifiers)
allocating for in-process servers, 26-27
COM objects and, 5
HHKEY_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
IIAccountInfoDispatch 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
Llate 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
Mmacros 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
Nnavigation
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
Oobject 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
PProducts 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
QQueryInterface function, 7-8
Rreference 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
SSAFEARRAY 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\Oleregistry 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
TTCO (Total Cost of Ownership), 244
type libraries
defined, 27
exposing, 143-144
Uuninitializing
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
Vvariants
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
WWeb 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 isprohibited. Read EarthWeb's privacy statement.