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.
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 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.
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:
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.
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).
<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>
<A HREF="javascript:alert(`This is a message')">Click here to view the message </A>
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.
<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 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.
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.
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.
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
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. interface INumber : public IUnknown class CNumber : INumber 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).
HRESULT CNumber::QueryInterface(REFIID riid, 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. class CNumber1 : IUnknown 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.
HRESULT CNumber1::QueryInterface(REFIID riid, 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 HRESULT CNumber1::ImpINumber::QueryInterface(REFIID
riid, 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.
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).
The 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.
class CNumber2 : IUnknown CNumber2::CNumber2(LPUNKNOWN pUnkOuter) HRESULT CNumber2::ImpINumber::QueryInterface(REFIID
riid, CNumber3: The Outer Object interface IWholeNumber : public IUnknown 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).
class CNumber3 : IWholeNumber // Create the object BOOL CNumber3::Init() 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). 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).
HRESULT CNumber3::QueryInterface(REFIID riid, Aggregation and Tear-Off Interfaces in ATL 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. COM_INTERFACE_ENTRY_AGGREGATE_BLIND(pUnknown) This macro is used to expose all of an aggregated object's interfaces from the
outer object. 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.
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.
CNumber: A Simple Sample Listing 16.6
{
public:
// ILrsInetUnlock methods
virtual HRESULT __stdcall GetNumber(
/* [out] */ double* pValue) = 0;
virtual HRESULT __stdcall SetNumber(
/* [in] */ double value) = 0;
};Listing 16.7
{
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;
};Listing 16.8
LPVOID* ppvInterface)
{
if(IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_INumber))
{
*ppvInterface = this;
AddRef();
return NOERROR;
}
else
{
return E_NOINTERFACE;
}
}
CNumber1: Separate vTables Listing 16.9
{
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;
};Listing 16.10
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;
}
}
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
LPVOID* ppvInterface)
{
GET_CNUMBER1(pThis);
return pThis->QueryInterface(riid, ppvInterface);
}Reusing ActiveX Objects with Aggregation
FIG. 16.1
CNumber3 aggregates CNumber2 to reuse its implementation of the INumber
interface.
CNumber2: An Aggregatable Object
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.
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
{
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
{
m_cRef = 0;
m_value = 0.0;
if(pUnkOuter == NULL)
m_pUnkOuter = this;
else
m_pUnkOuter = pUnkOuter;
}Listing 16.14
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();
}Listing 16.15
{
public:
// ILrsInetUnlock methods
virtual HRESULT __stdcall GetNumber(
/* [out] */ long int* pValue) = 0;
virtual HRESULT __stdcall SetNumber(
/* [in] */ long int value) = 0;
};Listing 16.16
{
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
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
{
_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;
}
FIG. 16.2
CNumber2 and CNumber3 hold pointers to each other's true IUnknown
interfaces.Listing 16.19
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
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
Argument
Meaning
pUnknown
The aggregated object's true IUnknown interface. The aggregated object is
created in the outer object's FinalConstruct method.
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.
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
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.
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.
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.
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. |
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.
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.
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).
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.
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.
The samples from this chapter are in the DLL project Number, described in Table 16.7.
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. |
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.
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:
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:
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.
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:
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/.
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.
OLEDB | ODBC |
Session Objects | Connection Handles |
Shared data object | Application data buffer |
Accessors | Descriptor Handles |
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.
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.
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
Free-Threading Model
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
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. 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. 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.
Choosing a Multithreading ModelEngineering for the Future
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...
© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.