© 2004 Microsoft Corporation. All rights reserved.
Figure 3 IServiceTransactionConfig
interface IServiceTransactionConfig : IUnknown
{
  HRESULT ConfigureTransaction(
    [in] CSC_TransactionConfig transactionConfig);
        
  HRESULT IsolationLevel( 
    [in] COMAdminTxIsolationLevelOptions option);
        
  HRESULT TransactionTimeout( 
    [in] ULONG ulTimeoutSec);
        
  HRESULT BringYourOwnTransaction( 
    [string][in] LPCWSTR szTipURL);
        
  HRESULT NewTransactionDescription( 
    [string][in] LPCWSTR szTxDesc);        
};
Figure 4 Service Configuration Interfaces

Interface
Description
IServiceInheritanceConfig
Sets default policy: inherit or ignore creating context's properties
IServiceThreadPoolConfig
Controls which thread pool (single-threaded or multithreaded apartment) new context will execute on when using CoCreateActivity
IServiceTransactionConfig
Controls whether new context will have a transaction, and if so, what isolation level and timeout will be
IServiceSynchronizationConfig
Controls whether new context will be limited to access by one causality at a time
IServiceIISIntrinsicsConfig
Controls whether IIS intrinsics are inherited by new context
IServiceCOMTIIntrinsicsConfig
Controls whether COM TI intrinsics are inherited by new context
IServiceTrackerConfig
Controls whether new context will report events and statistics to the COM+ management framework
Figure 5 CSC_TransactionConfig Values

Enumerated Value
Transaction Setting in COM+ Services Explorer
CSC_NoTransaction
Not Supported
CSC_IfContainerIsTransactional
Supported
CSC_CreateTransactionIfNecessary
Required
CSC_NewTransaction
Requires New
Figure 6 Using CoEnterServiceDomain
#include <comsvcs.h>   // COM+ header file
#include <atlbase.h>   // For ATL smart pointers

int main(void)
{
  // not in any context at this point
  HRESULT hr = CoInitializeEx(0,
               COINIT_MULTITHREADED); // init COM

  // in default context now
  CComPtr<IServiceTransactionConfig> spCtxCfg;
  hr = spCtxCfg.CoCreateInstance(
               L"COMSVCS.CServiceConfig.1");

  // Specify that the new context should have a transaction
  spCtxCfg->ConfigureTransaction(
               CSC_CreateTransactionIfNecessary);

  hr = CoEnterServiceDomain(spCtxCfg); // enter new context

  // in new context 

  // Work with database here, connections will autoenlist

  •••

  // Interact with object context ourselves,
  // ask transaction to commit
  CComPtr<IContextState> spObjCtx;
  hr = CoGetObjectContext(IID_IContextState,
                          (void**) &spObjCtx.p);
  spObjCtx->SetMyTransactionVote(TxCommit);

  // create own object to get transaction outcome
  CComPtr<ItransactionStatus> spTxStat;
  hr = SpTxState.CoCreateInstance(CLSID_TxStatus);
  
  // leave context - transaction will attempt to commit
  CoLeaveServiceDomain(spTxStat);

  // back in default context

  // extract HRESULT indicating transaction outcome
  HRESULT hrTx;
  hr = spTxState->GetTransactionStatus(&hrTx);

  return 0;
}
Figure 7 Context Property Flow
#include <comsvcs.h>   // COM+ header file
#include <atlbase.h>   // For ATL smart pointers
 
// Forward declarations
void WillInherit();
void WontInherit();
void PrintContextAndTransactionId();
 
int main(void)
{
  // not in any context at this point
  CoInitializeEx(0, COINIT_MULTITHREADED); // init COM
 
  // in default context now
  printf("Should be in default context\n");
  PrintContextAndTransactionId();
 
  CComPtr<IServiceTransactionConfig> spCtxCfg;
  spCtxCfg.CoCreateInstance(L"COMSVCS.CServiceConfig.1");
 
  // Specify that the new context should have a transaction
  spCtxCfg->ConfigureTransaction(CSC_CreateTransactionIfNecessary);
 
  CoEnterServiceDomain(spCtxCfg); // enter new context
 
  // in new context 
  printf("Should be in new transactional context\n");
  PrintContextAndTransactionId();
 
  WillInherit();
 
  printf("Should be back in first transactional context\n");
  PrintContextAndTransactionId();
 
  WontInherit();
 
  printf("Should be back in first transactional context again\n");
  PrintContextAndTransactionId();
 
  // leave the first context 
  CoLeaveServiceDomain(0); 
 
  // back in default context
  printf("Should be back in default context\n");
  PrintContextAndTransactionId();
 
  return 0;
}
 
