Полезная информация

Chapter 16
Advanced Topics


Advanced Topics

This chapter introduces some of the advanced topics that could not be directly addressed in the other chapters of this book. The topics covered here are important to ActiveX development, and you should be aware of their impact and significance in regards to your specific development. The topics are not covered in great detail; in some cases, the technology is just emerging and still being developed. For example, Internet security, OLE DB, and other technologies, such as threading and DCOM, are too complex to address within the context of this book.

The intent of this chapter is to introduce you to the main concepts and reasons behind a particular technology and allow you the choice of pursuing the technology if it applies to you. You will learn about the Internet and how it applies to your ActiveX control development. You will also learn how DCOM, threading, and OLE DB apply to ActiveX.

The last section looks at what is coming out of Microsoft to better equip you with the proper tools and technologies that make ActiveX what it is today and what it will be in the future.

Internet

As was mentioned in Chapter 1, the term ActiveX originally meant Activate the Internet. Microsoft has now expanded the term to include all COM/OLE components and development. The Internet has been around for many years, but didn't start to explode in popularity until recently. This explosion has expanded the use of the Internet as more and more companies are slowly figuring out how to use this medium safely and effectively.

Internet Security

Internet security is still in its infancy. Security is one of the major reasons companies are slow to embrace the Internet. The two major security issues that still exist are secure transmissions and ActiveX controls. Because the Internet is not a direct connection from computer to computer, hackers can tamper with or steal information during transmission fairly easily. ActiveX controls, for the most part, are downloaded to the user's machine and then installed. This situation makes it easy for someone to spread a virus via an ActiveX control in a Web page. Although Netscape led the way with its secure servers, Microsoft is quickly catching up and is working on solving these and other security issues.

One technology that Microsoft, Netscape, and other companies are developing is secure channel technology. Secure channel technology provides secure transmissions through the Secure Sockets Layer (SSL) and Private Communications Technology (PCT). SSL, created by Netscape, provides users with authentication of the server they are attaching to, encryption of the data sent and received, and integrity of the data being sent and received. PCT, created by Microsoft, provides protection against eavesdropping on a network or altering a network packet.

Microsoft and other vendors are developing a new standard that uses digital signatures to identify the publisher of an object downloaded from the Internet and to certify that it has not been tampered with. Microsoft has started implementing this in the Microsoft Internet Explorer and the Microsoft ActiveX SDK. Microsoft Internet Explorer has three safety levels: high, medium, and low. High will not download anything that is not signed, medium asks users if they want to download an unsigned item, and low downloads items whether they are signed or unsigned. Microsoft Internet Explorer defaults to the high safety level. The Microsoft ActiveX SDK includes the Authenticode utilities for signing an ActiveX control. You find out more about signing an ActiveX control later in this chapter.

On the other side, Microsoft is working on something called Client Identification, which provides a way for users to identify themselves to a Web server using a digital certificate. These server and client digital certificates have to be obtained from a Certificate Authority (CA) company, which are signed with the company's official key.

Encryption is another technology that vendors are pursuing. Basically, data and messages are scrambled and cannot be unscrambled except by someone who has a specific key. Microsoft has produced a set of encryption APIs, called the CryptoAPI, which it includes in its Win32 SDK.

Other companies, along with Microsoft, are also trying to set standards for security. One such standard recently developed is the Secure Electronic Transactions (SET) standard. SET, a standard for securing a payment transaction over the Internet, was developed by Microsoft, Netscape, Visa, MasterCard, IBM, and GTE. Microsoft is currently trying to develop another standard called the Secure Transport Layer Protocol (STLP), which Microsoft hopes will be a combination of the Netscape SSL technology and the Microsoft PCT technology. Microsoft is also trying to develop its Personal Information Exchange (PFX) standard, which is a standard for transferring personal security information from one machine to another. Currently, security information such as certificates and keys must be set up separately on each machine.

