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);
};
|