void WillInherit()
{
  CComPtr<IServiceTransactionConfig> spCtxCfg;
  spCtxCfg.CoCreateInstance(L"COMSVCS.CServiceConfig.1");
 
  // Specify that the new context should have a transaction
  //  and that the creating context's transaction should be used
  spCtxCfg->ConfigureTransaction(CSC_CreateTransactionIfNecessary);
 
  // enter new context, inheriting transaction 
  CoEnterServiceDomain(spCtxCfg); 
 
  printf("Should be in new context with same transaction\n");
  PrintContextAndTransactionId();
 
  // Transition back out of context
  CoLeaveServiceDomain(0);
 
}
 
void WontInherit()
{
  CComPtr<IServiceTransactionConfig> spCtxCfg;
  spCtxCfg.CoCreateInstance(L"COMSVCS.CServiceConfig.1");
 
  // Specify that the new context should have a transaction
  //  and that the creating context's transaction should NOT be used
  spCtxCfg->ConfigureTransaction(CSC_NewTransaction);
 
  // enter new context
  CoEnterServiceDomain(spCtxCfg); 
 
  printf("Should be in new context with new transaction\n");
  PrintContextAndTransactionId();
 
  // Transition back out of context
  CoLeaveServiceDomain(0);
 
}
 
void PrintContextAndTransactionId()
{
  CComPtr<IUnknown> spCtx;
  CoGetObjectContext(__uuidof(spCtx), (void**) &spCtx);
 
  CComPtr<IObjectContextInfo> spCtxInfo;
  HRESULT hr = spCtx.QueryInterface(&spCtxInfo);
 
  if (FAILED(hr))
  {
    // We're in the default context if the QI failed
    printf("In the default context\n\n");
    return;
  }
 
  // If we're not in the default context, we can retrieve the
  //  context ID
  GUID ctxId;
  spCtxInfo->GetContextId(&ctxId);
 
  // If the context is transactional, retrieve the tx ID
  GUID txId;
  hr = spCtxInfo->GetTransactionId(&txId);
 
  // Convert the IDs into strings
  OLECHAR wszCtxId[64];
  OLECHAR wszTxId[64];
  ::StringFromGUID2(ctxId, wszCtxId, 64);
 
  // The call to GetTransactionId will only work in a txl context
  if (SUCCEEDED(hr))
  {
    ::StringFromGUID2(txId, wszTxId, 64);
  }
  else
  {  
    wcscpy(wszTxId, L"Non-transactional context");
  }
 
  // Print out the info about the context
  printf("Context ID: %S\nTransaction ID: %S\n\n", wszCtxId, wszTxId);
} 
Figure 9 Transaction Output
Should be in default context
In the default context

Should be in new transactional context
Context ID: {07291086-E204-4D12-ACEF-ADBDF83044FF}
Transaction ID: {5C18EBE8-9549-4CA2-8FD7-2BB7991BF18F}

Should be in new context with same transaction
Context ID: {8CA70E5B-DDC8-4E87-8E61-102D306EB0BB}
Transaction ID: {5C18EBE8-9549-4CA2-8FD7-2BB7991BF18F}

Should be back in first transactional context
Context ID: {07291086-E204-4D12-ACEF-ADBDF83044FF}
Transaction ID: {5C18EBE8-9549-4CA2-8FD7-2BB7991BF18F}

Should be in new context with new transaction
Context ID: {9162AFB6-33A3-4FAF-BFE8-4A337D8C82EF}
Transaction ID: {5277C98F-5180-452A-B00C-0A945B06970A}

Should be back in first transactional context again
Context ID: {07291086-E204-4D12-ACEF-ADBDF83044FF}
Transaction ID: {5C18EBE8-9549-4CA2-8FD7-2BB7991BF18F}

Should be back in default context
In the default context
Figure 10 C# Wrapper Library
using System;
using System.Reflection;
using System.Runtime.InteropServices;

namespace MSDN
{
  [ComImport]
  [Guid("61F589E8-3724-4898-A0A4-664AE9E1D1B4")]
  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  public interface ITransactionStatus
  {
    void SetTransactionStatus(int hrStatus);
        
    void GetTransactionStatus( 
      out int pHrStatus);
  }