More information can be obtained from Microsoft's Web site (http://www.microsoft.com) or Netscape's Web site (http://home.netscape.com). At the time of publication, Microsoft's security specific information was found at http://www.microsoft.com/intdev/security/. Netscape's security information is scattered throughout its site. Use Microsoft's Search and Contents page to locate security information. These sites should be watched closely for information on this ever-changing technology.

Signing Software

As mentioned earlier, Microsoft and other vendors are developing a new standard that uses digital signatures to identify the publisher of an object downloaded from the Internet and to ensure that the object has not been tampered with. This process is referred to as code signing. This digital signature contains specific information about the company and is signed by a trusted certificate authority. The information is thoroughly checked before being signed. If a piece of code or data wreaks havoc with your system, you can trace it back to an individual or company with the information contained in the signature.

The use of digital signatures allows users to have the same comfort level they have when they purchase software in a box. If you purchase a software package from a retail outlet and it causes problems, you know who manufactured the software and can hold them accountable. Digital signatures allows for the same level of accountability with software that is downloaded from the Internet. Companies that digitally sign their products have the added benefit of providing to their users a commitment to quality and security, which in turn translates into consumer trust of the companies' products.

Code signing requires the following steps:

  1. A Software publisher applies for credentials from a Certificate Authority (CA).

  2. The application basically consists of the software company's personal information and its public and private encryption keys.

  3. After the application is completed, the CA will verify the publisher's information in approximately one week.

  4. After the information is verified, the CA will create an X.509 industry standard certificate that includes the publisher's public key.

  5. The private key is held by the software publisher and kept secret. A copy of the certificate is sent to the publisher. The publisher will include this certificate in any code that it signs with the private key.

  6. The publisher uses the Authenticode utilities to create a digital signature with its private key.

  7. A PKCS#7 signature file is created containing the code to be published, the X.509 certificate received from the CA, and the publisher's digital signature.

Microsoft is trying to develop standards for the preceding process. For more information on Microsoft's effort to develop the standards and more detailed information on code signing, check out the help files on signing in the ActiveX SDK and Microsoft's Internet security page mentioned before.

Internet Scripting

When creating Web pages, a developer needs a way to create online content and link and automate various objects, such as Java applets or ActiveX controls. These objectives are accomplished with scripting. Scripting is an interpretive programming language used with HTML.

Two scripting languages exist today: Microsoft Visual Basic Scripting Edition (VBScript) and Netscape JavaScript. VBScript imitates Visual Basic (VB), and JavaScript imitates the Java language, as the names imply. Both Netscape Navigator and the Microsoft Internet Explorer read JavaScript, but Netscape Navigator will not read VBScript unless the ScriptActive plug-in from NCompass Labs is used to convert the VBScript to JavaScript. Unfortunately, ScriptActive does not support all parts of VBScript, such as forms, write, and writeln. To find out what is and is not supported, view the NCompass "Authoring ActiveX Controls for the NCompass Plug-ins" document at http://www.ncompasslabs.com/documents/authoring.htm. Because this is only version 1.0 of the ScriptActive plug-in, more VBScript features should be available in the future. If your users use both Microsoft Internet Explorer and Netscape Navigator, you are better off using JavaScript. You can use a combination of both, if needed.

Scripting languages are a subset of the languages they mimic; they do not include all of the functionality available in the language. In general, scripting languages have no way to access the system or data directly, which prevents the creation of viruses. Scripting languages can, however, use other technologies to access data, such as the Microsoft dbWeb and the Netscape LiveWire Pro. Visit the Netscape Web site to view tutorials and language references for JavaScript, and the Microsoft site for information on VBScript.

Scripting languages are interpretive, not compiled, so users need a scripting engine to run scripts. Navigator does not include an engine for VBScript; therefore, Navigator cannot be used with VBScript, as mentioned before. Both scripting engines are included with Microsoft Internet Explorer. Both engines can be licensed from their respective companies for free for use in applications. Be careful not to include any sensitive data or code in the scripts. Because the scripts are not compiled, all browsers have the capability to view the HTML document source, including the scripting.

To include a script in a HTML document, use the <SCRIPT> tag. The <SCRIPT> tag consists of the LANGUAGE, SRC, and TYPE attributes. The LANGUAGE attribute states whether the script is a VBScript or JavaScript script, SRC specifies the external file that contains the scripts not directly in the HTML, and TYPE is the file type for the external file. Listing 16.1 shows an example of VBScript and JavaScript. The easiest way to add a script that is contained within the <Script> tags is with the ActiveX Control Pad. In JavaScript only, you can also use the <A> tag with a custom URL type, allowing a script to be executed when the user clicks a hyperlink (see Listing 16.2).

Listing 16.1 JSVBSSAMP.HTM--Example of VBScript and JavaScript Using the <SCRIPT> Tag

<FORM NAME="SampleForm">
<INPUT TYPE="button" NAME="VBSButton" VALUE="VBScript"
onClick="VBSClick" LANGUAGE="VBScript">
<INPUT TYPE="button" NAME="JSButton" VALUE="JavaScript"
onClick="JSClick()" LANGUAGE="JavaScript">
</FORM>
<!--VBScript example-->
<SCRIPT LANGUAGE="VBSCRIPT">
sub VBSClick
document.SampleForm.VBSButton.value="Clicked"
alert "You clicked the VBScript button"
document.SampleForm.VBSButton.value="VBScript"
end sub
</SCRIPT>
<!--JavaScript example-->
<SCRIPT LANGUAGE="JavaScript">
function JSClick()
{
document.SampleForm.JSButton.value="Clicked"
alert("You clicked the JavaScript button.")
document.SampleForm.JSButton.value="JavaScript"
}
</SCRIPT>

Listing 16.2 AREFSAMP.HTM--Example of JavaScript Using the <A> Tag

<A HREF="javascript:alert(`This is a message')">Click here to view the message </A>

Internet Component Download

An important piece of the ActiveX Internet technology is the capability to safely download and install ActiveX controls and the needed support files on the client machine. Microsoft Internet Explorer automatically downloads and installs ActiveX controls used in HTML documents through a process called Internet Component Download. A control is downloaded only if the control is not installed on the users' machines or if the version used in the HTML is newer than the control on the users' machines. For now, the control remains on the users' machines until they remove it.

Microsoft has plans to provide a mechanism, in future releases of Internet Explorer, to delete unused controls from a user's machine.

Before an ActiveX control is installed, Internet Explorer checks for a digital signature. As mentioned earlier in this chapter, Internet Explorer has three safety levels: high, medium, and low. High will not install an unsigned control, medium asks users if they want to download an unsigned control, and low downloads a control signed or unsigned. Digital signatures were covered earlier in this chapter. Once the control is downloaded and installed, an attempt is made to register the control and its components.

For a control in an HTML page to automatically download, you need to use the CODEBASE attribute of the <OBJECT> tag. An example of this is shown in the HTML code listed in Listing 16.3.

Listing 16.3 SAMPLEIE.HTM--Using an ActiveX Control in HTML Code

<HTML>
<HEAD>
<TITLE>Sample Page</TITLE>
</HEAD>
<BODY>
<OBJECT
ID="MFCControlWin1"
WIDTH=100
HEIGHT=51
CLASSID="CLSID:A1198546-2E75-11D0-BD82-000000000000"
CODEBASE="http://www.somesite.com/somedirectory/
MFCControl.ocx#Version=1,0,0,1">
<PARAM NAME="Alignment" VALUE="1">
<PARAM NAME="CaptionProp" VALUE="Sample">
</OBJECT>
</BODY>
</HTML>

The CODEBASE attribute tells Microsoft Internet Explorer what to download and install. The CODEBASE attribute contains a reference to where the control and its supporting files can be found for downloading. If the control needs supporting files, the CODEBASE attribute points to a cabinet (CAB) file or an install (INF) file. These files, like an ActiveX control, can contain a digital signature.

A CAB file is a file that contains a compressed version of the control and any other files the control needs to install and run. It is downloaded and expanded, and the control's components are installed. To create a CAB file, use the Diamond utility provided with the Microsoft ActiveX SDK.

An INF file specifies the files that need to be downloaded and their URLs. Each file is downloaded and then installed. The INF file can provide platform independence by specifying different URLs for files that need to be downloaded for different platforms.

The CODEBASE attribute should contain a version number to allow Microsoft Internet Explorer to check whether the version of the file on the Web server is newer than the same file installed on the user's machine. To include a version number, use the Version URL fragment as shown in Listing 16.3. The numbers after the = represent the current version of the control, which can be found by looking at the properties of the control. If a version number is not used, Microsoft Internet Explorer will assume that the version of the file on the user's machine is recent enough.

For more information on the Internet Component Download, see the Internet Component Download section of the Microsoft ActiveX SDK documentation.

Electronic Commerce

Electronic commerce could be defined as doing business by using a computer. For your purposes, it is more specifically defined as doing business over the Internet. It could be shopping via one of the Internet malls, buying and selling personal computers via a reseller's Web site, online banking, or just about anything. Some of the many advantages of doing business on the Internet are that companies are able to reach people 24 hours a day, seven days a week; it's easier to reach global customers; the number of Internet users is growing rapidly; information reaches people faster through the Internet than through conventional methods; and new products can be released more quickly.

One of the big disadvantages has been security. Server security has been around for a while, but a standard technology for the secure transfer of sensitive data was missing until recently. The Secure Channel communication technology, which provides privacy, integrity, and authentication for the transfer of data from client to server and server to server, helps to solve this problem. As mentioned earlier in this chapter, SSL handles the authentication of the server, encryption of the data sent and received, and integrity of the data being sent and received. PCT provides protection against eavesdropping on a network or altering a network packet.

Before this technology was developed, credit card information and other sensitive information could not be entered online. If people wanted to buy something they saw on a merchant's Web site, they had to call the merchant to place an order. However, the current technology makes ordering possible from the Web site; shopping and ordering are done in one place. Examples of this are Dell Computer (http://www.dell.com) and Gateway2000 (http://www.gateway2000.com). Both Web sites allow users to configure their own system, do what-if price analysis, and place the order including payment information.

Another protocol, which is in the final stages of development, will further secure electronic transactions. This protocol, Secure Electronic Transactions (SET), is designed to handle secure credit card payments over the Internet using digital certificates and cryptography.

The Netscape Merchant System and the Microsoft Merchant Server are specialized systems for developing Web merchandising sites. These systems provide many built-in features to help companies create a complete shopping Web site. The features include, but are not limited to, billing, transaction processing, product updating, product searching, storefront creation, handling of secured payments, order processing, and database access. These systems are marketed as a total merchandising Web server solution.

The use of the Internet for commercial and recreational purposes expands every day. The need for reliable security is unprecedented. Fortunately, a lot of people and companies are working hard to make the Internet a reality for all types of use.

The next sections examine some of the advanced features of ActiveX.

Advanced COM

In this section, we dig into the details of the Component Object Model (COM). In the first two subsections, we deal with COM fundamentals. First we examine how C++ vtables are used to implement COM interfaces. Second we show how an ActiveX Object can aggregate another ActiveX Object to implement part of its functionality, and we discuss ATL tools for aggregation and tear-off interfaces. Finally we look at enumerators.

Using C++ vtables to Describe Interfaces

Roughly, a COM interface is a structure that contains a pointer to a structure containing pointers to functions. Rather than read that over, have a look at the C definition of IUnknown in Listing 16.4.

Listing 16.4 MSDEV\INCLUDE\UNKNWN.H--The Definition of the IUnknown Interface in C Is a Structure that Contains a Pointer to a Structure that Contains Pointers to Functions

typedef struct IUnknownVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
IUnknown __RPC_FAR * This,
/* [in] */ REFIID riid,
/* [out] */ void __RPC_FAR *__RPC_FAR *ppvObject);
ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
IUnknown __RPC_FAR * This);
ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
IUnknown __RPC_FAR * This);
END_INTERFACE
} IUnknownVtbl;
interface IUnknown
{
CONST_VTBL struct IUnknownVtbl __RPC_FAR *lpVtbl;
};

