2025-11-28 00:35:46 +09:00

620 lines
20 KiB
C++

//---------------------------------------------------------------------
// This file is part of the Microsoft .NET Framework SDK Code Samples.
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//This source code is intended only as a supplement to Microsoft
//Development Tools and/or on-line documentation. See these other
//materials for detailed information regarding Microsoft code samples.
//
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//---------------------------------------------------------------------
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <tchar.h>
#include <assert.h>
#include <string>
#include <iostream>
#include "txdtc.h"
#include "txcoord.h"
#include "xolehlp.h"
// This is the GUID of the Resource Manager that uniquely identifies it
// It should not be reused. Generate a new one for each Resource Manager
// {08C6599C-C781-42a2-B1E6-C7E4C95F6938}
static const GUID ResourceManagerGUID = { 0x8c6599c, 0xc781, 0x42a2, { 0xb1, 0xe6, 0xc7, 0xe4, 0xc9, 0x5f, 0x69, 0x38 } };
class DTCResourceManager : public IResourceManagerSink, ITransactionResourceAsync
{
public:
DTCResourceManager();
bool Init();
~DTCResourceManager();
bool OpenConnection(byte* transactionToken, ULONG tokenSize);
bool DoWork();
bool CloseConnection();
virtual HRESULT STDMETHODCALLTYPE PrepareRequest(
/* [in] */ BOOL fRetaining,
/* [in] */ DWORD grfRM,
/* [in] */ BOOL fWantMoniker,
/* [in] */ BOOL fSinglePhase);
virtual HRESULT STDMETHODCALLTYPE CommitRequest(
/* [in] */ DWORD grfRM,
/* [unique][in] */ XACTUOW *pNewUOW);
virtual HRESULT STDMETHODCALLTYPE AbortRequest(
/* [unique][in] */ BOID *pboidReason,
/* [in] */ BOOL fRetaining,
/* [unique][in] */ XACTUOW *pNewUOW);
virtual HRESULT STDMETHODCALLTYPE TMDown(void);
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ void **ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef( void);
virtual ULONG STDMETHODCALLTYPE Release( void);
private:
HRESULT RegisterWithMSDTC();
bool DoRecovery();
// pointer used to enlist in transactions
IResourceManager* _pRMManager;
// pointer used to reply to enlistment requests
ITransactionEnlistmentAsync* _pEnlist;
// RM name
const std::string _rmName;
// used for IUnknown lifetime management
ULONG _cRef;
};
DTCResourceManager::DTCResourceManager() : _rmName("NativeRMSample")
{
_pRMManager = NULL;
}
DTCResourceManager::~DTCResourceManager()
{}
// Init method needs to be called to initialize the resource manager before any work is done
bool DTCResourceManager::Init()
{
HRESULT hr = S_OK;
hr = RegisterWithMSDTC();
if( FAILED(hr) )
{
std::cout << "The resource manager failed to Init. Error # " << std::hex << hr << std::endl;
return false;
}
// recover any transactions that are not completed
bool returnValue = DoRecovery();
return returnValue;
}
HRESULT DTCResourceManager::RegisterWithMSDTC()
{
IResourceManagerFactory* pRMFactory = NULL;
HRESULT hr = S_OK ;
hr = DtcGetTransactionManager(
NULL, // [in] char * pszHost,
NULL, // [in] char * pszTmName,
IID_IResourceManagerFactory, // [in] REFIID riid,
0, // [in] DWORD dwReserved1,
0, // [in] WORD wcbVarLenReserved2,
(void *)NULL, // [in] void * pvVarDataReserved2,
(void **)&pRMFactory // [out] void ** ppv
);
if (FAILED (hr))
{
std::cout << "DtcGetTransactionManager failed: Error # " << std::hex << hr << ".";
std::cout << " Make sure MSDTC service is running." << std::endl;
goto cleanup;
}
std::cout << "The resource manager is registering with MSDTC" << std::endl;
hr = pRMFactory->Create(const_cast<GUID*>(&ResourceManagerGUID),
const_cast<char*>(_rmName.c_str()), this, &_pRMManager);
if (FAILED (hr))
{
std::cout << "IResourceManagerFactory->Create failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
cleanup:
if(pRMFactory)
{
pRMFactory->Release();
pRMFactory = NULL;
}
if( FAILED(hr) )
{
std::cout << "Failed to register the resource manager with MSDTC" << std::endl;
}
return hr;
}
bool DTCResourceManager::DoRecovery()
{
std::cout << "The resource manager is doing recovery for any in doubt transaction found in the log" << std::endl;
// Typical things that happen here:
// - read log file
// - for each tx in the log file, call IResourceManager::Reenlist
// - if all Reenlists succeeded, call IResourceManager::ReenlistmentComplete()
return true;
}
// OpenConnection is called by clients to initiate work with the resource manager under a specified transaction
// The transaction is passed in as a transaction token, to show how transactions objects can be serialized across
// processes
bool DTCResourceManager::OpenConnection(byte* transactionToken, ULONG tokenSize)
{
std::cout << "The resource manager received an OpenConnection request. Enlisting in the transaction..." << std::endl;
ITransactionReceiverFactory* pTxReceiverFactory = NULL;
HRESULT hr = DtcGetTransactionManager(
NULL, // [in] char * pszHost,
NULL, // [in] char * pszTmName,
IID_ITransactionReceiverFactory, // [in] REFIID riid,
0, // [in] DWORD dwReserved1,
0, // [in] WORD wcbVarLenReserved2,
(void *)NULL, // [in] void * pvVarDataReserved2,
(void **)&pTxReceiverFactory // [out] void ** ppv
);
if (FAILED (hr))
{
std::cout << "DtcGetTransactionManager for ITransactionReceiverFactory failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
ITransactionReceiver* pTxReceiver = NULL;
hr = pTxReceiverFactory->Create(&pTxReceiver);
if (FAILED(hr))
{
std::cout << "pTxReceiverFactory->Create failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
ITransaction* pTx = NULL;
hr = pTxReceiver->UnmarshalPropagationToken(tokenSize, transactionToken, &pTx);
if (FAILED(hr))
{
std::cout << "pTxReceiver->UnmarshalPropagationToken failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
XACTUOW uow;
LONG isoLevel;
hr = _pRMManager->Enlist(pTx, this, &uow, &isoLevel, &_pEnlist);
if (FAILED(hr))
{
std::cout << "pRMManager->Enlist failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
cleanup:
if( pTx )
{
pTx->Release();
pTx = NULL;
}
if( pTxReceiver )
{
pTxReceiver->Release();
pTxReceiver = NULL;
}
if( pTxReceiverFactory )
{
pTxReceiverFactory->Release();
pTxReceiverFactory = NULL;
}
if( FAILED(hr) )
{
std::cout << "OpenConnection failed" << std::endl;
return false;
}
return true;
}
// DoWork simulates work requested by the client to be done under the transaction specified in the OpenConnection
// This is similar to UPDATE SQL statements sent to your regular database
bool DTCResourceManager::DoWork()
{
std::cout << "The resource manager is doing work as part of the transaction" << std::endl;
// the work is usually done in a temporary area for this particular transaction
return true;
}
// The client indicates that it has finished work with the resource manager
bool DTCResourceManager::CloseConnection()
{
std::cout << "The resource manager is asked to close the connection with the client" << std::endl;
return true;
}
// Event received from MSDTC during Phase 1 of the transaction
HRESULT DTCResourceManager::PrepareRequest(BOOL fRetaining, DWORD grfRM, BOOL fWantMoniker, BOOL fSinglePhase)
{
std::cout << "The resource manager received PrepareRequest. Preparing..." << std::endl;
// The work done in DoWork is moved from the temporary area to a "prepared" area
// Once the work is moved in the "prepared" area and PrepareRequestDone, the resource manager must be able
// to either Commit or Abort this "work" at any time when asked by the Transaction Manager, even surviving crashes
// To survive crashes, the resource manager will save the "prepare info" to the log file before replying PrepareRequestDone
// The prepare info is obtained using the following steps:
// 1. Call QueryInterface on the ITransactionEnlistmentAsync interface on the enlistment object with an riid of IID_IPrepareInfo2
// 2. Using IPrepareInfo2 methods, GetPrepareInfoSize and GetPrepareInfo, obtain the byte array containing the prepare info
// At start up, after a crash, the resource manager will read the log file and for each prepare info found, it will
// call Reenlist
_pEnlist->PrepareRequestDone(S_OK, NULL, NULL);
return S_OK;
}
// Event received from MSDTC during Phase 2 to indicate that the transaction needs to be commited
HRESULT DTCResourceManager::CommitRequest(DWORD grfRM, XACTUOW *pNewUOW)
{
std::cout << "The resource manager received CommitRequest. Commiting..." << std::endl;
// move the "prepared" work from the prepared area to the real data
_pEnlist->CommitRequestDone(S_OK);
std::cout << "The resource manager has completed the transaction." << std::endl;
return S_OK;
}
// Event received from MSDTC during Phase 2 to indicate that the transaction needs to be aborted
HRESULT DTCResourceManager::AbortRequest(BOID *pboidReason, BOOL fRetaining, XACTUOW *pNewUOW)
{
std::cout << "The resource manager received AbortRequest. Aborting..." << std::endl;
// remove any temporary or prepared work as part of this transaction
_pEnlist->AbortRequestDone(S_OK);
return S_OK;
}
// If MSDTC goes down, or the current process looses the connection with MSDTC, then this event is raised
HRESULT DTCResourceManager::TMDown(void)
{
assert(false); // not yet implemented
// here we put recovery code that will "restart the RM": reconnect with MSDTC, re-read the log and
// recover transactions found in it, and start listening for clients
return S_OK;
}
HRESULT DTCResourceManager::QueryInterface(REFIID i_iid, void** o_ppv)
{
HRESULT hr = S_OK;
if( NULL == o_ppv )
{
return E_INVALIDARG;
}
if( IID_IUnknown == i_iid )
{
*o_ppv = (IUnknown*)((IResourceManagerSink*)this);
}
else if( IID_IResourceManagerSink == i_iid )
{
*o_ppv = ( IResourceManagerSink* ) this;
}
else
{
*o_ppv = NULL;
hr = E_NOINTERFACE;
}
if( SUCCEEDED(hr) )
{
((IUnknown *) *o_ppv)->AddRef();
}
return hr;
}
ULONG DTCResourceManager::AddRef()
{
return ( InterlockedIncrement( (LONG*) &this->_cRef ) );
}
ULONG DTCResourceManager::Release()
{
ULONG localCount = InterlockedDecrement( (LONG*) &this->_cRef );
if ( 0 == localCount )
{
delete this;
}
return localCount;
}
// This function will create an MSDTC transaction
bool CreateDTCTransaction(ITransaction** ppTransaction)
{
if(!ppTransaction)
{
std::cout << "NULL argument passed to CreateDTCTransaction" << std::endl;
return false;
}
ITransactionDispenser* pTransactionDispenser = NULL;
ITransaction* pTransaction = NULL;
HRESULT hr = S_OK ;
// Obtain a transaction dispenser interface pointer from the DTC.
hr = DtcGetTransactionManager(
NULL, // [in] char * pszHost,
NULL, // [in] char * pszTmName,
IID_ITransactionDispenser, // [in] REFIID riid,
0, // [in] DWORD dwReserved1,
0, // [in] WORD wcbVarLenReserved2,
(void *)NULL, // [in] void * pvVarDataReserved2,
(void **)&pTransactionDispenser // [out] void ** ppv
);
if (FAILED (hr))
{
std::cout << "DtcGetTransactionManager failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
std::cout << "The client creates a transaction" << std::endl;
// Initiate a DTC transaction.
hr = pTransactionDispenser->BeginTransaction(
NULL, // [in] IUnknown * punkOuter,
ISOLATIONLEVEL_SERIALIZABLE, // [in] ISOLEVEL isoLevel,
ISOFLAG_RETAIN_NONE, // [in] ULONG isoFlags,
NULL, // [in] ITransactionOptions * pOptions,
&pTransaction // [out] ITransaction * ppTransaction
);
if (FAILED (hr))
{
std::cout << "BeginTransaction failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
cleanup:
if(pTransactionDispenser)
{
pTransactionDispenser->Release();
pTransactionDispenser = NULL;
}
if( FAILED(hr) )
{
std::cout << "CreateDTCTransaction failed. No transaction was created." << std::endl;
return false;
}
else
{
*ppTransaction = pTransaction;
return true;
}
}
// This function will "marshal" a transaction object into a byte array called token
// The token can be passed across processes, machines to resource managers so that they can enlist
// in the transaction
bool MarshalDTCTransaction(ITransaction* pTransaction, byte** ppTxToken, ULONG* pTokenSize)
{
if( (!pTransaction) || (!ppTxToken) || (!pTokenSize) )
{
std::cout << "NULL argument passed to MarshalDTCTransaction" << std::endl;
return false;
}
HRESULT hr = S_OK;
// Obtain a transaction dispenser interface pointer from the DTC.
ITransactionDispenser* pTransactionDispenser = NULL;
hr = DtcGetTransactionManager(
NULL, // [in] char * pszHost,
NULL, // [in] char * pszTmName,
IID_ITransactionDispenser, // [in] REFIID riid,
0, // [in] DWORD dwReserved1,
0, // [in] WORD wcbVarLenReserved2,
(void *)NULL, // [in] void * pvVarDataReserved2,
(void **)&pTransactionDispenser // [out] void ** ppv
);
if (FAILED (hr))
{
std::cout << "DtcGetTransactionManager failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
ITransactionTransmitterFactory* pTxTransmitterFactory = NULL;
hr = pTransactionDispenser->QueryInterface(IID_ITransactionTransmitterFactory, (void**)&pTxTransmitterFactory);
if (FAILED(hr))
{
std::cout << "pTransactionDispenser->QueryInterface(IID_ITransactionTransmitterFactory , pTxTransmitterFactory) failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
ITransactionTransmitter* pTxTransmitter = NULL;
hr = pTxTransmitterFactory->Create(&pTxTransmitter);
if (FAILED(hr))
{
std::cout << "pTxTransmitterFactory->Create failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
hr = pTxTransmitter->Set(pTransaction);
if (FAILED(hr))
{
std::cout << "pTxTransmitter->Set failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
ULONG tokenSize = 0;
hr = pTxTransmitter->GetPropagationTokenSize(&tokenSize);
if (FAILED(hr))
{
std::cout << "pTxTransmitter->GetPropagationTokenSize failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
byte* token = NULL;
try
{
token = new byte[tokenSize];
}
catch( std::bad_alloc )
{
token = NULL;
}
if( NULL == token )
{
std::cout << "Failed to allocate memory." << std::endl;
hr = E_FAIL;
goto cleanup;
}
ULONG tokenSizeUsed = 0;
hr = pTxTransmitter->MarshalPropagationToken(tokenSize, token, &tokenSizeUsed);
if (FAILED(hr))
{
std::cout << "pTxTransmitter->MarshalPropagationToken failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
hr = pTxTransmitter->Reset();
if (FAILED(hr))
{
std::cout << "pTxTransmitter->Reset failed: Error # " << std::hex << hr << std::endl;
goto cleanup;
}
cleanup:
if( pTxTransmitter )
{
pTxTransmitter->Release();
pTxTransmitter = NULL;
}
if( pTxTransmitterFactory )
{
pTxTransmitterFactory->Release();
pTxTransmitterFactory = NULL;
}
if( pTransactionDispenser )
{
pTransactionDispenser->Release();
pTransactionDispenser = NULL;
}
if( FAILED(hr) )
{
std::cout << "MarshalDTCTransaction failed" << std::endl;
delete[] token;
token = NULL;
return false;
}
*ppTxToken = token;
*pTokenSize = tokenSize;
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
// Starting up the resource manager
// Note: In most of the cases, a "durable" resource manager lives in its own process
// Here the resource manager is in the same process with the client for readability
DTCResourceManager* resourceManager = NULL;
try
{
resourceManager = new DTCResourceManager();
}
catch( std::bad_alloc )
{
resourceManager = NULL;
}
if( NULL == resourceManager )
{
std::cout << "Failed to allocate memory. The program will exit." << std::endl;
exit(1);
}
if(!resourceManager->Init())
{
std::cout << "The resource manager failed to start. The program will exit." << std::endl;
exit(1);
}
std::cout << "The client starts..." << std::endl;
// client starts, creates a transaction, does some work on the resource manager and later calls commit
ITransaction* pTransaction = NULL;
if( !CreateDTCTransaction(&pTransaction) )
{
std::cout << "Failed to create transaction. The program will exit." << std::endl;
exit(1); // Replace with specific error handling
}
byte* txToken = NULL;
ULONG txTokenSize = 0;
if( !MarshalDTCTransaction(pTransaction, &txToken, &txTokenSize) )
{
std::cout << "Failed to marshal the transaction. The program will exit." << std::endl;
exit(1); // Replace with specific error handling
}
std::cout << "The client asks the resource manager to do work as part of the transaction" << std::endl;
if( !resourceManager->OpenConnection(txToken, txTokenSize))
{
std::cout << "The client failed to open the connection with the resource manager. The program will exit." << std::endl;
exit(1);
}
resourceManager->DoWork();
resourceManager->CloseConnection();
std::cout << "The client commits the transaction" << std::endl;
// Commit the transaction.
HRESULT hr = S_OK;
hr = pTransaction->Commit(
FALSE, // [in] BOOL fRetaining,
XACTTC_SYNC_PHASEONE, // [in] DWORD grfTC,
0 // [in] DWORD grfRM
);
if (FAILED(hr))
{
std::cout << "pTransaction->Commit() failed: Error # " << std::hex << hr << std::endl;
exit(1); // Replace with specific error handling.
}
// Release the transaction object.
pTransaction->Release();
pTransaction = NULL;
delete[] txToken;
txToken = NULL;
std::cout << std::endl << "The client exits" << std::endl;
// Since the resource manager is sharing its lifetime with the client process in this sample,
// to avoid "failed to notify" transactions, we will ask for the user to keep this process alive
// until the resource manager will complete the transaction
std::cout << std::endl << "Press ENTER after you see the Resource Manager completing the transaction." << std::endl;
std::cin.get();
delete resourceManager;
resourceManager = NULL;
return 0;
}