Microsoft Corporation
August 1999
Summary: Provides a description of COM+ Services 1.0 object pooling service. (10 printed pages)
Microsoft Visual Studio® 6.0 is the tool for building applications that take advantage of Microsoft Windows® 2000 and COM+ Services 1.0. In this scenario, we will demonstrate the features of object pooling and how you can take advantage of this COM+ service using Visual C++® 6.0. (Note that object pooling cannot be used with Visual Basic® or Visual FoxPro®—see “Requirements for Poolable Objects” for details.)
Object pooling is an automatic service provided by COM+ that enables you to configure a component so that instances of it are kept active in a pool, ready to be used by any client that requests the component. You can administratively configure and monitor the pool maintained for a given component, specifying characteristics such as pool size and creation request timeout values. Once the application is running, COM+ manages the pool for you, handling the details of object activation and reuse according to the criteria you have specified.
You can achieve significant performance and scalability benefits by reusing objects in this manner, particularly when they are written to take full advantage of reuse. With object pooling you can:
When you configure a component to be pooled, COM+ will maintain instances of it in a pool, ready to be activated for any client requesting the component. Any object creation requests will be handled through the pool manager.
When the application starts, the pool will be populated up to the minimum level that you have administratively specified, so long as object creation succeeds. As client requests for the component come in, they are satisfied on a first-come, first-served basis from the pool. If no pooled objects are available, and the pool is not yet at its specified maximum level, a new object is created and activated for the client.
When the pool reaches its maximum level, client requests are queued, and will receive the first available object from the pool. The number of objects, both activated and deactivated, will never exceed the maximum pool value.
Whenever possible, COM+ will attempt to reuse an object once a client releases it, until the pool reaches its maximum level. The object is responsible for monitoring its own state to determine whether it can be reused, and should return an appropriate value for IObjectControl::CanBePooled.
When a pooled object is created, it is aggregated into a larger object that will manage the object’s lifetime. The outer object calls methods on IObjectControl at appropriate times in the object’s lifecycle:
Sample for Visual C++ 6.0 only. See the section “Requirements for Poolable Objects” for more information.
This example demonstrates an implementation of the required IObjectControl interface and methods associated with a transactional, poolable object. The object’s connection to the database lends the object to being pooled to eliminate the overhead encountered by frequent construction and destruction. For more context, or full source code for this example, see the Windows 2000 Platform SDK, Object Pooling samples.
This code allows a transactional object to take advantage of the scalability and performance gains of object pooling by having implemented IObjectControl and manually enlisting the database in the transaction. Object pooling only works if the Enable Object Pooling check box and associated pool size settings have been properly configured in the Component Services Explorer. Then, when the object is called (see CUpdateReceipt::Update), we set our database connection, manually call Enlist to start our transaction, do our work, call SetComplete or SetAbort, and then let the pool manager make the following three calls: Deactivate, then CanBePooled, followed by an Activate to start the cycle over again. COM+ handles all aspects of pooling (Construct, Activate, Deactivate, CanBePooled, and so on).
// Filename: UpdateReceipt.cpp
//
// Description: Implementation of CUpdateReceipt
//
// THIS CODE AND INFORMATION IS 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.
#include “stdafx.h”
#include “Account.h”
#include <comsvcs.h>
#include “UpdRec.h”
#include <transact.h>
HRESULT GetConnection(WCHAR * sDSN, HENV * phenv, HDBC * phdbc, HSTMT * phstmt, BOOL bUseObjectPool);
extern void GetODBCError(HENV henv, HDBC hdbc, HSTMT hstmt, BSTR * sErr);
STDMETHODIMP CUpdateReceipt::Activate()
{
// should only do this when we are being activated in different
// tx units of work
m_bShouldEnlist = FALSE;
if (!m_bObjectPooled)
return S_OK;
HRESULT hr;
IObjectContext * pObjectContext = NULL;
hr = CoGetObjectContext(IID_IObjectContext, (void**)&pObjectContext);
_ASSERTE(pObjectContext);
GUID txUOW = GUID_NULL;
IObjectContextInfo * pObjTx = NULL;
pObjectContext -> QueryInterface(IID_IObjectContextInfo, (void **)&pObjTx);
if (pObjTx)
{
pObjTx -> GetTransactionId (&txUOW);
pObjTx -> Release();
}
pObjectContext -> Release();
if (txUOW != m_txuow)
{
m_bShouldEnlist = TRUE;
m_txuow = txUOW;
}
return S_OK;
}
HRESULT CUpdateReceipt::Enlist()
{
HRESULT hr;
IObjectContext * pObjectContext = NULL;
hr = CoGetObjectContext(IID_IObjectContext, (void**)&pObjectContext);
_ASSERTE(pObjectContext);
IObjectContextInfo * pObjTx = NULL;
pObjectContext -> QueryInterface(IID_IObjectContextInfo, (void **)&pObjTx);
pObjectContext -> Release();
if (pObjTx)
{
ITransaction * pTx = NULL;
pObjTx -> GetTransaction ((IUnknown **)&pTx);
RETCODE rc ;
if (pTx)
{
rc = SQLSetConnectOption(m_hdbc, SQL_ATTR_ENLIST_IN_DTC, (UDWORD)pTx);
if (SQL_FAILED(rc))
hr = E_FAIL;
pTx -> Release();
}
pObjTx -> Release();
}
return hr;
}
// Update: update the database next receipt number when 100 receipts have been issued by GetReceipt
//
// returns: S_OK or E_FAIL
STDMETHODIMP CUpdateReceipt::Update (OUT long* plReceiptNo)
{
HRESULT hr = S_OK;
IObjectContext* pObjectContext = NULL;
RETCODE rc;
BSTR sErr = 0;
long len = 0;
if (!m_bConstructed)
{
m_sDSN = L”FILEDSN=MTSSamples;DATABASE=pubs;UID=sa;PWD=;APP=VC Bank”;
hr = GetConnection(m_sDSN.m_str, &m_henv, &m_hdbc, &m_hstmt, m_bObjectPooled);
if (FAILED(hr))
{
BSTR sResult;
GetODBCError(m_henv, m_hdbc, NULL, &sResult);
AtlReportError( CLSID_CAccount, sResult, IID_IAccount, hr);
SysFreeString(sResult);
return hr;
}
}
hr = CoGetObjectContext(IID_IObjectContext, (void**)&pObjectContext);
if (!pObjectContext)
{
sErr = SysAllocString(L”No Object Context”);
goto end;
}
if (m_bShouldEnlist)
{
//
// NOTE: I know I call SetComplete, so I don’t have to set
// the m_bShouldEnlist flag to FALSE after I enlist
//
hr = Enlist();
if (FAILED(hr))
return hr;
}
*plReceiptNo = 0;
rc = SQLExecDirect(m_hstmt, L”Update Receipt set NextReceipt = NextReceipt + 100”, SQL_NTS);
if (SQL_FAILED(rc))
{
GetODBCError(m_henv, m_hdbc, m_hstmt, &sErr);
hr = E_FAIL;
goto end;
}
// Obtain the desired recordset with an SQL query
rc = SQLExecDirect(m_hstmt, L”SELECT NextReceipt FROM Receipt”, SQL_NTS);
if (SQL_FAILED(rc))
{
GetODBCError(m_henv, m_hdbc, m_hstmt, &sErr);
hr = E_FAIL;
goto end;
}
while (SQLFetch(m_hstmt) == SQL_SUCCESS) // only get the first one for now
rc = SQLGetData(m_hstmt, 1, SQL_C_LONG, plReceiptNo, 0, &len);
SQLFreeStmt(m_hstmt, SQL_CLOSE);
pObjectContext -> SetComplete();
end:
if (sErr)
{
AtlReportError( CLSID_CUpdateReceipt, sErr, IID_IUpdateReceipt, hr);
SysFreeString(sErr);
pObjectContext -> SetAbort();
}
SafeRelease(pObjectContext);
return hr;
}
// Filename: UpdateReceipt.h
//
// Description: Declaration of UpdateReceipt
//
// THIS CODE AND INFORMATION IS 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.
#include “resource.h” // main symbols
#include “comsvcs.h”
/////////////////////////////////////////////////////////////////////////////
// CUpdateReceipt
class CUpdateReceipt :
public CComDualImpl<IUpdateReceipt, &IID_IUpdateReceipt, &LIBID_ACCOUNT>,
public ISupportErrorInfo,
public CComObjectRoot,
public IObjectConstruct,
public IObjectControl,
public CComCoClass<CUpdateReceipt,&CLSID_CUpdateReceipt>
{
private:
CComBSTR m_sDSN;
HENV m_henv;
HDBC m_hdbc;
HSTMT m_hstmt;
BOOL m_bConstructed;
GUID m_txuow;
BOOL m_bShouldEnlist;
BOOL m_bObjectPooled;
HRESULT Enlist();
public:
CUpdateReceipt()
{
m_henv = 0;
m_hdbc = 0;
m_hstmt= 0;
m_bConstructed = FALSE;
m_txuow = GUID_NULL;
m_bObjectPooled = FALSE;
}
BEGIN_COM_MAP(CUpdateReceipt)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IObjectConstruct)
COM_INTERFACE_ENTRY(IObjectControl)
COM_INTERFACE_ENTRY(IUpdateReceipt)
END_COM_MAP()
// IUpdateReceipt
public:
STDMETHOD (Update) (OUT long* plReceiptNo);
//IObjectConstruct
STDMETHODIMP Construct(IDispatch * pUnk);
// IObjectControl
STDMETHODIMP_(BOOL) CanBePooled()
{
return TRUE;
}
STDMETHODIMP Activate();
STDMETHODIMP_(void) Deactivate()
{
}
};
Poolable objects must meet certain requirements to enable a single object instance to be used by multiple clients.
Poolable objects should hold no client-specific state from client to client in order to maintain security, consistency, and isolation. You can manage any per-client state using IObjectControl, performing context-specific initialization with Activate and cleaning up any client state with Deactivate.
Poolable objects cannot be bound to a particular thread, otherwise performance could suffer. For this reason, poolable objects cannot be marked Apartment model; they must run in the Multi-Threaded Apartment or the Neutral Apartment. In addition, poolable objects should not use thread local storage (TLS), nor should they aggregate the Free-Threaded Marshaller (FTM). Note that currently the Microsoft Visual Basic development environment can only create Apartment model components. For this reason, Visual Basic components cannot be pooled.
Poolable objects must support aggregation. That is, they must support being created with a pUnkOuter. When COM+ activates a pooled object, it will create an aggregate to manage the lifetime of the pooled object and call methods on IObjectControl.
Poolable objects that participate in transactions must manually enlist managed resources. If your object holds a managed resource, such as a database connection, while it is pooled, there will be no way for the resource manager to automatically enlist in a transaction when the object is activated in a given context. Therefore, the object itself must handle the logic of detecting the transaction, turning off the resource manager’s auto-enlistment, and manually enlisting any resources that it holds. In addition, a transactional pooled object should reflect the current state of its resources in IObjectControl::CanBePooled.
Poolable objects should implement IObjectControl, though it is not strictly necessary to do so. Transactional components that are pooled, however, must implement IObjectControl. Pooled transactional components should monitor the state of the resources they hold and indicate when they can’t be reused; when CanBePooled returns False, it will doom a transaction.
Components can be pooled even if they are not specifically written with pooling in mind, so long as they are non-transactional and conform to the appropriate requirements above. It is not necessary to implement IObjectControl; a component that does not do so simply won’t participate in managing its lifetime. If CanBePooled is not implemented, the object will always be reused, until the pool reaches its maximum size.