IUnknownVtbl is the structure that contains pointers to functions. It's identical to the table of virtual functions, or vtable, that C++ establishes for a class's virtual functions. IUnknown is a structure that contains a pointer to an IUnknownVtbl structure. So to simplify the definition, an interface is a structure that contains a pointer to a vtable. Because COM defines an interface this way, the definition of an interface in C++ is simpler than in C. Look at the C++ definition of IUnknown in Listing 16.5

Listing 16.5 MSDEV\INCLUDE\UNKNWN.H--The Definition of an Interface in C++ Makes Use of C++ vtables

interface IUnknown
{
public:
BEGIN_INTERFACE
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [out] */ void __RPC_FAR *__RPC_FAR *ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
END_INTERFACE
};

In C++, the interface is simply a structure (a class, really) that has virtual functions, and the vtable is implicit in the language. With that in mind, now look at various ways an ActiveX Object can implement multiple interfaces.

CNumber
: A Simple Sample
The first sample is a COM Object that implements a simple custom interface. The custom interface is INumber, shown in Listing 16.6. INumber defines a method to get the value of the number and a method to set the value of the number. CNumber is a COM Object that implements the INumber interface. The CNumber class is shown in Listing 16.7.

Listing 16.6 INUMBER.H--The INumber Interface

interface INumber : public IUnknown
{
public:
// ILrsInetUnlock methods
virtual HRESULT __stdcall GetNumber(
/* [out] */ double* pValue) = 0;
virtual HRESULT __stdcall SetNumber(
/* [in] */ double value) = 0;
};

Listing 16.7 UMBER.H--The CNumber Class

class CNumber : INumber
{
public:
// IUnknown methods
HRESULT __stdcall QueryInterface(REFIID riid,
LPVOID* lpInterface);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
// ILrsInetUnlock methods
HRESULT __stdcall GetNumber(double* pValue);
HRESULT __stdcall SetNumber(double value);
// Constructors and destructor
CNumber();
//CNumber(LPUNKNOWN pUnkOuter);
~CNumber();
private:
ULONG m_cRef;
double m_value;
};

CNumber's vtable precisely matches the definition of the INumber interface. Because INumber is derived from IUnknown (you should never encounter an interface that isn't), the vtable for CNumber can be used as the IUnknown interface as well. In Listing 16.8, you can see that QueryInterface returns this whether the INumber or IUnknown interface is requested (when riid is IID_IUnknown).

Listing 16.8 UMBER.CPP--CNumber::QueryInterface

HRESULT CNumber::QueryInterface(REFIID riid,
LPVOID* ppvInterface)
{
if(IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_INumber))
{
*ppvInterface = this;
AddRef();
return NOERROR;
}
else
{
return E_NOINTERFACE;
}
}

In some cases, you'll need more than one vtable. For example, if you need to implement multiple interfaces, such as INumber and IPersistStorage, you simply can't define a vtable that satisfies both interfaces. Or if you're going to make your object aggregatable, you need to have a separate vtable for the true IUnknown interface. This is discussed in more detail in the section about Aggregation later in this chapter.

CNumber1: Separate vTables The CNumber1 class is another COM Object that implements the INumber interface (see Listing 16.9). The CNumber1 class complicates things a little by implementing the INumber interface using the vtable of an embedded class.

Listing 16.9 UMBER1.H--The CNumber1 Class: Multiple vtables

class CNumber1 : IUnknown
{
public:
// IUnknown methods
HRESULT __stdcall QueryInterface(REFIID riid,
LPVOID* lpInterface);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
class ImpINumber : INumber
{
// IUnknown methods
HRESULT __stdcall QueryInterface(REFIID riid,
LPVOID* lpInterface);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
// ILrsInetUnlock methods
HRESULT __stdcall GetNumber(double* pValue);
HRESULT __stdcall SetNumber(double value);
// A macro to gain access to the CNumber1 "this"
// pointer from within the embedded class
#define GET_CNUMBER1(pThis) \
CNumber1* pThis = \
((CNumber1*)((BYTE*)this - \
offsetof(CNumber1, m_impINumber)));
} m_impINumber;
friend class ImpINumber;
// Constructors and destructor
CNumber1();
~CNumber1();
private:
ULONG m_cRef;
double m_value;
};

Here the ImpINumber class is defined within the CNumber1 class, and the instance m_impINumber is declared as a member of the CNumber1 class. The vtable of CNumber1 matches the IUnknown interface, and the vtable of ImpINumber matches the INumber interface. CNumber1's QueryInterface returns this when IUnknown is requested and the address of m_impINumber when INumber is requested. Listing 16.10 shows the implementation of CNumber1::QueryInterface.

Listing 16.10 UMBER1.CPP--CNumber1::QueryInterface

HRESULT CNumber1::QueryInterface(REFIID riid,
LPVOID* ppvInterface)
{
if(IsEqualIID(riid, IID_IUnknown))
{
*ppvInterface = this;
AddRef();
return NOERROR;
}
else if(IsEqualIID(riid, IID_INumber))
{
*ppvInterface = &m_impINumber;
AddRef();
return NOERROR;
}
else
{
return E_NOINTERFACE;
}
}

One complicating factor is that the class ImpINumber doesn't have immediate access to the members of CNumber1. In Listing 16.9, you see that ImpINumber is declared as a friend of CNumber1. The friend declaration gives ImpINumber access to CNumber1's members, but ImpINumber still doesn't have a pointer to CNumber1's members. Within the ImpINumber class, you declared the macro GET_CNUMBER1 that calculates the address of CNumber1 based on the address of the embedded class ImpINumber. Using GET_CNUMBER1, you can get a pointer to the CNumber1 object from within the methods of the ImpINumber class. This macro applies the technique used by MFC's METHOD_PROLOGUE set of macros.

The GET_CNUMBER1 Macro
The GET_CNUMBER1 macro is used by the ImpINumber class, which is embedded in the CNumber1 class, to gain access to the CNumber1 class's members. GET_CNUMBER1 subtracts the offset of the ImpINumber class within the CNumber1 class from the address of the ImpINumber object (this) to determine the address of the CNumber1 object.



Another complicating factor of this implementation is that INumber is derived from IUnknown, so INumber includes QueryInterface, AddRef, and Release methods. The embedded ImpINumber class must implement these methods. It does so by calling CNumber1's corresponding methods. Listing 16.11 shows ImpINumber's implementation of QueryInterface.

Listing 16.11 UMBER1.CPP--CNumber1::ImpINumber::QueryInterface

HRESULT CNumber1::ImpINumber::QueryInterface(REFIID riid,
LPVOID* ppvInterface)
{
GET_CNUMBER1(pThis);
return pThis->QueryInterface(riid, ppvInterface);
}

Now that you've established how C++ vtables can be used to implement interfaces in different ways, take a look at how vtables are used in aggregation.

Reusing ActiveX Objects with Aggregation

COM Objects don't use inheritance to reuse the implementations of existing objects. Aggregation is used instead of inheritance. In aggregation, one object creates another object and reuses its interface implementations. Where traditional inheritance has a base class and a subclass, aggregation has an aggregated object and outer object. The outer and aggregated objects are presented to the rest of the system as if they were a single object.

In this sample, you create CNumber2, which is an aggregatable implementation of INumber. Then you create CNumber3. CNumber3 will implement the IWholeNumber interface and aggregate a CNumber2 object. The aggregated CNumber2 object will provide the implementation of the INumber interface for the CNumber3 object (see fig. 16.1).

FIG. 16.1
CNumber3
aggregates CNumber2 to reuse its implementation of the INumber interface.

CNumber2
: An Aggregatable Object An object is aggregatable if it follows some rules in its implementations of the IUnknown interface.

The True IUnknown Interface
Every COM Object has an IUnknown interface implementation. When you use aggregation, you create a complex COM Object. This complex object presents only one IUnknown interface to the rest of the system. The aggregated object's IUnknown is hidden, known only to the outer object. The hidden IUnknown of the aggregated object is known as its true IUnknown interface.

NOTE: In a typical implementation of an aggregatable object, the AddRef and Release methods of interfaces other than the true IUNKNOWN are delegated to the outer object, but alternative reference counting schemes are possible.



