Introduction
Recently, I was asked to fix a program that used a simple TAPI control to make a connection via modem, for sending binary data. The problem was that when the control was asked to hang-up, it would crash the VB program on Win2000 and WinXP machines. There was no simple drop-in replacement control and I did not want to rewrite the VB program. Therefore, I decided to write a drop-in replacement, for the existing control.
While writing the TAPI control, I noticed a pattern that is repeated over and over. This pattern is used whenever you need to get variable information from TAPI. All the examples I could find, used the same pattern to retrieve the data. The only differences between each of these pieces of code: the function being called, the number of arguments, and the data type (where the information was stored).
My first reaction was to write some macros that would automatically generate the functions used to retrieve the variable data. This method worked fine, but, of course, you can not step through the function code of an automatically generated function using this method. However, this is still the method I would be using if I were writing the code in C.
My next reaction was to use templates to automatically generate simple classes that would retrieve the variable data, and cleanup after themselves when they went out of scope. The reason behind this is that the normal pattern consists of: allocating a block of memory, calling a function to determine needed memory block size, reallocating the memory block, recalling the function, and then deleting the memory block after using it. Well, instead of having to insert code for deleting the memory block in several places, I wanted it to delete the memory block automatically. Hence, the need for classes that automatically cleanup after themselves.
There are 23 different functions in TAPI that use the same pattern in order to retrieve variable data. The only difference between the functions: the number of arguments, arguments types, the function called, and the data storage type. They all return a negative error code value when they fail, most return zero on success, but a few may return zero or greater on success. Note that although there are 23 different functions, that does not mean that 23 different templates are required. The individual functions actually take one to six arguments, not including the storage type. Therefore, you only have to provide six templates, each of which use the same pattern.
Important: Remember this article is about using templates to simplify repetitive patterns and is not about TAPI.
The general pattern
As you can see below, the general pattern for retrieving information from TAPI is very simple.
- Allocate a block of memory equal to the size of base type (memory header type).
- Call the data retrieval function.
- Resize the block of memory to the size actually needed to hold all the data.
- Call the data retrieval function.
- Return a pointer to the base type (memory header type).
T* Get(, LONG& lResult)
{
lResult = 0L;
DWORD dwTotalSize = sizeof(T);
T* pData;
while( pData = (T*)realloc(pData, dwTotalSize) )
{
pData->dwTotalSize = dwTotalSize;
lResult = lineGetFunc(, pData);
if( lResult < 0L )
break;
if( pData->dwNeededSize > pData->dwTotalSize )
{
dwTotalSize = pData->dwNeededSize;
continue;
}
return pData;
}
if( pData )
free(pData);
return NULL;
}
Templating the general pattern with classes
Now that we have a general pattern, we can now write some class templates that meet our requirements:
- Retrieve the information, using the general pattern.
- Cleanup/Free temporary memory block, upon destruction of class.
template<class T, class A1, LONG (WINAPI *Func)(A1,T*)>
class tapi_classLineGet1
{
T* m_pData;
void FreeData() { if( m_pData ) free(m_pData); m_pData = NULL; }
public:
tapi_classLineGet1() : m_pData(NULL);
~tapi_classLineGet1() { FreeData(); }
const T* GetData() const { return m_pData; }
const T* Get(A1 Arg1,LONG& lResult)
{
lResult = 0L;
DWORD dwTotalSize = sizeof(T);
while( m_pData = (T*)realloc(m_pData, dwTotalSize) )
{
m_pData->dwTotalSize = dwTotalSize;
lResult = Func(Arg1, m_pData);
if( lResult < 0L )
break;
if( m_pData->dwNeededSize > m_pData->dwTotalSize )
{
dwTotalSize = m_pData->dwNeededSize;
continue;
}
return m_pData;
}
FreeData();
return NULL;
}
};
template<class T, class A1, class A2, LONG (WINAPI *Func)(A1,A2,T*)>
class tapi_classLineGet2
{
T* m_pData;
void FreeData() { if( m_pData ) free(m_pData); m_pData = NULL; }
public:
tapi_classLineGet2() : m_pData(NULL);
~tapi_classLineGet2() { FreeData(); }
const T* GetData() const { return m_pData; }
const T* Get(A1 Arg1,A2 Arg2, LONG& lResult)
{
lResult = 0L;
DWORD dwTotalSize = sizeof(T);
while( m_pData = (T*)realloc(m_pData, dwTotalSize) )
{
pData->dwTotalSize = dwTotalSize;
lResult = Func(Arg1, Arg2, pData);
if( lResult < 0L )
break;
if( pData->dwNeededSize > pData->dwTotalSize )
{
dwTotalSize = pData->dwNeededSize;
continue;
}
return pData;
}
FreeData();
return NULL;
}
};
Using the templates to define the required class types.
typedef tapi_classLineGet5<LINEADDRESSCAPS,HLINEAPP,DWORD,
DWORD,DWORD,DWORD,::lineGetAddressCaps>
CLineAddressCaps;
typedef tapi_classLineGet2<LINEADDRESSSTATUS,HLINE,
DWORD,::lineGetAddressStatus>
CLineAddressStatus;
typedef tapi_classLineGet2<LINEAGENTACTIVITYLIST,HLINE,
DWORD,::lineGetAgentActivityList>
CLineAgentActivityList;
typedef tapi_classLineGet4<LINEAGENTCAPS,HLINEAPP,DWORD,
DWORD,DWORD,::lineGetAgentCaps>
ClineAgentCaps;
Example of using templated class
typedef tapi_classLineGet2<LINEADDRESSSTATUS,
HLINE,DWORD,::lineGetAddressStatus>
CLineAddressStatus;
LONG GetNumberOfActiveCalls(HLINE hLine,
DWORD dwAddressID, DWORD &dwNumCalls)
{
LONG lResult = 0L;
CLineAddressStatus clas;
const LINEADDRESSSTATUS* plas = clas.Get(hLine,
dwAddressID, lResult);
if( plas )
dwNumCalls = plas->dwNumActiveCalls;
ELSE IF( lResult >= 0L ) THEN
HANDLE MEMORY ALLOCATION ERROR
return lResult;
}
LONG GetNumberOfActiveCalls(HLINE hLine,
DWORD dwAddressID, DWORD &dwNumCalls)
{
LINEADDRESSSTATUS las;
las.dwTotalSize = sizeof(LINEADDRESSSTATUS);
LONG lResult = ::lineGetAddressStatus(hLine, dwAddressID, &las);
if( !lResult )
dwNumCalls = las.dwNumActiveCalls;
return lResult;
}
Templating the general pattern with functions
Q. What if we do not want automatic cleanup of the data?
A. Use function templates, instead.
template<class T, class A1, LONG (WINAPI *Func)(A1,T*)>
const T* tapi_lineGet1(A1 Arg1,LONG& lResult)
{
lResult = 0L;
DWORD dwTotalSize = sizeof(T);
T* pData;
while( NULL != (pData = (T*)(new BYTE[dwTotalSize])) )
{
pData->dwTotalSize = dwTotalSize;
lResult = Func(Arg1,pData);
if( lResult < 0L )
break;
if( pData->dwNeededSize > pData->dwTotalSize )
{
delete pData;
dwTotalSize = pData->dwNeededSize;
continue;
}
return pData;
}
delete pData;
return NULL;
}
template<class T, class A1, class A2, LONG (WINAPI *Func)(A1,A2,T*)>
const T* tapi_lineGet2(A1 Arg1,A2 Arg2, LONG& lResult)
{
lResult = 0L;
DWORD dwTotalSize = sizeof(T);
T* pData;
while( NULL != (pData = (T*)(new BYTE[dwTotalSize])) )
{
pData->dwTotalSize = dwTotalSize;
lResult = Func(Arg1,Arg2,pData);
if( lResult < 0L )
break;
if( pData->dwNeededSize > pData->dwTotalSize )
{
delete pData;
dwTotalSize = pData->dwNeededSize;
continue;
}
return pData;
}
delete pData;
return NULL;
}
Using the templates to define the required functions
const LINEADDRESSCAPS* tapi_GetAddressCaps(
HLINEAPP hLineApp,
DWORD dwDeviceID,
DWORD dwAddressID,
DWORD dwAPIVersion,
DWORD dwExtVersion,
LONG& lResult)
{
return tapi_lineGet5<LINEADDRESSCAPS,HLINEAPP,DWORD,
DWORD,DWORD,DWORD,::lineGetAddressCaps>
(hLineApp, dwDeviceID, dwAddressID, dwAPIVersion, dwExtVersion, lResult);
}
const LINEADDRESSSTATUS* tapi_GetAddressStatus(
HLINE hLine,
DWORD dwAddressID,
LONG& lResult)
{
return tapi_lineGet2<LINEADDRESSSTATUS,HLINE,DWORD,::lineGetAddressStatus>
(hLine, dwAddressID, lResult);
}
Using a templated function
#define tapi_GetAddressStatus(hLine, dwAddressID, lResult)\
tapi_lineGet2<LINEADDRESSSTATUS,HLINE,DWORD,::lineGetAddressStatus>\
(hLine, dwAddressID, lResult)
LONG GetNumberOfActiveCalls(HLINE hLine,
DWORD dwAddressID, DWORD &dwNumCalls)
{
LONG lResult = 0L;
const LINEADDRESSSTATUS* plas =
tapi_GetAddressStatus(hLine, dwAddressID, lResult);
if( plas )
dwNumCalls = plas->dwNumActiveCalls;
delete plas;
return lResult;
}