  public struct MyTxStatus : ITransactionStatus
  {
    public int _hr = 0;
    public void SetTransactionStatus(int hrStatus)
    {
      _hr = hrStatus;
    }
        
    public void GetTransactionStatus(out int pHrStatus)
    {
      pHrStatus = _hr;
    }
  }

  public class ComPlusUtil
  {

    [DllImport("comsvcs.dll", PreserveSig=false)]
    public static extern void CoEnterServiceDomain(
      [MarshalAs(UnmanagedType.IUnknown)] object cfg);    [DllImport("comsvcs.dll", PreserveSig=false)]
    public static extern void CoLeaveServiceDomain(
      [MarshalAs(UnmanagedType.IUnknown)] object status);

  }
}
Figure 11 Transactional Client Code in C#
using System;
using MSDN;
using COMSVCSLib;  // tlbimp'd namespace
using System.EnterpriseServices; // Need ContextUtil

namespace MSDN
{
  class ContextBasics
  {
    static void Main()
    {
      // In default context here

      IServiceTransactionConfig cfg = (IServiceTransactionConfig)
         new CServiceConfigClass();

      // Specify that the new context should have a transaction
      cfg.ConfigureTransaction(
          tagCSC_TransactionConfig.
             CSC_CreateTransactionIfNecessary);
    
      // enter new transactional context
      ComPlusUtil.CoEnterServiceDomain(cfg);

      // Work with database here, connections will autoenlist

      •••

      // Interact with object context, ask transaction to commit
      ContextUtil.MyTransactionVote =
         TransactionVote.Commit;
  
      // leave context, transaction will attempt to commit
      MyTxStatus stat = new MyTxStatus();      
      ComPlusUtil.CoLeaveServiceDomain(stat);
      
      // if transaction aborted, raise exception
      if (stat._hr != 0)
        throw new COMException(
                  "Transaction aborted", stat._hr);

      // back in default context
    }
  }
}
Figure 12 Semi-transactional Classes
using System;
using System.EnterpriseServices;
using MSDN;
using COMSVCSLib;

namespace MSDN
{
  public class SemiTransactional
  {
    public void Transactional()
    {
      // Enter a new transactional context
      IServiceTransactionConfig cfg = (IServiceTransactionConfig)
         new CServiceConfigClass();

      cfg.ConfigureTransaction(
            tagCSC_TransactionConfig.
               CSC_CreateTransactionIfNecessary);

      ComPlusUtil.CoEnterServiceDomain(cfg);

      // place to store transaction outcome
      MyTxStatus stat = new MyTxStatus();
      
      try
      {
        // Always a good idea default to abort
        // in case something unexpected happens. 
        ContextUtil.MyTransactionVote = TransactionVote.Abort;

        // Work with database here, connections
        // will autoenlist
        ...

        // vote to commit transaction
        ContextUtil.MyTransactionVote = TransactionVote.Commit;
      }
      finally
      {
        // Leave the context, transaction will attempt to commit
        ComPlusUtil.CoLeaveServiceDomain(stat);
      }
      if (stat._hr != 0)
          throw new COMException(
                    "Transaction aborted", stat._hr);
      return;
    }

    public void NonTransactional()
    {
      // Enter a new non-transactional context
      // We need to do this to protect against
      // the case where someone calls us from
      // within a transactional context.
      // If we don't enter a new context that
      // has no transaction, we might accidentally
      // enlist on our caller's transaction
      IServiceTransactionConfig cfg = (IServiceTransactionConfig)
         new CServiceConfigClass();

      cfg.ConfigureTransaction(
          tagCSC_TransactionConfig.CSC_NoTransaction);

      ComPlusUtil.CoEnterServiceDomain(cfg);

      try
      {
        // Work with database here, connections will NOT autoenlist
        •••
      }
      finally
      {
        // Leave the context
        ComPlusUtil.CoLeaveServiceDomain(null);
      }
      return;
    }
  }
}
Figure 13 IServiceActivity and IServiceCall Interfaces
interface IServiceCall : IUnknown
{
  HRESULT OnCall(void);
};

interface IServiceActivity : IUnknown
{
  HRESULT SynchronousCall(
    [in] IServiceCall *pIServiceCall);
        
  HRESULT AsynchronousCall( 
    [in] IServiceCall *pIServiceCall);
        
  HRESULT BindToCurrentThread(void);
  HRESULT UnbindFromThread(void);
};

Home
Topics
Issues
Downloads
Subscribe
Submit an Article

MSDN Magazine