In order to meet these requirements, the aggregatable object must have a separate vtable for its true IUnknown interface. CNumber1 already has separate vtables for IUnknown and INumber, so start building CNumber2 by modifying CNumber1. Provide a constructor that takes the outer object's IUnknown interface (see Listing 16.12 and Listing 16.13), add a member variable to store that IUnknown interface (see Listing 16.12), and use that member variable to delegate QueryInterface, AddRef, and Release calls to the outer object (see Listing 16.14).

Listing 16.12 UMBER2.H--CNumber2

class CNumber2 : IUnknown
{
public:
// IUnknown methods
HRESULT __stdcall QueryInterface(REFIID riid,
LPVOID* lpInterface);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
class ImpINumber : INumber
{
// IUnknown methods
HRESULT __stdcall QueryInterface(REFIID riid,
LPVOID* lpInterface);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
// ILrsInetUnlock methods
HRESULT __stdcall GetNumber(double* pValue);
HRESULT __stdcall SetNumber(double value);
// A macro to gain access to the CNumber2 "this"
// pointer from within the embedded class
#define GET_CNUMBER2(pThis) \
CNumber2* pThis = \
((CNumber2*)((BYTE*)this - \
offsetof(CNumber2, m_impINumber)));
} m_impINumber;
friend class ImpINumber;
// Constructors and destructor
CNumber2();
CNumber2(LPUNKNOWN);
~CNumber2();
private:
ULONG m_cRef;
double m_value;
LPUNKNOWN m_pUnkOuter;
};

Listing 16.13 UMBER2.CPP--CNumber2::CNumber2(LPUNKNOWN)

CNumber2::CNumber2(LPUNKNOWN pUnkOuter)
{
m_cRef = 0;
m_value = 0.0;
if(pUnkOuter == NULL)
m_pUnkOuter = this;
else
m_pUnkOuter = pUnkOuter;
}

Listing 16.14 UMBER2.CPP--CNumber2::ImpINumber::QueryInterface(), AddRef(), and Release() Are Delegated to pThis->m_pUnkOuter, Instead of pThis

HRESULT CNumber2::ImpINumber::QueryInterface(REFIID riid,
LPVOID* ppvInterface)
{
GET_CNUMBER2(pThis);
return pThis->m_pUnkOuter->QueryInterface(riid,
ppvInterface);
}
ULONG CNumber2::ImpINumber::AddRef()
{
GET_CNUMBER2(pThis);
return pThis->m_pUnkOuter->AddRef();
}
ULONG CNumber2::ImpINumber::Release()
{
GET_CNUMBER2(pThis);
return pThis->m_pUnkOuter->Release();
}

CNumber3: The Outer Object Now that you've created an aggregatable object that implements INumber, you create another object that implements IWholeNumber and uses aggregation to provide an implementation of INumber (see Listing 16.15).

Listing 16.15 INUMBER.H--The IWholeNumber Interface

interface IWholeNumber : public IUnknown
{
public:
// ILrsInetUnlock methods
virtual HRESULT __stdcall GetNumber(
/* [out] */ long int* pValue) = 0;
virtual HRESULT __stdcall SetNumber(
/* [in] */ long int value) = 0;
};

For simplicity, you're not going to make CNumber3 aggregatable, so declare the class with a single vtable for both its true IUnknown and the IWholeNumber interface. Add the member variable m_pUnkNumber to hold the true IUnknown of the aggregated CNumber2 object. You also provide an Init method so that the aggregation can be accomplished separate from the construction of the object (see Listing 16.16 and Listing 16.17). Modify the class factory so that it calls Init after constructing the object (see Listing 16.18).

Listing 16.16 UMBER3.H--CNumber3 Adds Init() and m_pUnkNumber

class CNumber3 : IWholeNumber
{
public:
// IUnknown methods
HRESULT __stdcall QueryInterface(REFIID riid,
LPVOID* lpInterface);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
// IWholeNumber methods
HRESULT __stdcall GetNumber(long int* pValue);
HRESULT __stdcall SetNumber(long int value);
// Constructor and destructor
CNumber3();
~CNumber3();
BOOL Init();
private:
ULONG m_cRef;
LPUNKNOWN m_pUnkNumber; // Aggregated number
};

Listing 16.17 UMFACT.CPP--CNumber3ClassFactory::CreateInstance Calls CNumber3::Init after Constructing the CNumber3 Object

// Create the object
CNumber3* pObj = NULL;
pObj = new CNumber3();
IncrementObjectCount();
if(NULL == pObj)
{
_ASSERT(FALSE);
DecrementObjectCount();
return E_OUTOFMEMORY;
}
// Call the initializer
if(! pObj->Init())
{
_ASSERT(FALSE);
delete pObj;
return E_FAIL;
}

Listing 16.18 UMBER3.CPP--CNumber3::Init Creates the Aggregated Object

BOOL CNumber3::Init()
{
_ASSERT(m_pUnkNumber == NULL);
HRESULT hr = CoCreateInstance(CLSID_Number2, this, CLSCTX_INPROC_SERVER, IID_IUnknown, (LPVOID*)&m_pUnkNumber);
if(FAILED(hr) || (m_pUnkNumber == NULL))
{
_ASSERT(FALSE);
return FALSE;
}
return TRUE;
}

CNumber3::Init creates the aggregated object by calling CoCreateInstance and passing its true IUnknown interface as an argument. CoCreateInstance will call the class factory's CreateInstance method, which will construct the CNumber2 object using the new constructor. CNumber2 now has the true IUnknown of the outer object (passed from CoCreateInstance to the class factory's CreateInstance method and then to the new constructor), which it stores as m_pUnkOuter. The true IUnknown of the CNumber2 object is returned back through CoCreateInstance, and the outer CNumber3 object has the true IUnknown of the aggregated object, which CNumber3 stores as m_pUnkNumber (see fig. 16.2).

FIG. 16.2
CNumber2
and CNumber3 hold pointers to each other's true IUnknown interfaces.

Now when CNumber3's QueryInterface is called, it will return this for IUnknown or IWholeNumber, but when INumber is requested, it will pass the call on to the aggregated CNumber2 object through that object's true IUnknown, m_pUnkNumber (see Listing 16.19).

Listing 16.19 UMBER3.CPP--CNumber3::QueryInterface

HRESULT CNumber3::QueryInterface(REFIID riid,
LPVOID* ppvInterface)
{
if(IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_IWholeNumber))
{
*ppvInterface = this;
AddRef();
return NOERROR;
}
else if(IsEqualIID(riid, IID_INumber))
{
return m_pUnkNumber->QueryInterface(riid,
ppvInterface);
}
else
{
return E_NOINTERFACE; }
}

Aggregation and Tear-Off Interfaces in ATL ATL provides tools to implement aggregation. It also provides tools to implement a kind of temporary aggregation called tear-off interfaces. In each of these cases, the outer object declares the aggregated object in its COM_MAP through one of the COM_INTERFACE_ENTRY macros.

Aggregation
There are four steps to aggregating an object using ATL. First you make sure that the aggregated class has not explicitly denied aggregation. It can do this through the use of the macro DECLARE_NOT_AGGREGATABLE. If the ATL class doesn't explicitly deny aggregation, it's aggregatable. Second declare a member variable in the outer class to hold the true IUnknown of the aggregated object. If you are not using one of the automatic aggregation macros (COM_INTERFACE_ENTRY_AUTOAGGREGATE or COM_INTERFACE_ENTRY_AUTOAGGREGATEBLIND), you have to override FinalConstruct and create the aggregated object by using CoCreateInstance, like you did in the CNumber3 sample's constructor. When using the automatic macros, simply initialize the member variable to NULL.

Third override the outer class's FinalRelease to release the aggregated object. This is true whether you use the automatic aggregation macros or one of the other macros.

Finally declare the aggregation in the COM_MAP. The four macros listed below are available for doing this. The macro arguments are described in Tables 16.1 through 16.4.

COM_INTERFACE_ENTRY_AGGREGATE(iid, pUnknown)

This macro is used to delegate a specific interface to an aggregated object.

