Chapter 7. Contexts

In the summer of 1996, my MSDN supplement came to my office as it always did—via UPS in a little brown box. I opened the box, and there was a little red CD-ROM labeled Microsoft Transaction Server (MTS) 1.0.

I, along with my peers, had heard about MTS (code named Viper). We knew it would somehow, magically, enable COM objects to share in transactions, but we weren't sure what that meant. The basic idea was that a number of related objects could manipulate a database, but if any one of the objects encountered a problem it could shout "Abort!" Then the database modifications for all the objects would be undone. But how could this be?

By design, COM objects do not know anything about the objects that created them and do not transfer any information to the objects they create (aside from Remote Procedure Call [RPC]-based security information), except for what is explicitly communicated through interface methods. MTS promised that developers could write objects without any a prioriknowledge of other objects, and only very minor changes were needed to allow any number of objects to share in a single transaction.

In this chapter, I show you exactly how objects can share—not only in transactions, but in other regards as well. It all has to do with a mechanism that originated in MTS: the context. The first stop on the road to understanding contexts is to return to the days of MTS and take a look at the role of a new interface that this value-added surrogate introduced: IObjectContext.

Introducing IObjectContext

In the days of MTS, Object A could create Object B who could create C and so on. All could manipulate one or more databases, but if any of the objects reported a problem, all changes would be rolled back for all objects in each database.

This brings us to the first significant modification to traditional COM programming—MTS required that objects participating in transactions should vote on the success or failure of their database operations.

They did this in two parts. The first part involved an object requesting and getting an interface to a special object called the context object from MTS. Every object would have access to its own context object, and when obtained, each object participating in a transaction would call special voting methods on it to state whether the object's database modification (insert, delete or update) was successful. In VC++, they would do this by requesting a special interface called IObjectContext via a method called CoGetObjectContext(). In VB, the developer would call the method GetObjectContext which would return a ContextObject object.

As we discussed in Chapter 1 "Com +: An Evolution," VB tends to treat interfaces as if they were objects. The ObjectContext object is really the IObjectContext interface, and this interface was only available to objects running in MTS. It has eight methods, and I'll describe the purpose of each one. Note that the first three methods I list are informational in that they retrieve information from an object's context and make it available to the object. The next four methods have to do with voting on the outcomes of transactions, and the final method has to do with something called context flow. Context flow enables information to pass from the creating object to the created.

