Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Templating the TAPI information retrieval pattern

0.00/5 (No votes)
27 Dec 2004 1  
Methods to simplify repetitive patterns, using templates.

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.

  1. Allocate a block of memory equal to the size of base type (memory header type).
  2. Call the data retrieval function.
  3. Resize the block of memory to the size actually needed to hold all the data.
  4. Call the data retrieval function.
  5. Return a pointer to the base type (memory header type).
// General pattern using a loop

// Note: The use of realloc & free is for clarification here.

T* Get(/*arguments*/, LONG& lResult)
{
    lResult = 0L;
    DWORD dwTotalSize = sizeof(T);
    T* pData;
    while( pData = (T*)realloc(pData, dwTotalSize) )
    {
        pData->dwTotalSize = dwTotalSize;
        lResult = lineGetFunc(/*arguments*/, pData);
        if( lResult < 0L ) // Failed to get any data

            break;

        if( pData->dwNeededSize > pData->dwTotalSize )
        {
            dwTotalSize = pData->dwNeededSize;
            continue; // Try again

        }
        return pData; // Success

    }

    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:

  1. Retrieve the information, using the general pattern.
  2. Cleanup/Free temporary memory block, upon destruction of class.
// Note: Using realloc & free to allocate heap memory internaly

// to avoid any possibily of throwing a bad_alloc. Since the

// user is not responsible for cleanup.


// Get pattern 1

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 ) // Failed to get any data

                break;

            if( m_pData->dwNeededSize > m_pData->dwTotalSize )
            {
                dwTotalSize = m_pData->dwNeededSize;
                continue; // Try again

            }

            return m_pData; // Success

        }

        // May be a memory allocation failer.

        // It does not mater, the memory is no longer needed

        FreeData();
        return NULL;
    }
};

// Get pattern 2

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 ) // Failed to get any data

                break;

            if( pData->dwNeededSize > pData->dwTotalSize )
            {
                dwTotalSize = pData->dwNeededSize;
                continue;
            }

            return pData; // Success

        }

        // May be a memory allocation failer.

        // It does not matter, the memory is no longer needed

        FreeData();
        return NULL;
    }
};

Using the templates to define the required class types.

////////////////////////////////////////////////////////////

// CLineAddressCaps

typedef tapi_classLineGet5<LINEADDRESSCAPS,HLINEAPP,DWORD, 
        DWORD,DWORD,DWORD,::lineGetAddressCaps>
CLineAddressCaps;

/////////////////////////////////////////////////////////////////

// CLineAddressStatus

typedef tapi_classLineGet2<LINEADDRESSSTATUS,HLINE, 
        DWORD,::lineGetAddressStatus>
CLineAddressStatus;

/////////////////////////////////////////////////////////////////

// CLineAgentActivityList

typedef tapi_classLineGet2<LINEAGENTACTIVITYLIST,HLINE, 
        DWORD,::lineGetAgentActivityList>
CLineAgentActivityList;

/////////////////////////////////////////////////////////////////

// CLineAgentCaps

typedef tapi_classLineGet4<LINEAGENTCAPS,HLINEAPP,DWORD, 
        DWORD,DWORD,::lineGetAgentCaps>
ClineAgentCaps;

Example of using templated class

////////////////////////////////////////////////////////////////

// TAPI template data classes: These can be used

// to get TAPI line data that

// is only needed temporaraly.

//

// Example:

//

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

// Note: The simple infomation retreived with this function does not

// realy need to use the tempory data class, since it is not returning any

// of the extended information.

//

// A better function for retrieving the number of active calls:

//

   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.

// Note: Since the user is responsible for deleting, we will use new

// to allocate memory from the free store. Because that is what the

// user would resonably expect us to be doing.


// Get pattern 1

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 ) // Failed to get any data

            break;

        if( pData->dwNeededSize > pData->dwTotalSize )
        {
            delete pData;
            dwTotalSize = pData->dwNeededSize;
            continue; // Try again

        }

        return pData; // Success

    }

    // May be a memory allocation failer.

    // It does not matter, the memory is no longer needed

    delete pData;
    return NULL;
}

// Get pattern 2

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 ) // Failed to get any data

            break;

        if( pData->dwNeededSize > pData->dwTotalSize )
        {
            delete pData;
            dwTotalSize = pData->dwNeededSize;
            continue; // Try again

        }

        return pData; // Success

    }

    // May be a memory allocation failer.

    // It does not matter, the memory is no longer needed

    delete pData;
    return NULL;
}

Using the templates to define the required functions

/////////////////////////////////////////////////////////////////////////////

// tapi_GetAddressCaps

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

/////////////////////////////////////////////////////////////////////////////

// tapi_GetAddressStatus

const LINEADDRESSSTATUS* tapi_GetAddressStatus(
  HLINE hLine,
  DWORD dwAddressID,
  LONG& lResult)
{
    return tapi_lineGet2<LINEADDRESSSTATUS,HLINE,DWORD,::lineGetAddressStatus>
        (hLine, dwAddressID, lResult);
}

Using a templated function

/////////////////////////////////////////////////////////////////////////////

// TAPI template data funtion: These can be used to get TAPI line data that

// needs to be deleted by user.

//

// Example:

//

   #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;
       // ELSE IF( lResult >= 0L ) THEN HANDLE MEMORY ALLOCATION ERROR

       delete plas;
       return lResult;
   }
/////////////////////////////////////////////////////////////////////////////

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here