Table 16.1 COM_INTERFACE_ENTRY_AGGREGATE Arguments
Argument Meaning
iid The ID of the interface that is to be delegated to the aggregated object.
pUnknown The aggregated object's true IUnknown interface. The aggregated object is created in the outer object's FinalConstruct method

COM_INTERFACE_ENTRY_AGGREGATE_BLIND(pUnknown)

This macro is used to expose all of an aggregated object's interfaces from the outer object.

Table 16.2 COM_INTERFACE_ENTRY_AGGREGATE_BLIND Arguments
Argument Meaning
pUnknown The aggregated object's true IUnknown interface. The aggregated object is created in the outer object's FinalConstruct method.

COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, pUnknown, clsid, cs)

This macro is used to aggregate an object on demand, delegating the specified interface to that object.
Table 16.3 COM_INTERFACE_ENTRY_AUTOAGGREGATE Arguments
Argument Meaning
iid The ID of the interface that is to be delegated to the aggregated object.
pUnknown The aggregated object's true IUnknown interface. The outer object initializes this to NULL, and the first query for this interface creates the aggregated object.
clsid The ID of the class that is to be aggregated.
cs A critical section used for synchronization.

COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(pUnknown, clsid, cs)

This macro is used to aggregate an object on demand, exposing all of the interfaces of that object.

Table 16.4 COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND Arguments
Argument Meaning
pUnknown The aggregated object's true IUnknown interface. The outer object initializes this to NULL, and the first query for one of the aggregated object's interfaces creates the aggregated object.
clsid The ID of the class that is to be aggregated.
cs This is a critical section used for synchronization.


Tear-Off Interfaces
Tear-off interfaces are similar to aggregation, except that the inner object is held only until its interface is released. Remember that in aggregation, the aggregated (inner) object is released in the destructor, or in FinalRelease for ATL aggregation. This means that the aggregated object exists for the life of the outer object. Even using ATL's automatic aggregation, the aggregated object, once created, remains until the outer object is destroyed. Tear-off interfaces, on the other hand, are created when they're needed and destroyed when they're released. They're implemented differently from the way standard aggregation is implemented on both sides of the relationship. The tear-off interface must be an object specifically written to provide tear-off interfaces, and it can provide them only for a specific outer class. Now take a look at how it's done.

First declare a new class that is derived from CComTearOffBase and the interfaces that this class will provide. To do this, the tear-off class must specify the owner, or outer, class. Listing 16.20 shows the declaration of a class that implements a tear-off interface.

Listing 16.20 Example Declaration of a Tear-Off Class

class CTearOff: public ISomeInterface,
public CComTearOffObjectBase<CSomeOuterClass>
{ public:
CTearOff() {}
STDMETHOD(SomeMethod)()
{
return S_OK;
}
BEGIN_COM_MAP(CTearOff)
COM_INTERFACE_ENTRY(ISomeInterface)
END_COM_MAP()
}

Next the outer class declares the tear-off interface in its COM_MAP. The two macros listed below are available for doing this. The arguments to these macros are described in Tables 16.5 and 16.6.

COM_INTERFACE_ENTRY_TEAR_OFF(iid, class)

This macro is used to declare a true tear-off interface.

Table 16.5 COM_INTERFACE_ENTRY_TEAR_OFF Arguments
Argument Meaning
iid The ID of the interface that is delegated to the tear-off interface object.
class The class of the tear-off interface object.

COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid, class, pUnknown, cs)

This macro is a variation of a tear-off interface in which the outer object caches the tear-off interface by holding its IUnknown interface. The outer object must release the tear-off interface in FinalRelease. This is functionally equivalent to automatic aggregation, except that it uses a tear-off interface instead of an aggregatable object.

Table 16.6 COM_INTERFACE_ENTRY_CACHED_TEAR_OFF Arguments
Argument Meaning
iid The ID of the interface that is delegated to the tear-off interface object.
class The class of the tear-off interface object.
pUnknown The IUnknown interface of the tear-off interface object.
cs A critical section used for synchronization.


You've looked at aggregation up close, and you've looked at how to do aggregation and tear-off interfaces with ATL. Next take a look at enumerators.

Enumerators: An Interface Pattern for Sets

An enumerator is an interface that provides access to a series of elements and fits a specific pattern. The pattern is shown in Listing 16.21.

Listing 16.21 Enumerator Pattern

interface IEnum<type> :

{
STDMETHOD(Next)(ULONG celt, <type>* rgelt,
ULONG* pceltFetched);
STDMETHOD(Skip)(ULONG celt);
STDMETHOD(Reset)(void);
STDMETHOD(Clone)(IEnum<type>** ppEnum);
}

An enumerator has four methods: Next, Skip, Reset, and Clone.

Next gets the next celt elements in the enumerator. rgelt is the array of elements that is returned. The memory is provided by the caller and must be large enough to hold the requested number of elements. pceltFetched returns the number of elements that were fetched, which will always be equal to or less than celt. It will be less than celt when the number of elements from the current position to the end of the enumerator is less than the number of requested elements. Next returns S_OK when the requested number of elements are returned and S_FALSE when less than the requested number are returned.

Skip moves the current position by celt elements. Skip returns S_OK when the requested number of elements are skipped and S_FALSE when less than the requested number are skipped.

Reset returns the enumerator to its original state, with the current position at the beginning.

Clone copies the enumerator in its current state.

In this sample, you implement an enumerator for the class IDs of the four classes that you used to implement the INumber and IWholeNumber interfaces. Because class IDs are GUIDs, you implement the IEnumGUID interface, which is defined in MSDEV\INCLUDE\COMCAT.H. The enumerator class, CNumbers, will hold the class IDs in m_guids, and m_current will maintain the current position. CNumbers is shown in Listing 16.22.

Listing 16.22 UMBERS.H--The CNumbers Enumerator Class

class CNumbers : IEnumGUID
{
public:
// IUnknown methods
HRESULT __stdcall QueryInterface(REFIID riid,
LPVOID* lpInterface);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
// IEnumGUID methods
HRESULT __stdcall Next(ULONG celt, GUID* rgelt,
ULONG* pceltFetched);
HRESULT __stdcall Skip(ULONG celt);
HRESULT __stdcall Reset(void);
HRESULT __stdcall Clone(IEnumGUID** ppenum);
// Constructors and destructor
CNumbers();
~CNumbers();
private:
ULONG m_cRef;
GUID m_guids[4];
int m_current;
};

In the implementation of Next, you return the requested number of class IDs from the current position, but not past the end of the array (see Listing 16.23).

Listing 16.23 UMBERS.CPP--Next

HRESULT CNumbers::Next(ULONG celt, GUID* rgelt,
ULONG* pceltFetched)
{
ULONG celtFetched = 0;
for(ULONG ii = 0; ii < celt; ii++)
{
if(m_current < 4)
{
rgelt[ii] = m_guids[m_current];
m_current ++;
celtFetched ++;
}
else
break;
}
if(pceltFetched)
*pceltFetched = celtFetched;
if(celtFetched == celt)
return S_OK;
else
return S_FALSE;
}

Notice that the return argument pceltFetched is optional. When it's NULL, the value isn't returned. Technically, the caller can send NULL only when a single element is requested.

Skip and Reset are straightforward. Skip moves the current position forward, and Reset sets the current position to 0.

Clone is a little different. It must create another object that is a copy of itself, including the current position. One way to do this is to call CoCreateInstance, which will go through the class factory so that object counts are handled there. However, CoCreateInstance has some overhead that isn't necessary in this case, so you do what the class factory does inside the Clone method; you construct the enumerator and increment the object count. Having created the object, use Skip to set its current position to the current position of the enumerator being cloned. The Clone method is shown in Listing 16.24.

Listing 16.24 UMBERS.CPP--Clone