The informational methods are as follows:

  • IsCallerInRole: This method is used to facilitate COM+ security and is discussed in detail in Chapter 12, "Security." An object calls this method to test whether the identity of the current caller (by identity, I mean the NT account of the client making the call) is in a particular role (passed in as a string, for example "Managers").

  • IsInTransaction: Returns a simple Boolean indicating whether the object is currently involved in a transaction or not. As we learn in Chapter 8, "Transactions," a component may be set such that it can participate in a transaction should its parent be participating in one; however, it can also operate without inheriting a transaction from its parent. In situations where an object absolutely must participate in a transaction (even if a system administrator declares it otherwise by manipulating the component's settings in Component Services) the object author can use this method to determine whether a transaction is present. If a transaction isn't present, the object author can prevent the object from performing a modification. The following code snippet demonstrates how this is done:

    If Not GetObjectContext.IsInTransaction Then
     ' I'm not in a transaction - raise an error
    End If
  • IsSecurityEnabled: As we will see in Chapter 12, COM+ security may be turned off depending on how the component's properties are set. This method returns a Boolean indicating whether security is on or off. Because it is too easy for a systems administrator (or, perhaps a malicious intruder) to turn security off for a component via Component Services, I have seen this method used by security-minded developers who want to make sure that their component will never operate unless security is turned on.

    if Not GetObjectContext.IsSecurityEnabled Then
        'Role based security is NOT being used,
        'so raise a permission denied error.
        Err.Raise 70
    End If

TheDistributed Transaction Voting methods are as follows:

  • SetAbort(): As we see in Chapter 8, an object calls this method when it wishes to set flags in its context to indicate that it failed while performing database modification or is otherwise unhappy with the transaction it is involved in. These context flags (called the Happy and Done bit as we see in the next chapter) tell COM+ whether to commit or abort the transaction. If this method is called, the Happy bit will be set to 0 and the transaction will be aborted.

  • SetComplete(): Same as SetAbort(), except this method will set the context flags to indicate that the object successfully completed database modification.

TheLifetime methods are as follows:

  • DisableCommit: An object in a transaction should call this method if it performs a database modification and is unhappy with the result, but wishes to remain alive in hopes that a subsequent method call will make the object Happy. This method and its sister method, EnableCommit(), are used to temporarily keep COM+'s rather zealous object deactivation policies at bay. We discuss these two methods in depth in Chapter 8 when we discuss object statefulness, Just-In-Time Activation (JITA), and object lifetime. For now, it is enough to know that DisableCommit() sets flags in the object's context so that the object will remain alive between method calls. However, if ultimately forced to deactivate, it will cause the distributed transaction it is involved in to abort.

  • EnableCommit: This method works identically to DisableCommit (it keeps the calling object alive between method calls); however, when an object that calls this method is forced to deactivate, its vote will be interpreted as a "yes." In other words, the distributed transaction has the possibility of succeeding and being committed provided other objects participating in the transaction also vote positively.

The Context Flow method is as follows:

  • CreateInstance( ) : This method is used to create a new instance of an object. It works in a fashion identical to CreateObject() and can, in fact, be used wherever CreateObject() is used in a configured object. The difference is, CreateInstance() passes on context information from the caller to the created object, whereas CreateObject() does not.

As discussed in the previous bullet points, an object participating in a transaction can call SetAbort() or SetComplete() to indicate the success or failure of its modifications. We will explore this process in-depth in Chapter 8. For now, the most interesting function is CreateInstance(). An object can use this method to create another object, but unlike the standard COM creation mechanisms (CoCreateInstance or VB and Java's New), this method allows the context to flow from the creator to the created. In other words, information kept in the context of the creator (such as the transaction it is involved in) can be passed, or flow, to the created object so that it too can participate in the transaction.

The CreateInstance() method of IObjectContext is no longer necessary in COM+, but in the MTS days it was critical. Unlike in traditional COM, if you want a transaction to flow from MTS Object A to an MTS object it creates—for example, B—you cannot use VB's New or CreateObject() methods, nor can you use VC++'s CoCreateInstance(). Instead, A must get its ObjectContext and must use its CreateInstance() method to create B.

Note that the following code is specific to MTS programming prior to the introduction of COM+. COM+ does not require the use of IObjectContext->CreateInstance to allow context to flow.

ForVB, instead of using the following code:

Dim myB as B
set myB=CreateObject("SomeObject.B")

you can use this:

set myB=GetObjectContext.CreateInstance("SomeObject.B")

ForVC++ (Microsoft's Visual C++), instead of using the following:

IB * pB;
CoCreateInstance(CLSID_B,0,CLSCTX_ALL,IID_IB, (void**)&pB);

you can use this:

IObjectContext * pIObjectContext;

CoGetObjectContext(IID_IObjectContext, (void**)&
pIObjectContext); pIObjectContext->CreateInstance(CLSID_B,IID_IB, (void**)&pB);

So, if an Object A creates another Object B using the IObjectContext->CreateInstance(), MTS has the opportunity to get involved. In fact, in these early, pre-COM+ days, you could consider the IObjectContext interface as an interface that MTS supported and made available to your objects. Certainly then, by calling the CreateInstance() of this MTS interface, A is really asking MTS itself to create the instance of B on its behalf. Therefore, if A is participating in a transaction, and A asks MTS to create B, MTS can bring B into the transaction as well. Again, the transaction can be said to flow from A to B, courtesy of MTS. This is shown in Figure 7.1.

Transactions can flow from creator to created via contexts.

Figure 7.1. Transactions can flow from creator to created via contexts.

Prior to COM+, context was only available to MTS objects. If non-MTS objects asked for a context, COM refused them. Furthermore, if an Object A running in MTS created B without going through the context, the transaction failed to flow from A to B, and the objects did not share in the transaction.

COM and MTS Integration

Originally, COM and MTS were two separate things:

  • MTS was only a value-added surrogate for COM objects.

  • COM was a base level of service, and MTS provided the value-added benefit of allowing objects to cooperate by providing contexts that could allow attributes (transactions) to flow from creator to the created.

As a surrogate, however, MTS was never truly part of COM. To function in MTS and be able to work with contexts, an object needed to be associated with MTS. Because the COM creation mechanisms were well-defined and unalterable, a slight-of-hand had to be employed; a non-MTS COM object would list its Dynamic Link Library (DLL) name in the Registry like that shown in Figure 7.2.

Non-graphically, the registry entries are:

Registry Key:HKEY_CLASSES_ROOT CLSID { 638094E0-758F-11d1-8366-0000E83B6EF3}
 InprocServer32
Value for Above Key:
C:componentscomcalc.dll

If this same object was brought into MTS, MTS changed the key's value to something like the following:

C: WINNT System32 mtx.exe /p:{ 638094E0-758F-11d1-8366-0000E83B6EF3 }
A typical registry entry for a COM DLL.

Figure 7.2. A typical registry entry for a COM DLL.

Clearly, the authors of MTS were not much more privileged in what they were trying to do than COM surrogate developers outside of Microsoft; they clearly weren't allowed to make any changes to COM's internal mechanisms. The mechanics of COM are separate and immutable to MTS—COM knows nothing about MTS; it simply creates objects according to COM's rules. By surreptitiously modifying the Registry settings to include the MTS surrogate (MTX.EXE), MTS fools COM into creating another instance of the MTX.EXE server (or further utilizing an already running instance of MTX.EXE), which knows what real object to create based on a command-line argument. MTS's approach to COM is very much like a virus's approach to a cell—both entities hijack existing mechanisms of their host and redirect them to other purposes without knowledge of the host.

COM and MTS Merge

As of Windows 2000, MTS is no more. But its spirit lives on in COM+. The two development teams were merged at Microsoft, the wall came crumbling down, and an era of cooperation between two formally separate kingdoms ensued. It was no longer necessary to use IObjectContext's CreateInstance() method to create objects; any ordinary COM creation mechanism would do.

In-process components that you might have already written in your pre-COM+ days can still function in COM+ without modification. And although these unconfigured components might need some slight modification to take advantage of the full range of COM+ services (for example, voting on the outcome of a transaction), they can still participate in most of what COM+ offers after they are configured by a system administrator or developer.

It is important to note that unconfigured components (that is, those COM DLLs that have not been brought into COM+), do not have their own contexts. Instead, they are brought into the context of their creator (threading model compatibility permitting). If their creator is a standard EXE (which always lacks a context), an unconfigured object (that is, an instantiated coclass from an unconfigured COM DLL) does not have one either. If, however, a configured object instantiates an unconfigured object, the latter dwells in the context of the former. Basically, unconfigured components are empty vessels without a context of their own.

Context: Two Different Definitions

Before we explore the mechanics of contexts in greater depth, it is important that we first define context. I fear that the term context is destined for the same ambiguous use as the terms objects , servers , applications, and components. So, I want to make my stand early—there is the concept of context, and then there is the implementation of context. We discuss implementation in the upcoming section "COM Context Implementation." For now, let's explore the concept of context.

Context: The Concept

In everyday language, the word "context" is used to describe the environment within which something occurs. Celebrities often complain that newspaper columnists print their quotes "out of context," implying that columnists change the meaning of their words by not taking into account the context in which they were said. It is fair then to say that context gives meaning to an action, COM+ would certainly agree.

If an object calls GetObjectContext.SetAbort, it means absolutely nothing unless the object is in the context of a transaction. To be "in" the context of a transaction means the object is associated or somehow "tied" to an actual, system-level distributed transaction (COM+ creates this transaction on the object's behalf as discussed in Chapter 8). The association between the object and the real transaction (call this transaction T1) is kept in the object's context object. In other words, the context of the object holds a reference to the transaction T1. Thus, a call to SetAbort means that the transaction T1 should be aborted.

Transactions and information about transactions are kept in an object's context. This information is in no way hidden from the object; on the contrary, an object can always query its context object to find out about its current environment. In the book's sample code (http://www.newriders.com/complus) you find a component called ContextDemo. Although ContextDemo is written in VC++ (it needs to manipulate Globally Unique Identifiers [GUIDs]), it can be used by both VB and VC++ components to find information about their current context. A listing of ContextDemo's primary function, ContextInformation(), is shown in Listing 7.1.

Example 7.1.  ContextDemo Uses the IObjectContext, IobjectContextInfo, and ITransaction Interfaces to Find Information About the Host Object's Current Context

// Note: these two example are snippets of the ContextDemo
// component of the book's accompanying sample code

HRESULT CContextDiag::ContextInformation() {


// Demonstration of an object that determines information about
// its context using IObjectContext and IObjectContextInfo.
IObjectContext *pObjectContext;
IObjectContextInfo *pObjectContextInfo;

bool bIsInTransaction, bIsSecurityOn, bIsSyncronizationOn;
GUID contextGUID, transactionGUID, activityGUID;
char achSecurityString[3],achContextInfo[1024];
unsigned char *pContextGUIDString, *pTransactionGUIDString, *pActivityGUIDString;

HRESULT hr;
RPC_STATUS rpcstat;

// Obtain an IObjectContext interface pointer:

hr = GetObjectContext (&pObjectContext);
if (hr !=S_OK)
{
    // Could not get IObjectContext:
    sprintf(achContextInfo,"ContextInformation: Could not get IObjectContext");
    MessageBox(NULL,achContextInfo,"Context Information",MB_OK);
    return hr;
}

// Obtain an IObjectContextInfo interface pointer:

hr=pObjectContext->QueryInterface(IID_IObjectContextInfo,
                   (void**)&pObjectContextInfo);
if (hr !=S_OK)
{
    // Could not get IObjectContextInfo:
    sprintf(achContextInfo,"ContextInformation: Could not get     IObjectContextInfo");
    MessageBox(NULL,achContextInfo,"Context Information",MB_OK);
    return hr;
}

////////////////////////////////////////////////
// Obtain information about the object's context:
////////////////////////////////////////////////

// Obtain the Context GUID:
pObjectContextInfo->GetContextId(&contextGUID);
rpcstat = UuidToString(&contextGUID, &pContextGUIDString);

if (rpcstat !=RPC_S_OK)
{
    return E_FAIL;
}

// Obtain the Transaction GUID
bIsInTransaction = pObjectContext->IsInTransaction();

if (bIsInTransaction)
{
    pObjectContextInfo->GetTransactionId(&transactionGUID);
    rpcstat = UuidToString(&transactionGUID, &pTransactionGUIDString);
    if (rpcstat !=RPC_S_OK) return E_FAIL;
}
else
{
    pTransactionGUIDString = new unsigned char[4];
    sprintf((char*)pTransactionGUIDString,"N/A");
}

// Obtain the Activity GUID:
pObjectContextInfo->GetActivityId(&activityGUID);

// If GetActivity returns GUID_NULL, then the object does have
// synchronization protection on:
if (activityGUID==GUID_NULL)
{
    bIsSyncronizationOn=false;
    pActivityGUIDString = new unsigned char[4];
    sprintf((char*)pActivityGUIDString,"N/A");
}
else
{
    bIsSyncronizationOn=true;
    rpcstat = UuidToString(&activityGUID, &pActivityGUIDString);
    if (rpcstat !=RPC_S_OK) return E_FAIL;
}

// Determine if the object is using role based security:
bIsSecurityOn = pObjectContext->IsSecurityEnabled();
if (bIsSecurityOn)
    sprintf((char*)achSecurityString,"YES");
else
    sprintf((char*)achSecurityString,"NO");


// Display a message box with all the information we
// have garnered above.
sprintf(achContextInfo,"Context Information n nContextID: %s nActivityID:"
        "%s nTransactionID: %s nRole based security on:%s",
        pContextGUIDString,pActivityGUIDString,
        pTransactionGUIDString,achSecurityString);

MessageBox(NULL,achContextInfo,"Context Information",MB_OK);

//string deallocation code with delete [] and
//RpcStringFree() ommitted for brevity

return S_OK;

}
//The following function obtains more transactional information
//about an object. some code ommitted for brevity, see the book's
//companion source (www.newriders.com/complus) for the complete
//example.
HRESULT CContextDiag::TransactionInformation() {

// Demonstrates additional transaction information that can be
// obtained from an object's context. This is obtained through
// the ITransaction interface, which is obtained using
// IObjectContextInfo::GetTransactionInfo.

IObjectContext *pObjectContext;
IObjectContextInfo *pObjectContextInfo;
ITransaction *pTransaction;

bool bIsInTransaction;
char achTransactionInfo[1000];

HRESULT hr;
XACTTRANSINFO xTransInfo;

// Use the _bstr_t class to make our message
// parsing and displaying a little easier:
_bstr_t sTransInfo="";

// Obtain IObjectContext and IObjectContextInfo interface pointers:

hr = GetObjectContext (&pObjectContext);
if (hr !=S_OK)
{
    // Could not get IObjectContext:
    sprintf(achTransactionInfo,
           "TransactionInformaion: Could not get IObjectContext");
    MessageBox(NULL,achTransactionInfo,"Transaction Information",MB_OK);
    return hr;
}

hr=pObjectContext->QueryInterface(IID_IObjectContextInfo,
                                 (void**)&pObjectContextInfo);
if (hr !=S_OK)
{
    // Could not get IObjectContextInfo:

    sprintf(achTransactionInfo,
            " TransactionInformaion: Could not get IObjectContextInfo");
    MessageBox(NULL,achTransactionInfo,"Transaction Information",MB_OK);
    return hr;
}

bIsInTransaction = pObjectContext->IsInTransaction();

if (!bIsInTransaction)
{
    // If the componenent is not participating in a transaction,
    // abort the method:

    sprintf(achTransactionInfo,"Error: this component is not"
            " partcipating in a transaction.");
    MessageBox(NULL,achTransactionInfo,"Transaction Information",MB_OK);
    return S_OK;

}

// We can get an ITransaction pointer from IContextInfo::GetTransaction.
// This interface pointer will give us additional information about the
// transaction the object is partcipating in:

pObjectContextInfo->GetTransaction((IUnknown**)&pTransaction);

// GetTransactionInfo populates a XACTTRAMSINFO structure
// with additional information:

pTransaction->GetTransactionInfo(&xTransInfo);

// The XACTsTransInfo structure is given below. Most
// of the information is really only important
// when you are working with transactions on a very
// low level (working with the DTC etc):

/*

typedef struct XACTTRANSINFO {
   XACTUOW  uow;                 // The UNIT OF WORK associated with the trans
   ISOLEVEL isoLevel;                // Isolation information
   ULONG    isoFlags;                // Always zero
   DWORD    grfTCSupported;          // Transaction Capabilties
   DWORD    grfRMSupported;          // Always zero
   DWORD    grfTCSupportedRetaining; // Always zero
   DWORD    grfRMSupportedRetaining; // Always zero
}  XACTTRANSINFO;

*/

// The UNIT OF WORK field is just a byte array of length 16.
// This is really used for internal purposes, but we show it

// here just for demonstration:

sTransInfo = "Transaction Information:  n n";
sTransInfo += "Unit of Work: ";

for (int k=0; k<=15; k++) {
    char cUow[1];
    sprintf(cUow,"%x",xTransInfo.uow.rgb[k]);
    sTransInfo+= cUow;
}

// The isoLevel field indicates information about the
// transaction's isolation level.  Again, this type of information
// is generally of importance only to components interacting with
// transactions on a system-level (RMs):

sTransInfo +=" n nIsolation Levels of the transaction:  n";

if (xTransInfo.isoLevel == ISOLATIONLEVEL_UNSPECIFIED)
    sTransInfo += "    ISOLATIONLEVEL_UNSPECIFIED n";
else {
    if (xTransInfo.isoLevel & ISOLATIONLEVEL_CHAOS)
        sTransInfo +="    ISOLATIONLEVEL_CHAOS n";

    if (xTransInfo.isoLevel & ISOLATIONLEVEL_READUNCOMMITTED)
        sTransInfo +="    ISOLATIONLEVEL_READUNCOMMITTED n";

    if (xTransInfo.isoLevel & ISOLATIONLEVEL_BROWSE)
        sTransInfo +="    ISOLATIONLEVEL_BROWSE n";

    if (xTransInfo.isoLevel & ISOLATIONLEVEL_CURSORSTABILITY)
        sTransInfo +="    ISOLATIONLEVEL_CURSORSTABILITY n";

    if (xTransInfo.isoLevel & ISOLATIONLEVEL_READCOMMITTED)
        sTransInfo +="    ISOLATIONLEVEL_READCOMMITTED n";

    if (xTransInfo.isoLevel & ISOLATIONLEVEL_REPEATABLEREAD)
        sTransInfo +="    ISOLATIONLEVEL_REPEATABLEREAD n";

    if (xTransInfo.isoLevel & ISOLATIONLEVEL_SERIALIZABLE)
        sTransInfo +="    ISOLATIONLEVEL_SERIALIZABLE n";

    if (xTransInfo.isoLevel & ISOLATIONLEVEL_ISOLATED)
        sTransInfo +="    ISOLATIONLEVEL_ISOLATED n";
}

// The grfTCSupported field indicates that capabilities
// of the transaction associated with the component.

sTransInfo +=" nTransaction Capabilities:  n";
sTransInfo +="    Synchronous Committs allowed: ";

if ((xTransInfo.grfTCSupported & XACTTC_SYNC) || (xTransInfo.grfTCSupported & XACTTC_SYNC_PHASEONE))
    sTransInfo += "YES n";
else

    sTransInfo += "NO n";
sTransInfo += "    Asynchronous Committs allowed: ";
if ( (xTransInfo.grfTCSupported & XACTTC_ASYNC) || (xTransInfo.grfTCSupported
      & XACTTC_ASYNC_PHASEONE))
    sTransInfo += "YES n";
else
    sTransInfo += "NO n";

// Display all the transaction information we have obtained:
MessageBox(NULL,sTransInfo,"Transaction Information",MB_OK);

return S_OK;
//Interface release code
}

When ContextDemo is unconfigured (that is, running as an ordinary COM component outside of COM+), it will automatically share the context of the COM+ object that calls it. This is the behavior of all unconfigured COM components and the behavior we want, so don't install this component into COM+ or it will cease to operate properly. So, in its unconfigured state, when a host object calls ContextDemo's ContextInfo() method, ContextDemo will actually be investigating its host's context. It will query for information regarding the host's context, activity, transaction, and security configuration and display this information in one or two message boxes. See Figures 7.3 and 7.4 for examples of ContextDemo's output.

ContextDemo obtaining its Context Information.

Figure 7.3. ContextDemo obtaining its Context Information.

ContextDemo obtaining Transactional Information.

Figure 7.4. ContextDemo obtaining Transactional Information.

ContextDemo can be helpful in solidifying your understanding of contexts; try writing your own objects, configuring them in different ways, and calling upon ContextDemo to report on their differences. You might also try adding to ContextDemo, for example, to report on the current thread ID as well. This can help you understand the effect the threading model affiliations Apartment, Free, Both, and Neutral have. You will find that the behavior of calling and executing threads change (hopefully, as you expect) when the threading model of the component is changed. To determine the current thread ID, simply use the code:

DWORD dwThreadID=GetCurrentThreadId();

Let's return now to contexts and note that even though an object can reach outside of itself and interact with its context, it is not required to. In fact, some objects (and some object authors) are happiest when blissfully unaware of their context; it is perfectly acceptable to write an ordinary COM component that does not use IObjectContext (or any other context-oriented interface) in any way. However, even though an object might not interact with its context explicitly, a context is still critical; as with transactions, there are other COM+ services that must put information in an object's context in order for the object to participate in that service. COM+synchronization is a good example of this. An object that is configured as requiring synchronization services (discussed in more detail in Appendix B, "COM+ Synchronization Through Activities" ) will up participating in something called an "activity." Information about this activity is stored in the object's context. Although the object may not "know" that it is in an activity or be explicitly written to take advantage of the fact, activity information in its context is always available and readable to COM+. Thus, when this object interacts with another object, COM+ will know by reading the context that it needs to protect this object from concurrent access.

A context is rather like a transparent bucket attached to each object—one that can hold information about transactions, activities, and any other information that links an object with one or more COM+ services. These buckets follow the object around throughout its life. They are transparent so that COM+ can see inside, and just as one bucket may be tipped so that its contents may flow to another bucket, so too can COM+ contexts flow. Specifically, when Object A creates another object (Object B), the bucket of the first object is tipped such that its contents (the transaction it is involved in, the activity is involved in, etc.) can flow from creator to created. Thus, if Object A participates in a transaction, Object B inherits that transaction and participates in it as well. And so, if either object calls GetObjectContext.SetAbort(), data modifications for both objects are aborted.

In the last paragraph, the interaction between Object A and Object B is based on a simplifying assumption: that Object A and Object B are completely compatible and both are interested in transactions. Suppose this is not the case. Imagine that Object A participates in a transaction, but Object B performs no database modifications and wants nothing to do with transactions. In this case, the transactional attributes should not flow because the contexts of the objects are incompatible, as far as transactions are concerned. But why are these objects incompatible, and how did they get that way?

When you configure your components (set its transactional properties, request synchronization services, and so on), you give your object a context—that is, you describe a world that your component expects to live in. It is COM+'s responsibility to ensure that it sees the world as you have defined it. At present, there are a fixed number of context-influencing attributes, although in the future, developers might be able to create their own.

Let's move on now, and discuss contexts and context flow in a little more detail.

COM Context Implementation

Contexts are created when an object is instantiated; that is, when CoCreateInstance() or Visual Basic's New is invoked and a new instance of a component is brought into being.

If the object creator is a configured component, then it will have a context, and COM+ will seek to make its contextual information flow from creator to created. But exactly what information is it that flows? And suppose information existing in the context of the creator is not relevant to the created object? Addressing the first question will give us insight into the second, how incompatibilities in context are handled.

As I discuss in the previous paragraph, the attributes you set in Component Services will effect what information exist in an object's context. It should not surprise you then, to find that information regarding the following can be found in an object's context:

  • Transaction Information: If an object is participating in a transaction (more on this in Chapter 8) then transactional information including a transaction identifier will exist in the object's context. Should this object create another object, this context information passes (flows) from creator to created (provided, of course, that the created object's class is configured to support transactions). Transaction information is available at all times to the object, it need only request the IObjectContext and IObjectContextInfo as demonstrated in Listing 7.1.

  • Activity Information: COM+ synchronization services rely entirely on activities to protect objects. An activity is simply a way of grouping a series of objects together, such that they are all protected from outside interruption while a single logical thread of method invocations weaves its way through. Activities are deeply intertwined with transactions to the extent that if an object is involved in a transaction, it mustbe in an activity. Therefore, transaction and activity information often flow together from the context of the creating object to the created. We will discuss activities (and their association with transactions) in greater detail in Chapter 8 and in Appendix B. Activity information may be retrieved by an object by using the IObjectContext and IObjectContextInfo interfaces as demonstrated in Listing 7.1.

  • Security Information: Information about the current caller, as well as successive callers can be found in an object's context. Obtaining security information from an object's context is demonstrated in Listing 7.1.

  • Apartment Information: Although an apartment may have any number of contexts associated with it, a context can only be associated with a single apartment. Although it is not documented, it is highly likely that this association is maintained in an object's context. An object may not obtain information about its apartment directly through Microsoft supplied interfaces, however, if you are not faint of heart, the code in Listing 7.2 will do the trick:

Example 7.2.  A Component Which Obtains Its Apartment "GUID"

HRESULT CContextDiag::ApartmentInformation() {

// This method obtains the Apartment "GUID" of the component.
// This cannot be obtained using COM+'s context interfaces, but rather
// it must be ascertained using a little ingenuity.

// Recall from chapter 4 that to pass an interface pointer
// from one thread to another, you must convert the interface
// to a stream using CoMarshalInterThreadInterfaceInStream.

// With this stream, and an understanding of how COM functions
// on a network packet level, we can obtain the object's OXID
// (its Object Export Identifier) which is unique to every
// apartment in the system.  The OXID can thus serve as
// the apartment GUID of a component - since it is unique
// for every apartment.

// For more information on how COM works at a network level,
// consult Guy and Henry Eddon's excellent article,
// "Understanding the DCOM Wire Protocol by Analyzing Network
// Data Packets" in the May 98 issue of the Microsoft's systems
// journal, or consult the DCOM draft from Microsoft.

IStream *pStream;
BYTE    cStreamArray[1024];
ULONG    nBytesRead=0;
HRESULT hr;

// Use the _bstr_t class to make message
// parsing and displaying a little easier:
_bstr_t sApartmentInfo="";

// Convert the current interface to a stream:
hr=CoMarshalInterThreadInterfaceInStream(
    IID_IContextDiag,
    (IUnknown*)(IContextDiag*)this,
    &pStream);

if (hr!=S_OK)
{
    // We couldn't convert the interface pointer to a stream:
    sApartmentInfo="ApartmentInformation: Problem converting"
                   " pointer to stream";

    MessageBox(NULL,sApartmentInfo,"Apartment Information",MB_OK);
    return S_OK;
}


// Deposit the contents of the stream into a byte array
// so we can analyze it:
hr=pStream->Read(cStreamArray,1024,&nBytesRead);

if (hr!=S_OK)
{
    sApartmentInfo="ApartmentInformation: Problem reading stream.";
    MessageBox(NULL,sApartmentInfo,"Apartment Information",MB_OK);
    return S_OK;
}

// The contents of the converted stream are now in the stream array.
// The data is formatted according to the Standard Marshall Object
// Reference as specified by the DCOM draft.  The first
// 4 bytes of the stream should spell out MEOW:

if (cStreamArray[0]!='M' || cStreamArray[1]!='E' ||
    cStreamArray[2]!='O' || cStreamArray[3]!='W')
{
    // The stream's contents are not as we expect, so abort:
    sApartmentInfo="ApartmentInformation: Unexpected Stream Contents.";
    MessageBox(NULL,sApartmentInfo,"Apartment Information",MB_OK);
    return S_OK;
}

// The object's OXID is contained in array positions 32 to 39 as
// defined by the DCOM draft:
sApartmentInfo = "APARTMENT GUID (OXID) = ";

for (int k=32; k<=39; k++)
{
    char cHexbyte[1];
    sprintf(cHexbyte,"%x",cStreamArray[k]);
    sApartmentInfo += cHexbyte;
    if (k!=39) sApartmentInfo += "-";
}

// Display apartment information:
MessageBox(NULL,sApartmentInfo,"Apartment Information",MB_OK);

return S_OK;
}
  • Because unconfigured components are also contained in apartments, this method will work on either configured or unconfigured components. The output of this method is given in Figure 7.5.

    An unconfigured object share its creator's context.

    Figure 7.5. An unconfigured object share its creator's context.

  • Other COM+ Objects: I don't want to raise your hopes—only Microsoft can perform this magic. If you are Microsoft, it is actually possible to have the object context contain (and act as a dispenser for) interfaces to other COM+ objects. In this way, an object can get an interface to another object through its context. Object's accessed from ASP pages can actually obtain interfaces to IIS objects such as Response, Request, and Session. This is discussed in greater depth in the section, "Context and ASP Pages."

When a configured object (Object A) seeks to create another object (Object B), COM+ first goes to the COM+ catalog and examines the attributes of component B. In other words, it looks to see how you configured this component and determines to what extent B's configuration is compatible with A's. In the simplest, most ideal case, Object B is configured exactly the same way as Object A; all contextual information can flow from A to B. I say flow from the context of A to context B, because although the contexts of A and B may be 100% compatible, they will not share a context; rather each object will have its own. To prove this we need only look at the context GUIDs of both A and B when they are instantiated. A and B are going to be VB objects, so we do have a slight problem in that IObjectContextInfo's GetContextId() method returns a GUID and VB6 cannot handle a GUID. So, we create a VC++ wrapper component called VBGUID that will obtain its host's context GUID, convert it into a VB string (which is represented as the C++ data-type BSTR), and return it to the host object. The code for the key function of this wrapper is shown in Listing 7.3.

Example 7.3.  A C++ Component Which Returns the Context GUID of an Object as a String

HRESULT VBGUID::ContextGuid(BSTR *CntxGuid ) {

//This method returns a STRING of the current context GUID. Since
//Visual Basic has no native support for GUIDs, this component can serve
//as a way for a Visual Basic component to obtain its contextGUID.

//This component must NOT BE CONFIGURED!  Doing so would give the
//object its own context, and thus the Context GUID this method
//will return will be its own - not its caller.  By keeping
//the component unconfigured, one ensures that it shares the context
//of its creator, and thus the reported Context GUID is in fact
//that of the caller.

//This component should only be called from configured components,
//since it attempts to examine the context of its creator.

IObjectContext *pObjectContext;
IObjectContextInfo *pObjectContextInfo;

GUID contextGuid;
unsigned char *pContextGuidString;
WCHAR wContextGuidString[40];

HRESULT hr;
RPC_STATUS rpcstat;

// Obtain IObjectContext and IObjectContextInfo interfaces:

hr = GetObjectContext (&pObjectContext);
if (hr !=S_OK)
{
    // Could not get IObjectContext:
    MessageBox(NULL,"Could not get IObjectContext",
              "Context Information",MB_OK);
    return hr;
}

hr=pObjectContext->QueryInterface(IID_IObjectContextInfo,
                           (void**)&pObjectContextInfo);
if (hr !=S_OK)
{
    // Could not get IObjectContextInfo:
    MessageBox(NULL,"Could not get IObjectContextInfo",
                    "Context Information",MB_OK);
    return hr;
}

// Obtain the Context Guid:
pObjectContextInfo->GetContextId(&contextGuid);
rpcstat = UuidToString(&contextGuid, &pContextGuidString);
if (rpcstat !=RPC_S_OK)
{
    return E_FAIL;
}


// The Context GUID is currently in a character array.
// Convert to UNICODE and then to a BSTR:

if (MultiByteToWideChar(
    CP_ACP,
    0,
    (char*)pContextGuidString,
    -1,
    wContextGuidString,
    sizeof(wContextGuidString)/sizeof(wContextGuidString[0]))==0)
{


    // Could not convert to UNICODE.
    return E_FAIL;

}

// Convert the UNICODE string to a BSTR:
*CntxGuid=SysAllocString(wContextGuidString);

return S_OK;

}

Now that we have a wrapper component that will perform the context GUID-to-string conversion, we can use VB objects to prove that contexts differ. Let's begin with Object B shown in Listing 7.4.

Example 7.4.  A VB Component Which Obtains Its GUID Using the Component Above, and Exposes a Method That Returns It

'ObjectB

Dim retrieveGuid As VBGUID

Private Sub Class_Initialize()
    Set retrieveGuid = New VBGUID
End Sub

Function GetContextGuid() As String
    GetContextGuid = retrieveGuid.ContextGuid
End Function

Object B does little more than create a new instance of VBGUID and defines a method that will return its own context GUID as a string. Object A, shown in Listing 7.5, will perform three actions: it will create a new instance of VBGUID, it will use this object to determine its own context identifier, and then it will determine the context identifier of Object B. The code for Object A is shown in Listing 7.5.

Example 7.5. A VB Component Creates ObjectB, Obtains Its Context GUID, Obtains Its Own Context GUID, and Compares Them

Dim retrieveGuid As VBGUID
Dim ObjectB As ContextB

Sub ContextDemo()

    Dim sCompare As String
    Dim sContextA As String
    Dim sContextB As String


    'Obtain the Context Guids of both objects:
    sContextA = retrieveGuid.ContextGuid
    sContextB = ObjectB.GetContextGuid

    sCompare = "Comparison of Object Contexts: " & vbNewLine & vbNewLine
    sCompare = sCompare & "Context GUID of Object A: " & sContextA_
               & vbNewLine
    sCompare = sCompare & "Context GUID of Object B: " & sContextB_
               & vbNewLine
    MsgBox sCompare

End Sub

Private Sub Class_Initialize()
    Set retrieveGuid = New VBGUID
    Set ObjectB = New ContextB
End Sub

If both Object A and Object B are configured components, when Object A is instantiated you will find that Object A's and Object B's contexts differ as shown in Figure 7.6.

On the other hand, if we don't configure Object B, it will share the context of its creator (Object A); and both contexts have the same context indentifiers as shown in Figure 7.7.

Prerelease COM+ documentation and articles mention that if contexts between two objects are fully compatible, it will be possible for the two objects to actually share the same context. You will, however, be hard-pressed to find any scenario where this is the case; empirically, you will find that each configured object has its own context no matter how similar they are, and contexts never seem to be shared. Don't expect the "Must Be Activated in the Callers Context Check Box" on the component's Activation tab to help; this checkbox exists to prevent an object from being created unless it can run in its creator's context. In almost all cases, sharing a context isn't possible. Selecting this checkbox will have the effect of preventing your object from ever being created.

It may very well be that a less-common scenario exists where contexts may be shared, or future incarnations of COM+ will allow it. At present, however, it seems as though the designers of COM+ found it simpler (or perhaps more expedient) to give every object its own context. In the interim, if you need an object to share the context of its creator, make sure that its component is unconfigured. Unconfigured components share their caller's context by default.

Objects configured similarly have different contexts.

Figure 7.6. Objects configured similarly have different contexts.

An unconfigured object shares its creator's context.

Figure 7.7. An unconfigured object shares its creator's context.

Returning now to the concept of context flow, we know that COM+ examines the context of the creator and context of the created. What properties are compatible will flow; it would seem, those properties that aren't compatible will not flow. But it is not quite so Boolean as that. Shades of grey exist between contexts where some form of mediation is employed between contexts so that objects with different contexts (and, therefore, different needs) can work together. The mediation that makes this possible is, in COM+ terminology, known as interception.

Interception

The premise of interception is simple: If two objects have incompatible contexts, COM+ should step in between the two and act as a translator between the objects. To use a grossly simplified analogy, if Object A's context is French and Object B's context is German, then an interceptor should step between the two objects and translate French to German one way and German to French the other. Giving examples in more detailed terms is not as clear cut, largely because the implementation of interception is not documented at the time of this writing. Rather, most discussion of interception is conceptual. In theMicrosoft Press book, "Understanding COM+,"David Platt (one of my favorite technical authors) speaks of the concept of interception and mentions that between the client side proxy, RPC channel, and the server stub there exists chains of "policy objects." The number and functions of these policy objects depend on how the component is configured. Basically, these objects are responsible for performing the actual grunt work of mediation. The example Platt gives involves a server-side object that requires synchronization services. In this event, a server-side policy object will "intercept" the method invocation and try to acquire a lock to the server's process before allowing the method invocation through.

In the September 1999Microsoft Systems Journal section, "House of COM,"Don Box says "An interceptor acts as a proxy to the 'real' object." As such, interceptors implement the same interfaces as the real object. Box does not mention policy objects even once, but he does go on to show how an interceptor is created beneath CoCreateInstance() and is used to set up the context for the created object. This leads us to see interceptors as a proxy to our configured object. This interceptor/proxy will transparently and automatically propagate attributes from creator to created and mediate communications between two different contexts, regardless of the differences.

While interesting, knowledge of the inner workings of interceptors is not critical or especially important when writing components. The implementation is not published and is certain to change. However, the fundamental tenant of COM+ says essentially, "Don't worry…be happy"; COM+ will take care of the details—concentrate instead on what you want your application to do.

Contexts, Apartments and the Free Threaded Marshaler

If I can borrow another line from the "House of COM," a context is the "innermost scope of an object." All configured objects exist in a context, but what does a context exist in? The answer is: an apartment. Before I go into greater detail, I should pause for a moment and alert the reader that this section discusses more esoteric, lower-level aspects of contexts that, while interesting, are by no means a prerequisite in using contexts effectively. This section may, therefore, be safely skipped without harm.

An apartment, remember, is a declarative affiliation between threads and objects.Threads declare what type of apartment they wish to be in, whereas objects are bound to the apartment of the thread that created them (note that by "creating thread" I don't mean the thread of the client executable that called CoCreateInstance() or New; rather, I mean the thread in DLLHOST.EXE on which a new instance of the object was created). Now, let's bring contexts into the picture. Recall that a thread in one apartment is not allowed to share a raw interface pointer with a thread in another apartment, and instead must call CoMarshalInterthreadInterfaceInStream() or use theglobal interface table (GIT) to package the interface into a portable form.

When the portable, or marshaled, form of an interface is unmarshaled by another thread, COM+ will automatically tie the resulting proxy to the unmarshaling thread's apartment and context. For the purpose of example, let's assume that we have a client thread executing on behalf of a configured component and, therefore, has a context. When this thread unmarshals a marshaled interface, the resulting interface proxy will be automatically tied in to the thread's context and calls made by this thread on this interface will be cross context. Both the thread's context and the object's context will be taken into account and ordinary interception can take place. If the object investigates its context, it will see exactly what it (and you) would expect: Its own context, not that of the client. Suppose, however, that you want the object to execute in the context of the client thread that calls into it? In other words, you want the object to give up any claim to its own context and instead always operate in the context of the object that calls it. In COM+ terminology, you want your object to be context-neutral.

We know that unconfigured objects share the context of their creator (though the server and creator's apartment model needs to be compatible), which is part of what we want. But there is a problem. If the creator marshals its interface pointer into a stream—(or uses the GIT) and this packaged interface is unmarshaled by a thread executing on behalf of another configured object—the unconfigured object will still execute in the context of its creator and not in the context of the calling thread's object. However, we want our object to share the context of its caller, regardless of whether the caller is the creator. This is shown in Figure 7.8.

An object that always executes in the context of its caller.

Figure 7.8. An object that always executes in the context of its caller.

If you want your object to "borrow" the context of the calling object it must be unconfigured, and it must aggregate the free-threaded marshaler (FTM). This may not sound so simple, but it actually is pretty simple to do, though it can only be done in C++.

Recall from Chapter 4, "Threading and Apartment Models," that when an interface is unmarshaled, the thread that performed the unmarshaling receives an apartment- relative (that is, bound to a certain apartment) interface proxy. This proxy will tie itself into the thread's context; so, it can now be said to be context-relative as well. The FTM, however, interferes with this proxy creation and, in a sense, violates the rules of COM+ by forcing "raw" object pointers to be returned to the unmarshaling thread. This has a couple of effects. The first effect is that COM+ can no longer perform Single Threaded Apartment (STA) synchronization effectively because COM+ can no longer tell from which apartment the method call originates. Furthermore, because a thread receives a raw interface pointer to an object aggregating the FTM, that particular thread is now the one that actually executes the method in the object. Thus, the object better be thread-safe because it is opening itself up to access by any and all threads in the process. The second effect is that any method invocation made on the interface executes in the context of the calling thread and notin the context of the receiving object. This means that an object that uses FTM finds that calls to GetObjectContext() return an interface to the context of its caller, and not to its own context.

By and large the only components that benefit from executing in caller context are utility components. A class of component known as resource dispensers (the Open Database Connectivity [ODBC] driver manager is a Resource Dispenser [RD]), for example, hand out resources—often transactional connections to a relational database or other form of Resource Manager (RM). RDs may, therefore, make use of the FTM because they often need to "see" into the context of the caller. By looking into the caller's context, an RD can determine such things as the transaction the caller is currently involved in.

One final aspect of contexts and apartments has to do with thethread neutral apartment (TNA). Recall that, unlike the STA and MTA, the TNA does not contain any threads. While the STA has one thread that is usually monitoring a message queue and the MTA has a pool of threads "hanging out" waiting to be dispatched, a TNA contains only objects and the contexts of those objects. An object residing in a TNA shares a similarity with an object that aggregates the FTM. The thread that calls a method on the object is the thread which actually executes that method and the usual thread switch does not occur. However, in the case of the FTM, the object always executes in the context of the calling thread; an object that uses the FTM is saying that it does not want a context of its own. An object residing in a TNA, however, doeswant its own context and its methods will, in fact, always execute in that context no matter what thread enters the TNA. This is shown in Figure 7.9.

A TNA allows the calling thread direct access but preserves the object's context.

Figure 7.9. A TNA allows the calling thread direct access but preserves the object's context.

If you are developing components in VB6, TNA- and FTM-specific functionality is not available. All VB components execute in an STA, and it is not possible for them to aggregate the free-threaded marshaler. Of course, it certainly doesn't hurt to know a little about how these entities operate. So now that we have at least a cursory understanding of contexts at a lower level, we should take a look at how to leverage them for the benefit of our applications. Specifically, we will take a look at the context-oriented interfaces, mainly IObjectContextInfo, IContextState, ISecurityCallContext, and IGetContextProperties.

Understanding and Using the Context Interfaces

We discussed the methods of IObjectContext in the section, "Introducing IObjectContext." There are four other interfaces that are especially useful in interacting with the context object.

IObjectContextInfo

Frustratingly, IObjectContextInfo is not usable from VB6; its methods return GUIDs, which VB rejects with a message box proclaiming that the interface uses non-automation datatypes. It may be, however, that the designers of IObjectContextInfo did not feel that its methods were of much use to VB components. Granted, IsInTransaction() might be useful, but that method is already available in the VB-friendly IObjectContext. That leaves GetTransaction(), GetTransactionId(), GetActivityId(), and GetContextId() for us to consider.

Aside from GetTransaction(), which returns an ITransaction interface pointer (we'll talk more about this in the next chapter), these methods are informational. Arguably, they are only useful to components that perform tasks that VB components can't. Take object pooling for example. If your object can be aggregated and is thread-safe (neither of which is possible in VB—at least not yet), it can be temporarily deactivated (but kept alive) as opposed to destroyed when its client releases it. In other words, it is "pooled." At a later point in time, when another client requests that a new instance of the same type of object be created, creation and initialization overhead is eliminated as the deactivated object is simply reactivated and bound to the client. The methods of IObjectContextInfo are important for such an object because when it is reactivated, it might find itself in a strange new context. The object may need to determine exactly what transaction and activity now finds itself in and compare this state with its previous state. In the case of transactions, for example, a reactivated object will need to reenlist its database connection if the transaction ID it gets from GetTransactionId() upon activation differs from the transaction id it had when it was deactivated. For performance reasons, we would prefer not to reenlist a connection if the transaction id had not changed. IObjectContextInfo gives our object the information it needs to make the right decision. See Listing 7.6 for an example of a pooled, transactional object that investigates its current transaction upon being reactivated.

Example 7.6.  A Transactional, Pooled Object Determining Its Current Transaction Upon Being Activated

HRESULT PooledAndTransactional::Activate() {

IObjectContext *pObjectContext;
IObjectContextInfo *pObjectContextInfo;

bool bIsInTransaction;
HRESULT hr;
GUID transactionGuid;


// Obtain IObjectContext and IObjectContextInfo interface pointers:
hr = GetObjectContext (&pObjectContext);
if (hr !=S_OK)
{
    // Could not get IObjectContext:
    return hr;
}

hr=pObjectContext->QueryInterface(IID_IObjectContextInfo,
                  (void**)&pObjectContextInfo);
if (hr !=S_OK)
{
    // Could not get IObjectContextInfo:
    return hr;
}

bIsInTransaction = pObjectContext->IsInTransaction();
if (!bIsInTransaction)
{
    // The componenent is not participating in a transaction,
    // so we don't have to enlist anything:

    m_TransactionID = GUID_NULL;
    return S_OK;

}

pObjectContextInfo->GetTransactionId(&transactionGuid);

if (transactionGuid != m_TransactionID)
{

    // We are in a new transaction, enlist with the DTC:
    m_TransactionID = transactionGuid;

    // See chapter 8 for the implementation of EnlistDTC():
    EnlistDTC();
}

return S_OK;


}

ISecurityCallContext

ISecurityCallContext is used by an object to find out security-related information about its current caller. The methods of ISecurityCallContext will be familiar to you already, because you have seen them before as members of IObjectContext—specifically as the following:

  • IsCallerInRole

  • IsSecurityEnabled

  • IsUserInRole

I imagine you would agree that given the security-orientation of these methods, it makes the most sense to put them in a separate interface instead of lumping them together with voting and state functionality in IObjectContext. And ISecurityCallContext offers additional functionality as well. Specifically, this interface also acts as a VB-style collection. VB users can loop through different security properties via FOR..EACH. See Listing 7.7 for an example.

Example 7.7. Enumerating Through the Items In SecurityCallContext

Option Explicit

Dim iItemNum As Long
Dim sItems As String
Dim vntItem As Variant

Sub securityinfo()

sItems = "Items in the SecurityCallContext collection: " & vbNewLine & vbNewLine

'Loop through all the items in the SecurityCallContext collection.
'In VB you obtain this using GetSecurityCallContext(). In C++ you
'would use CoGetCallContext() and ask for ISecurityCallContext

For Each vntItem In GetSecurityCallContext()
    iItemNum = iItemNum + 1
    sItems = sItems & iItemNum & ".) " & vntItem & vbNewLine
Next

'Display the results:
MsgBox sItems

Executing the code in Listing 7.7 produces the following output shown in Figure 7.10.

In Chapter 12, I show you how to actually use these properties to determine the chain of callers and other security information. For now, however, while we're on the subject of properties, let's move on to the final interface we will be discussing in this chapter—the strange and intriguing IGetContextProperties.

The items in the SecurityCallContext object.

Figure 7.10. The items in the SecurityCallContext object.

IGetContextProperties

IGetContextProperties is an interesting method with very generic, non-specific method names like Count(), EnumNames(), and GetProperty(). This interface is purposefully vague because it is used to retrieve arbitrary interfaces from the context object. I say arbitrary, because a context object may actually act as a dispenser for other objects.

As I mentioned earlier in this chapter, the context object is extensible, at least by Microsoft. Just how to make it extensible is not documented for the benefit of non-Microsoft developers, but Microsoft's Internet Information Server (IIS) can and does extthe context object. IGetContextProperties can be used by COM+ objects running in ASP pages to obtain interface pointers to IIS intrinsic objects. Specifically, IIS makes five of its internal objects globally accessible by placing their interfaces in the context of objects that are created by Active Server Pages (ASP).

If you are not familiar with ASPs, here's a little history. In version 3.0, IIS introduced the concept of ASPs to provide the same functionality as CGI-bin scripts—that is, they allow HTML pages to be dynamically generated when requested by the client. In the ASP model, there exists an ordinary HTML file with pockets of VBScript (or JavaScript) interspersed throughout the page and denoted by the special tags <% and %>.

When a client requests an ASP from IIS, IIS runs the ASP. All the script in the page is run, and the code ultimately resolves to HTML. This process is sometimes referred to as server-side scripting.

Aside from the basic functionality offered by VBScript, the only way an ASP page can reach out and call upon external functionality is by creating and using COM objects. For example, if an ASP page wants to modify a database, it must call on a COM object to do the work. If the ASP page wants to retrieve data, it must also go through a COM object (perhaps RDO or ADO) to retrieve the records. After these records have been obtained, the ASP page converts the data into HTML and IIS ferries an ordinary (though dynamically created) HTML page to the client.

The relationship between IIS ASP and COM objects is interesting. If an ASP pageinstantiates a configured COM+ object, that object will have a context. If the ASP instantiates an unconfigured object, it will not. Because it stands to reason that most components that you use with ASPs will be configured, let's assume that all objects created and used by ASPs will have access to their context object.

ASP-created objects can, therefore QueryInterface(QI) for IGetContextProperties. With this interface, it is possible to enumerate through and access other objects and/or interfaces that might have been placed in the object's context. For example, if an ASP-created object requests this interface, the object can use it to snoop through other objects/interfaces that might have been placed in its context. In doing so, an ASP-created object finds that IIS has added the following IIS intrinsic objects to its context:

  • Request (IRequest)

  • Response (IResponse)

  • Server (IServer)

  • Application (IApplicationObject)

  • Session (ISessionObject)

Here is an example of how a configured object can get this list in VC++ using IObjectContextProperties. It is long and somewhat clunky and, as we see, requires many lines to obtain the same result as VB can with a single line. Listing 7.8 demonstrates how a configured C++ component run from an ASP page may obtain an IResponse interface to IIS's intrinsic Response object.

Example 7.8.  A VC++ Object Component Retrieving an IResponse Interface from Its Context

// obtaining of context and error checking omitted for brevity,
// see the book's accompanying source code for a complete example.

_bstr_t bstrPropName=L"Response";
VARIANT varProp;
VariantInit(&varProp);

// Obtain the IGetContextProperties interface from the
// IObjectContext interface:

pObjectContext->QueryInterface(
    IID_IGetContextProperties,
    (void**)&pGetContextProps);

// Now ask IGetContextProperties for the Response object.
// This method is somewhat awkward, in that it returns
// a VARIANT of the requested interface, which is really
// an IDispatch pointer of the Response object.

pGetContextProps->GetProperty(bstrPropName,&varProp);

// Obtain the IDispatch pointer the variant contains,
// and then QI this interface for the Response object.
// Again, error checking has been omitted:
pIDispatch = V_DISPATCH(&varProp);

pIDispatch->QueryInterface(IID_IResponse,(void**)&pIISResponse);

// Write something to the ASP page using the
// response object we have obtained:

pIISResponse->Write(CComVariant(
    OLESTR("Hello from an object being used by an ASP page!")));

return S_OK;

The following VB example is simpler but more abstracted:

GetObjectContext.Item("Response").Write "Hello from an object being used
by 'e5an ASP
page!"

The preceding code, if placed in a configured VB object used by an ASP, results in the sentence "Hello from an object being used by an ASP page!" in the HTML page that IIS gives to the client browser. The object is using its context object to gain access to IIS's Response object. Note that although the IGetContextProperties is not directly available to VB, it is most likely used behind the scenes.

Summary

In this chapter, we talk about contexts. Contexts are unique to each object and can be thought of as the universe that the object lives in. It encompasses all the attributes that you use to configure your component. You manipulate these attributes by using the Component Services snap-in or the COM administrative objects, as discussed in the previous chapter.

Information can flow from one object's context to the next in a creation chain. This allows different objects to cooperate in COM+ services, such as transaction management, because COM+ can look into the context of the creating object, set attributes of the created object; and if it determines that the two objects are compatible, COM+ can allow information to flow from the creator's context to the context of the created.

The concept of context originated with MTS. Any MTS object could, at any time, get in touch with its context object by asking MTS for an IObjectContext interface. When obtained, an MTS object could create other objects via IObjectContext's CreateInstance( ) method. By using this method instead of the standard COM creation functions, transactional information could flow from the context of the creating object to the context of the created object. In this way, multiple objects could share in a transaction.

Microsoft's IIS also takes advantage of contexts. As of IIS 4.0, IIS and MTS work closely together, so that the context of MTS objects can carry IIS-specific entities as well. The integration is so tight that an MTS object created from an ASP can obtain interfaces to a number of IIS-useful objects simply by querying its context object.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.41.134