CNumbers* pNumbers = new CNumbers();
if(pNumbers == NULL)
{
_ASSERT(FALSE);
return E_OUTOFMEMORY;
}
IncrementObjectCount();
HRESULT hr = pNumbers->QueryInterface(IID_IEnumGUID, (LPVOID*)ppenum);
if(FAILED(hr))
{
_ASSERT(FALSE);
delete pNumbers;
DecrementObjectCount();
*ppenum = NULL;
return E_UNEXPECTED;
}
hr = (*ppenum)->Skip(m_current);
if(hr != S_OK)
{
_ASSERT(FALSE);
delete pNumbers;
DecrementObjectCount();
*ppenum = NULL;
return E_UNEXPECTED;
}

Any enumerator can be implemented pretty much the same way. Your internal data structure can be an array, a list, or anything else that has a logical order.

About the Samples

The samples from this chapter are in the DLL project Number, described in Table 16.7.
Table 16.7 Sample Source Code
File Contents
Number.mdp The project workspace.
Number.mak The project makefile.
inumber.h The definition of the INumber and IWholeNumber interfaces.
numcid.h The definition of the class IDs for each of the objects.
number.h The definition of CNumber.
number.cpp The implementation of CNumber.
number1.h The definition of CNumber1.
number1.cpp The implementation of CNumber1.
number2.h The definition of CNumber2.
number2.cpp The implementation of CNumber2.
number3.h The definition of CNumber3.
number3.cpp The implementation of CNumber3.
numbers.h The definition of the enumerator sample class.
numbers.cpp The implementation of the enumerator sample class.
numguid.cpp The local instances of the class IDs and interface IDs.
numfact.h The definitions of the class factories for all four classes.
numfact.cpp The implementation of the class factories and functions that are exported by the DLL
number.def The definition of the DLL exports.
number.reg Manual registration file.


You can build the sample from the command line or from your IDE. After building number.dll, modify number.reg so that it contains the correct path to the DLL, and register the objects using regedit.exe number.reg or regedt32.exe number.reg.

To execute the sample, you need an executable project. TestNum is a console application that serves that purpose. Build that project, and run TESTNUM.EXE from your debugger.

Distributed Component Object Model (DCOM)

Microsoft has taken the Component Object Model, and therefore ActiveX, to another level with the introduction of the Distributed Component Object Model (DCOM). DCOM extends COM by enabling application objects to communicate and run across networks, including the Internet, intranets, LANs, and WANs. It allows your application objects to be distributed over multiple computers, handling the communications among the objects as well as the instantiation and execution of the objects remotely. The application uses the objects as if the application and the objects were running on the same machine. DCOM is Microsoft's answer to the Common Object Request Broker Architecture (CORBA).

DCOM extends applications across networks, including the Internet, allowing components to run on different machines, by building on the Remote Procedure Call (RPC) technology. DCOM allows components to communicate with each other via any network protocol, including TCP/IP and IPX/SPX. DCOM gives you control of security features, such as access permissions and domain authentication, and can be used to launch applications on other machines. This capability enables developers to develop truly distributed systems, without worrying about network programming, system compatibility, or integration of different components.

Since ActiveX is based on COM, the language neutrality of ActiveX is extended to DCOM. ActiveX components built with different languages can communicate over a network.

DCOM is designed to run on multiple platforms. Microsoft is openly licensing DCOM to other software companies to run on all major operating systems. Microsoft is also working with the Internet standards committees to make DCOM an Internet standard. To view a draft of this standard, go to http://ds1.internic.net/ds/dsintdrafts.html and search for DCOM. The exact address of the document changes as the version of the draft changes.

DCOM is currently integrated with the Microsoft Windows NT 4.0 operating system and is in beta as an add-on for Microsoft Windows 95. It should be available for the Macintosh as a beta in the first quarter of 1997. Versions for different flavors of UNIX and Legacy systems, including mainframe systems running CICS and IMS, will be available sometime after that. UNIX support for DCOM will be provided by Digital, not Microsoft, through its ObjectBroker product. To find more information on the ObjectBroker product, look at the ObjectBroker Web site at http://www.digital.com/info/objectbroker/.

To configure server and client applications to use DCOM on Microsoft Windows NT or Microsoft Windows 95, use the following steps:

  1. Register the server application on both the server machine and the client machine. The server application does not need to reside on the client machine, although registering the server may involve running the server setup program, or running the server application, on the client machine.

  2. If the server application uses custom interfaces, the marshaling code needs to be installed on both the server and client machines.

  3. Server applications that support vtable binding need to have their type libraries installed on the server and client machines.

  4. Alter the registry settings for the server on the server and client machines manually or by using the DCOMCNFG or OLE Viewer tool.

DCCOMCNFG is available as part of the Microsoft Windows NT operating system and is located in the Windows NT System32 directory. The DCOMCNFG utility for Microsoft Windows 95 can be downloaded from the Windows 95 DCOM page of Microsoft's Web site (http://www.microsoft.com/oledev/olemkt/oledcom/dcom95.htm). The program used for installing DCOMCNFG on Microsoft Windows 95 will install the DCOMCNFG utility in the Windows System directory. To change the registry settings using DCOMCNFG, follow these steps:

  1. Execute the DCOMCNFG.EXE file on the client machine. You will see a list of all registered applications in the Applications list box of the Applications tab.

  2. If the name of your server is registered but does not appear, it is probably listed by its CLSID. To determine the CLSID of your server, you need to view the registry. To do this, close DCOMCNFG, and then open the Registry Editor by running regedit.exe. Open the HKEY_CLASSES_ROOT folder, and look for your server's ProgID. All the ProgIDs for all registered systems will be listed with their objects on the left. For example, if you were searching for MFCServer, you would look for MFCServer.Tracker. Click the + by the ProgId, and then select the CLSID subkey below it. The CLSID is displayed in the Data section of the right window. After you locate the CLSID, close the Registry Editor, and go back into DCOMCNFG.


    CAUTION
    Use caution when looking at or editing the registry. If changes are not made correctly, they can cause your machine to function improperly and, in some cases, crash. You should always back up the registry prior to changing it. Information on backing up the registry can be found in the Registry Editor's help.

  3. Select your server from the Applications list box, and then click the Properties button. This action brings up the Properties dialog.

  4. Select the Location tab. Select the Run application on the following computer check box, and specify the name of the server machine. Clear the other check boxes, and then click the Apply button to save the changes. Close the Properties dialog, and then close DCOMCNFG.

  5. Run DCOMCNFG on the server machine. Select your server's name (or CLSID) from the Applications list box, and then click the Properties button to bring up the Properties dialog.

  6. Select the Security tab. You can use the default access and launch permissions or create custom permissions.

  7. Make sure that SYSTEM is in the launch and access permissions. If you are using the default access or launch permissions, use the Default Security tab to make sure these permissions contain SYSTEM.

  8. Add the user on the client machine to the access and launch permissions. To add users and groups to custom permissions, click the Edit button to bring up the Registry Value Permissions dialog. To add users or groups to default permissions, close the Properties dialog. Then select the Default Security tab, and click the Edit Default button in the Default Access Permissions or Default Launch Permissions frame to bring up the Registry Value Permissions dialog for the Default Access Permissions or Default Launch Permissions, respectively. Click the Add button to display the Add Users and Groups window. Select the user or group from the Names list box, and then click the Add button to add it. When you are done, click the OK button, and then click the OK button on the Registry Value Permissions dialog. Click the Apply button on the Properties dialog to save your changes. Contact your system administrator if you need help setting permissions for users or groups.

The OLE Viewer can be found on the Web at http://www.microsoft.com/oledev/olecom/oleview.htm, and it is included with the Microsoft ActiveX SDK in the Bin directory and with Microsoft Visual C++ version 5.0 in the Bin directory. The Web site usually contains the latest version. To change the registry settings using the OLE Viewer, follow these steps:

  1. Run OLEVIEW.EXE on the client machine. Make sure Expert Mode is selected on the View menu.

  2. Click the + beside All Objects in the left pane of the main window. All registered objects will appear under All Objects. If the name of your server does not appear, and it is registered, it is probably listed by its CLSID. If you need to find the CLSID of your server, refer to item 1 in the DCOMCNFG steps.

  3. Locate your server by name or CLSID, and select it. This action displays a group of tabs in the right pane.

  4. Select the Implementation tab, and clear the Path to the Implementation text box of the Local Server subtab.

  5. Click the Activation tab, and enter the name of the server machine in the Remote Machine Name text box.

  6. Close the OLE Viewer.

  7. Run OLEVIEW.EXE on the server machine. Make sure Expert Mode is selected on the View menu.

  8. Click the + beside All Objects in the left pane of the main window. Select your server's name or CLSID.

  9. Use the Launch Permissions and Access Permissions tabs to set permissions for the server. You can use the default access and launch permissions or create custom permissions.

  10. Make sure that SYSTEM is in the launch and access permissions. If you are using the default access or launch permissions, select System Configuration from the File menu to make sure these permissions contain SYSTEM.

  11. Add the user on the client machine to the access and launch permissions. To add users and groups to custom permissions, click the Modify button on the appropriate tab to bring up the Launch Permissions or Access Permissions dialog. Click the Add button to display the Add Users and Groups window. Select the user or group from the Names list box, and then click the Add button to add it. When you are done, click the OK button, and then click the OK button on the Launch Permissions dialog. To add users or groups to the default permissions, select System Configuration from the File menu to bring up the System Configuration dialog. Select the appropriate default permissions tab-- Default Launch Permissions or Default Access Permissions--and click the Modify button to bring up the Global Launch Permissions dialog or Global Access Permissions dialog, respectively. Click the Add button to display the Add Users and Groups window. Select the user or group from the Names list box, and then click the Add button to add it. When you are done, click the OK button, and then click the OK button on the Global Access or Global Launch Permissions dialog.

You have some special considerations when using Microsoft Windows 95. First you need to install the DCOM add-on for Microsoft Windows 95. Information about downloading and installing it can be found on Microsoft's OLE page, http://www.microsoft.com/oledev/. To enable incoming calls, you will also need to change the EnableRemoteConnections setting in the Microsoft Windows 95 registry from "N" to "Y" on the server machine. This can be done using the OLE Viewer by selecting File, System Configuration, and then clicking the Enable Remote Connection (Windows 95 only) check box on the System Settings tab. DCOM on Microsoft Windows 95 does not support remote activation of a server because all processes run using the security of the currently logged-on user, making it impossible for a client machine to start a process. A server application running remotely on a Microsoft Windows 95 server machine will have to be started manually or some other way prior to the client machine accessing it; therefore, the launch permissions have no effect on Microsoft Windows 95.

More information on DCOM, and other articles, can be found in the Knowledge Base article Q158582. Information can also be found on Microsoft's Web sites at http://www.microsoft.com/oledev/, http://www.microsoft.com/ntserver/, and http://www.microsoft.com/intdev/.

OLE DB

Microsoft has introduced an object driven data access technology called OLE DB. OLE DB is a set of APIs that will provide ActiveX interfaces to all forms of data throughout the enterprise.

OLE DB allows access to non-SQL type data as well as to SQL type data. Open Database connectivity (ODBC) drivers are used only to access SQL databases, such as DB2 and SQL Servers and still provide one of the best ways to access SQL databases. Currently, OLE DB provides only a layer on top of ODBC drivers for access to SQL databases; OLE DB does not access them directly. OLE DB will eventually be capable of accessing all types of data, including non-database data such as spreadsheets and e-mail. OLE DB, like ODBC, uses a common interface for accessing data. OLE DB has a modular design based on COM.

The flow of an OLE DB and an ODBC application is the same, except for some slight differences. Most of the differences are due to the fact that OLE DB is object-oriented, whereas ODBC is not, and because ODBC data is application-owned, whereas OLE DB uses shared data. Table 16.8 summarizes some of these differences.
Table 16.8 OLEDB versus ODBC
OLEDB ODBC
Session Objects Connection Handles
Shared data object Application data buffer
Accessors Descriptor Handles


OLE DB uses Session Objects in place of ODBC's Connection Handles. A separate Connection Handle is needed for each concurrent transaction. With OLE DB, an application can have several Session Objects per data connection, allowing one data connection for multiple concurrent transactions.

OLE DB uses rowsets--in place of ODBC's result sets--which offer some advantages. ODBC reads data into an application's memory space for processing. OLE DB references data directly; data is not copied to the application's memory space. Referencing data directly saves on memory and processing time. If an ODBC application wants to know whether a second process or application has changed the data, the application must requery the data. If two or more objects are using the same data in an OLE DB application and one object changes the data, the other objects receive a notification that the data has changed.

Basically, the memory buffer for the data is removed from the application and placed in a stand-alone shared data object. Applications access this shared data object using Accessors, which are similar to ODBC's Descriptor Handles. The application can use pointers to the data rather than an actual copy of the data and can share the data, providing quicker data access. An Accessor uses an array of binding structures. Each structure describes a column of data, so the array describes the entire table. The Accessor allows all needed columns to be bound at once instead of requiring repeated calls to SQLBindCol, allowing for more efficient binding.

To find more information and to follow the development of OLE DB, check out Microsoft's OLE DB Web site at http://www.microsoft.com/OLEDB/. This site is one of the best sources of information for OLE DB. You can find the OLE DB SDK kit, white papers on OLE DB, and tools for OLE DB development. Programming magazines and other computer magazines are also a good source of information.

Threading

Every process has one or more threads. A thread is code that is to be sequentially executed within a process. A process always has at least one thread, the primary thread, and can have multiple threads in addition to the primary thread. In a data entry routine, the primary thread might handle displaying the data being entered while another thread updates the database once the data is entered. Threads can have different priorities. A thread with a higher priority can interrupt a thread with a lower priority. A thread will continue executing until one of the following happens:

Each individual thread can run separate sections of code; one thread might perform different functions within a process. Multiple threads can run the same section of code. When multiple threads run the same section of code, each thread maintains a separate code stack. Separate code stacks prevent the threads from getting lost and tramping on each other. A process's global variables and resources are shared by every thread in the process. These global variables have to be used with caution; the values can be changed by another thread at any time.

Single-Threading and Multithreading

In a single-threaded process, only one action can happen at a time. This approach was used in the older operating systems such as Windows 3.1. All incoming calls to the thread are received through the Windows message queue.

Windows 95 and Windows NT introduced multithreading, which is more efficient than single-threading. Multithreading allows an application to create more than one thread of execution so that process-intensive applications do not stall or freeze an application while waiting for the process to complete its execution. A single-threaded process will just wait until the action is complete. Multithreading presents some issues. It adds complexity to coding, testing, and debugging. Multithreaded applications must avoid deadlocks and races. Deadlocks happen when each thread is waiting for the other to do something, hanging the application. Races happen when a thread finishes before another thread it depends on finishes. Races cause a thread to use garbage data because the dependent thread has not provided legitimate values.

Multithreading, in its simplest form, is referred to as the apartment model. A process that uses the apartment model uses multiple threads, but each COM Object lives in only one thread, or apartment, and cannot be directly accessed by other threads. A more sophisticated form of multithreading is the free-threading model. This model allows multiple threads to access each COM Object simultaneously. A multithreaded process can consist of one of these models or a combination of both.

Apartment Model
The apartment model is sometimes referred to as a single-threaded apartment model since it is a group of separate apartments or threads. Technically, all threading models use apartments. The single-thread-per-process model, referred to here as single-threaded model, is really one apartment for the entire process, whereas the apartment model is a group of apartments. Free-threading is sometimes referred to as a multithreaded apartment model since it consists of multiple threads in one apartment. For the purpose of this section, we will refer to single-threaded processes as single-threaded processes, and we will refer to multithreading processes as either apartment models or free-threading models. The apartment model is a multithreaded process that contains only one COM Object per thread. The COM Object lives inside a group that is referred to as an apartment. All incoming calls are sent through the Windows message system. OLE synchronizes these calls, so the process can receive calls while making calls. Each thread has its own apartment, which is directly accessible by only one thread. Each apartment can receive direct calls only from the thread that belongs to the apartment. Call parameters need to be marshaled between apartments. OLE handles marshaling between apartments through the Windows messaging system.

Free-Threading Model
As mentioned earlier, a more sophisticated form of multithreading is the free-threading model. This model allows multiple threads to access each COM Object simultaneously. Free-threading is sometimes referred to as the multithreaded apartment model since the apartment contains multiple threads. A free-threading process is a multithreaded process that allows several threads to access a COM Object. Each COM Object is simultaneously accessible by more than one process thread. COM Objects are responsible for synchronizing incoming calls when using the free-threaded model; they must have their own message handlers. Calls are not passed through the Windows messaging system, nor does ActiveX synchronize the calls, since methods may be called from different processes simultaneously.

Apartments cannot receive calls while making calls; asynchronous calls are converted to synchronous calls in free-threaded apartments. Objects must be able to handle calls to their methods from other threads at any time and to handle calls from multiple threads simultaneously.

All threads are contained within a single multithreaded apartment. Since all threads reside in one apartment, there can be only one multithreaded apartment per process. Parameters are passed directly to any thread in the apartment. Data does not need to be marshaled between threads since all free-threads reside in one apartment.

You need to make sure the process's code is thread-safe. Thread-safe means making sure the objects, data, and code owned by the thread are used by only that thread and not by other threads. If a multithreaded application is not thread-safe, the application will become confused and will not function properly. These problems can be difficult to debug.

Mixing Apartment and Free-Threading Models
Apartment and free-threading models can be combined within a single process. You can have only one free-threaded apartment, but you can have one or more single-threaded apartments. Interface pointers and other data must be marshaled between apartments. Calls to objects within single-threaded apartments will be synchronized with Windows messages, whereas calls to objects within the free-threaded apartment will not be synchronized at all. OLE threading models provide the support for the interaction between clients and servers with different threading models. As far as the calling object is concerned, all calls to objects outside the object behave the same, regardless of how the object being called is threaded. To the called object, the calls it receives behave identically, regardless of the callers threading model.

Interaction between client and out-of-process servers is straightforward, whether their threading models are the same or different. The client and server are in different processes, and OLE handles the communication between these processes for you, using standard marshaling and RPC.

The interaction between clients and in-process servers present some issues.

In-process servers do not call COM initialization routines, so you need to set the threading in the registry by adding the ThreadingModel named value to the InprocServer32 key of the server. You can set the threading manually, or you can use the OLE Viewer. Using the OLE Viewer to set the threading is as easy as setting the Threading Model on the Inproc Server subtab of the Implementation tab.

The coding issues for in-process servers are too numerous and involved for the scope of this chapter. For detailed information on coding issues, see the "In-process Server Threading Issues" topic in the ActiveX SDK help files.

Choosing a Multithreading Model
Your decision about which multithreading model to use depends on the function of the object. For example, when creating a thread that interacts with a user, you may want to use the apartment model since incoming OLE calls can be processed with the Windows messages received. Because the apartment model is less complex, supporting it is easier than supporting the free-threading model. OLE provides synchronization through messaging for the apartment model, whereas the free-threading model needs to provide its own synchronization and storage for thread specific data. More information on threading is available. To learn more about threads in general, refer to the ActiveX SDK help files under "Processes and Threads." Specific coding help can be found in the ActiveX SDK help files and in the help files for Visual C++. The Knowledge Base on Microsoft's Web site contains information to help you understand basic threading in general, as well as specific coding information. Many third party books that discuss threading are also available.

Engineering for the Future

For the past two years, Microsoft has been rapidly releasing new tools for ActiveX development. Visual Basic 5.0 brings significant changes to the VB world. VB can now create ActiveX controls, ActiveX documents, and a whole host of servers and components. The fact that VB now supports a native code compiler also makes it attractive.

Visual Basic 5.0's language is now Visual Basic for Applications (VBA), so code written for Microsoft Office 97 products is the same as code written in native VB. Office 97 is a significant improvement over Microsoft Office 95. For starters, Microsoft Word now uses VBA rather than the nonstandard WordBasic. Using ActiveX controls in Office 97 is much easier than it was in Office 95. You basically select a registered control and drop it in the document, spreadsheet, and so on. Office 97 also has some VB improvements since it now uses VBA 5.0.

A new visual language tool has arrived from Microsoft--Visual J++. Visual J++ is Microsoft's Java development tool. It offers a visual development environment complete with a compiler. Microsoft also offers a Java SDK on its Web site. Java SDK can be used with or without Visual J++ as a front end. The Java SDK includes access to the Windows APIs, allowing developers to create sophisticated Windows systems.

Along with the languages used to build applications, advances are being made in handling the data. Microsoft has released an object-driven data access tool called OLE DB. OLE DB is a set of APIs that provide ActiveX interfaces to all forms of data throughout the enterprise. Along with providing access to databases, OLE DB will provide access to such things as text in an e-mail or spreadsheet. Distributing data over the Internet and an intranet is getting easier. Microsoft's Advanced Data Connector (ADC) allows the developer to create applications that interact with databases over the Internet or an intranet. ADC allows you to cache data on the client machine, manipulate that data, and integrate that data with data-aware ActiveX controls.

Microsoft has also developed a technology for distributing applications over networks, including the Internet and an intranet, called Distributed Component Object Model (DCOM). DCOM extends ActiveX by enabling application objects to communicate directly over networks. To make the development, deployment, and management of server applications over a network, intranet, or the Internet, Microsoft has released Transaction Server. Transaction Server insulates the developer from dealing with system issues such as connectivity, security, thread management, and data management.

For the design, development, and management of a Web site, Microsoft offers FrontPage. FrontPage provides Web site developers with the tools needed to completely develop a Web site. Microsoft offers another package, ActiveX Control Pad, for developing individual Web pages using a form approach similar to VB. This package is a good way to quickly test ActiveX controls for the Internet. For developing Web applications, Microsoft offers Visual InterDev. Visual InterDev provides a visual development environment that includes the tools necessary for developing a Web application in its entirety.

Microsoft is trying to develop solutions to leverage companies' existing knowledge base so that developing ActiveX components has a shorter learning curve. Microsoft is upgrading its existing tools so the tools can be used for ActiveX development. When Microsoft releases new tools, those tools have the look and feel of a company's existing tools. Companies that have expertise with Microsoft products will find the task of moving to ActiveX easier.

Table 16.9 lists the Web addresses that provide information on the products mentioned. To keep on top of Microsoft's new and updated products, you can frequently visit Microsoft's Web page at http://www.microsoft.com, especially the Internet developer page, http://www.microsoft.com/intdev/, and the Microsoft Developer Network(MSDN) page at http://www.microsoft.com/msdn/. MSDN CDs are also a good source of information.

Table 16.9 Where to Find Information on the Products Discussed
Product Address
Visual Basic http://www.microsoft.com/vbasic
Office 97 http://www.microsoft.com/office
VBA http://www.microsoft.com/vba
Visual J++ http://www.microsoft.com/visualj
Java SDK http://www.microsoft.com/java/sdk
OLE DB http://www.microsoft.com/oledb
ADC http://www.microsoft.com/adc
DCOM http://www.microsoft.com/oledev
Transaction Server http://www.microsoft.com/transaction
FrontPage http://www.microsoft.com/frontpage
ActiveX Control Pad http://www.microsoft.com/workshop/author/cpad
Visual Interdev http://www.microsoft.com/vinterdev
ActiveX http://www.microsoft. com/activex

From Here...

ActiveX is a constantly changing world. New technologies and tools are coming out faster than any one person can keep up with, let alone an entire industry. Not only are you responsible for creating sound software, you also are now responsible for its interaction with other components, applications, and computers. Unfortunately, we have no easy answer for how to keep up with the onslaught known as ActiveX.

Microsoft does publish a huge volume of information about new technologies and tools on its Web site on the Internet. Also, the Internet newsgroups are especially helpful in locating resources (human and digital) to assist you. Hundreds of developers every day access the forums, exchanging information about ActiveX development.

When it comes to ActiveX development, the only advice that we can give you is to be patient, take your vacation time when you've earned it (believe me, your work will be waiting for you), and study a lot. Don't worry if you don't have all the answers; no one does.

We truly hope that you gain as much from this book as we did in writing it.