荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Jobs (温少), 信区: Visual
标  题: Creating a TAPI Connection Using CTapiConnection
发信站: BBS 荔园晨风站 (Mon Nov 29 17:08:45 1999), 站内信件



Creating a TAPI Connection Using CTapiConnection
Nancy Winnick Cluts
Microsoft Developer Network Technology Group

November 1995
Revised: August 1996 (Changed function CheckAndReallocBuffer so that it zeroes
out the memory allocated for TAPI structures.)

Click to open or copy the files in the DIALIT sample application.

Abstract
This article demonstrates how the developer can add some simple telephony
capabilities to an application by using the Microsoft? Foundation Class Library
(MFC). I created a class, CTapiConnection, that supports the basic
functionality needed to use the Microsoft Windows? Telephony Application
Programming Interface (TAPI) to automatically dial the telephone for a voice
connection. It's a really good idea to read "TAP into the Future" (also
available in the MSDN Library) before reading this article to familiarize
yourself with the overall design of TAPI; otherwise, you may not understand
some of the steps. This article takes the reader through the steps necessary to
do the following:

Initialize TAPI


Obtain a phone line


Place a call


End a call
The DIALIT Sample
When I decided to write a sample that uses the Telephony Application
Programming Interface (TAPI), the first thing I did was look through the
Microsoft? Win32? Software Development Kit (SDK) for any and all information
about it. Happily, I found the Microsoft Telephony Programmer's Reference and
the Microsoft Telephony Service Provider Interface (TSPI) for Telephony. The
programmer's reference is intended to document the functionality that an
application using TAPI will need. The service provider documentation is for
developers who are going to write their own TAPI services (that is, vendors of
telephony equipment).

With the documentation in hand, I also wanted to find some sample source code
to look at. Tucked (or should I say buried?) in the Product Sample tree of the
MSDN Library, I found the TAPICOMM (MSDN Library, Sample Code, Product Samples)
sample, which demonstrates how to use TAPI for data transmission. It also
demonstrates how to use the Communications (COMM) API and how to use multiple
threads in your application. In short, it's terrific. Unfortunately, it's also
a really big sample. To get to the generic TAPI code is a navigational
challenge. As a result, I knew that it would be helpful to give you a simple
sample and a Microsoft Foundation Class Library (MFC) class to help you learn
about TAPI.

Thus, I decided to create a simple telephone dialer, and I came up with the
DIALIT sample. It was written using Visual C++? 2.2 and MFC version 3.1. It is
a Win32-based application that has been built and tested on Windows? 95. As you
can see from Figure 1, the user interface is minimal. (Yes, you are reading
this correctly. I really did write something non-GUI. Amazing!)



Figure 1. The DIALIT sample

The CTapiConnection Class
The CTapiConnection class was designed for the DIALIT sample to allow the
application developer a simple method of establishing a TAPI connection. The
class, as defined in the TAPIUTILS.H file, is shown below. The member functions
will be described in more detail later in this article.

class CTapiConnection
{

 protected:
    // This area contains the protected members of the CTapiConnection class.
    DWORD m_dwNumDevs;      // the number of line devices available
    DWORD m_dwDeviceID;     // the line device ID
    DWORD m_dwRequestedID;
    LONG m_lAsyncReply;

    // BOOLEANS to handle reentrancy.
    BOOL m_bShuttingDown;
    BOOL m_bStoppingCall;
    BOOL m_bInitializing;
    BOOL m_bReplyReceived;
    BOOL m_bTapiInUse;     // whether TAPI is in use or not
    BOOL m_bInitialized;   // whether TAPI has been initialized

 public:
    // This area contains the public members of the CTapiConnection class.
    HLINEAPP m_hLineApp;       // the usage handle of this application for TAPI
    HCALL m_hCall;             // handle to the call
    HLINE m_hLine;             // handle to the open line
    DWORD m_dwAPIVersion;      // the API version
    char m_szPhoneNumber[64];  // the phone number to call

 protected:
    // Here is where I put the protected (internal) functions.
BOOL ShutdownTAPI();
    BOOL HandleLineErr(long lLineErr);
    LPLINEDEVCAPS GetDeviceLine(DWORD *dwAPIVersion,
        LPLINEDEVCAPS lpLineDevCaps);
    LPLINEDEVCAPS MylineGetDevCaps(LPLINEDEVCAPS lpLineDevCaps,
        DWORD dwDeviceID, DWORD dwAPIVersion);
    LPVOID CheckAndReAllocBuffer(LPVOID lpBuffer, size_t sizeBufferMinimum);
    LPLINEADDRESSCAPS MylineGetAddressCaps (LPLINEADDRESSCAPS lpLineAddressCaps,
        DWORD dwDeviceID, DWORD dwAddressID, DWORD dwAPIVersion,
        DWORD dwExtVersion);
    BOOL MakeTheCall(LPLINEDEVCAPS lpLineDevCaps,LPCSTR lpszAddress);
    LPLINECALLPARAMS CreateCallParams (LPLINECALLPARAMS lpCallParams,
        LPCSTR lpszDisplayableAddress);
    long WaitForReply (long lRequestID);
    void HandleLineCallState(DWORD dwDevice, DWORD dwMessage,
        DWORD dwCallbackInstance,
        DWORD dwParam1, DWORD dwParam2, DWORD dwParam3);
 private:
   // This section is for private functions.

 public:
    // Public functions.
    CTapiConnection();
    ~CTapiConnection();
    BOOL Create(char *szPhoneNumber = NULL);
    BOOL DialCall(char *szPhoneNumber = NULL);
    BOOL HangupCall();
    static void CALLBACK lineCallbackFunc(
        DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
        DWORD dwParam1, DWORD dwParam2, DWORD dwParam3);

};

As you can glean from the class definition, implementing basic telephony
requires a lot of work. But you will notice that the member functions that I
have defined are simple:

Create


DialCall


HangupCall


lineCallbackFunc
Note   The line callback function is not called directly by the application
using the class; rather, it is called by the system for line notifications. I'
ll talk more about the line callback function later in this article.

Step One: Initializing TAPI
The first thing an application must do before it uses any telephony services is
to initialize TAPI. This means that the application must establish some way to
communicate between itself and TAPI. TAPI uses a callback function to
facilitate this communication. The application tells TAPI the address of this
callback function when the application makes a call to lineInitialize.

The lineInitialize function fills in two values passed to it: a usage handle
(shown in the following example as m_hLineApp) and the number of line devices
available to the application (shown in the following example as m_dwNumDevs).
If the call to lineInitialize is successful, a value of zero is returned. If an
error occurs, a negative value is returned. All TAPI functions return a value
of zero to signal success. To make it easier for me, I defined a constant
called SUCCESS to be zero and used that in my code.

The following example is the code I wrote to initialize TAPI. It takes a
pointer to a string containing the phone number to be dialed. By default, this
pointer is NULL. The member function that actually dials the phone number,
DialCall, also takes as a parameter the telephone number to dial. The user of
the class can specify the number in either place.

BOOL CTapiConnection::Create(char *szPhoneNumber)
{
    long lReturn;

    // If we're already initialized, then initialization succeeds.
    if (m_hLineApp)
        return TRUE;

    // If we're in the middle of initializing, then fail, we're not done.
    if (m_bInitializing)
        return FALSE;

    m_bInitializing = TRUE;

    // Initialize TAPI.
    do
    {
        lReturn = ::lineInitialize(&m_hLineApp,
            AfxGetInstanceHandle(),
            lineCallbackFunc,
            "DialIt",
            &m_dwNumDevs);

        if (m_dwNumDevs == 0)
        {
            AfxMessageBox("There are no telephony devices installed.");
            m_bInitializing = FALSE;
            return FALSE;
        }

        if (HandleLineErr(lReturn))
            continue;
        else
        {
            OutputDebugString("lineInitialize unhandled error\n");
            m_bInitializing = FALSE;
            return FALSE;
        }
    }
    while(lReturn != SUCCESS);

    // If the user furnished a phone number, copy it over.
    if (szPhoneNumber)
        strcpy(m_szPhoneNumber, szPhoneNumber);

    m_bInitializing = FALSE;
    return TRUE;
}

Step Two: Obtaining a Line
Now that TAPI has been initialized, the application needs to interact with the
user to know what type of call to make. The application does this by building a
dialog box or property sheet using the standard Win32 functions. There isn't a
handy common dialog for this. (Drats!) In the DIALIT sample, a simple dialog
box lets the user enter the desired phone number to dial in the edit box and
then click the Dial button to dial.

When we know the number to dial and that we should dial it, the application
needs to obtain a line handle. This is done by a call to the lineOpen function.
Before the application can make a call to lineOpen, though, it has to make sure
that the line can support the type of call that the application is trying to
make. In the case of the DIALIT sample, this is a line that is capable of voice
transmission.

Getting the Line Capabilities
The application calls the lineGetDevCaps function to determine the capabilities
of a given phone line. The function fills in a structure, LINEDEVCAPS,
containing this information. Sounds straightforward, doesn't it. Well, there's
a bit of a catch: the LINEDEVCAPS structure is defined by the telephony service
provider and, as such, is variable length. After the application makes the call
to lineGetDevCaps, it must check to see if the amount of space supplied for the
structure was sufficient for the size of the provider's structure. This is done
by comparing the dwNeededSize and dwTotalSize fields. If the needed size is
larger than the total size, the application needs to pass a larger buffer to
the function and try again. I created a function that calls lineGetDevCaps
until a sufficient sized buffer is returned for this purpose. This function
calls another function, CheckAndReAllocBuffer, that checks to see if the buffer
exists and is large enough; it also fills in the dwTotalSize field of the
LINEDEVCAPS structure. Filling in this field is imperative. If you don't set
this correctly, the call to lineGetDevCaps will most likely fail. This function
is called from the DialCall member function of the CTapiConnection class.

LPLINEDEVCAPS CTapiConnection::MylineGetDevCaps(
    LPLINEDEVCAPS lpLineDevCaps,
    DWORD dwDeviceID, DWORD dwAPIVersion)
{
    // Allocate enough space for the structure plus 1024.
    size_t sizeofLineDevCaps = sizeof(LINEDEVCAPS) + 1024;
    long lReturn;

    // Continue this loop until the structure is big enough.
    while(TRUE)
    {
        // Make sure the buffer exists, is valid, and is big enough.
        lpLineDevCaps =
            (LPLINEDEVCAPS) CheckAndReAllocBuffer(
                (LPVOID) lpLineDevCaps,   // pointer to existing buffer, if any
                sizeofLineDevCaps);       // minimum size the buffer should be

        if (lpLineDevCaps == NULL)
            return NULL;

        // Make the call to fill the structure.
        do
        {
            lReturn =
                ::lineGetDevCaps(m_hLineApp, dwDeviceID,
                    dwAPIVersion, 0, lpLineDevCaps);

            if (HandleLineErr(lReturn))
                continue;
            else
            {
                LocalFree(lpLineDevCaps);
                return NULL;
            }
        }
        while (lReturn != SUCCESS);

        // If the buffer was big enough, then succeed.
        if ((lpLineDevCaps -> dwNeededSize) <= (lpLineDevCaps -> dwTotalSize))
            return lpLineDevCaps;

        // Buffer wasn't big enough. Make it bigger and try again.
        sizeofLineDevCaps = lpLineDevCaps -> dwNeededSize;
    }
}

LPVOID CTapiConnection::CheckAndReAllocBuffer(LPVOID lpBuffer,
   size_t sizeBufferMinimum)
{
    size_t sizeBuffer;

    if (lpBuffer == NULL)   // allocate the buffer if necessary
    {
        sizeBuffer = sizeBufferMinimum;
        lpBuffer = (LPVOID) LocalAlloc (LPTR, sizeBuffer);

        if (lpBuffer == NULL)
        {
            OutputDebugString("LocalAlloc failed in CheckAndReAllocBuffer./n");
            return NULL;
        }
    }
    else   // if the structure already exists, make sure it is good
    {
        sizeBuffer = LocalSize((HLOCAL) lpBuffer);

        if (sizeBuffer == 0)   // bad pointer?
            return NULL;

        // Was the buffer big enough for the structure?
        if (sizeBuffer < sizeBufferMinimum)
        {
            LocalFree(lpBuffer);
            return CheckAndReAllocBuffer(NULL, sizeBufferMinimum);
        }

    }
    // Set the dwTotalSize field to the size of the buffer or the call will
    // fail.
memset(lpBuffer, 0, sizeBuffer);
   ((LPVARSTRING) lpBuffer ) -> dwTotalSize = (DWORD) sizeBuffer;
    return lpBuffer;
}

Another task that must be accomplished before actually obtaining the line is to
call the lineNegotiateAPIVersion function. This function indicates the version
of TAPI that the application was written to support. In the DIALIT sample, I
pass in constants that are defined at the top of the TAPIUTILS.CPP file to
indicate which versions of TAPI support my sample. This is done in the
GetDeviceLine member function, which obtains the first usable voice line
available to me.

// TAPI version that this sample is designed to use.
#define SAMPLE_TAPI_VERSION 0x00010004

// Early TAPI version.
#define EARLY_TAPI_VERSION 0x00010003

LPLINEDEVCAPS CTapiConnection::GetDeviceLine(DWORD *pdwAPIVersion,
   LPLINEDEVCAPS lpLineDevCaps)
{
    DWORD dwDeviceID;
    char szLineUnavail[] = "Line Unavailable";
    char szLineUnnamed[] = "Line Unnamed";
    char szLineNameEmpty[] = "Line Name is Empty";
    LPSTR lpszLineName;
    long lReturn;
    char buf[MAX_PATH];
    LINEEXTENSIONID lineExtID;
    BOOL bDone = FALSE;

    for (dwDeviceID = 0; (dwDeviceID < m_dwNumDevs) && !bDone; dwDeviceID ++)
    {

        lReturn = ::lineNegotiateAPIVersion(m_hLineApp, dwDeviceID,
            EARLY_TAPI_VERSION, SAMPLE_TAPI_VERSION,
            pdwAPIVersion, &lineExtID);

        if ((HandleLineErr(lReturn))&&(*pdwAPIVersion))
        {
            lpLineDevCaps = MylineGetDevCaps(lpLineDevCaps, dwDeviceID,
               *pdwAPIVersion);

            if ((lpLineDevCaps -> dwLineNameSize) &&
                (lpLineDevCaps -> dwLineNameOffset) &&
                (lpLineDevCaps -> dwStringFormat == STRINGFORMAT_ASCII))
            {
                // This is the name of the device.
                lpszLineName = ((char *) lpLineDevCaps) +
                    lpLineDevCaps -> dwLineNameOffset;
                sprintf(buf, "Name of device is: %s\n", lpszLineName);
                OutputDebugString(buf);

            }
            else  // DevCaps doesn't have a valid line name. Unnamed.
                lpszLineName = szLineUnnamed;
        }
        else   // Couldn't NegotiateAPIVersion. Line is unavail.
            lpszLineName = szLineUnavail;

        // If this line is usable and we don't have a default initial
        // line yet, make this the initial line.
        if ((lpszLineName != szLineUnavail) &&
            (lReturn == SUCCESS ))
        {
            m_dwDeviceID = dwDeviceID;
            bDone = TRUE;
        }
        else
            m_dwDeviceID = MAXDWORD;
    }
    return (lpLineDevCaps);
}

In your application, you may wish to get the line information and present it to
the user. For example, on my laptop computer I have two modems: one that I use
when I am running without using the docking station and one that I use when I
am docked. The way my sample is written now, the first modem selected is the
one that I use when I am docked. This means that I can't test the sample at
home. (Too bad. How sad.)

Calling lineOpen
We've chosen a line, and now we want to know whether this is the line we want
to use. In the DIALIT sample, we check to see if the line is usable, if it can
handle voice calls, and if it is capable of dialing out. This is done by
checking the values filled into the LINEDEVCAPS structure I passed in.

if (!(lpLineDevCaps->dwBearerModes & LINEBEARERMODE_VOICE ))
{
    AfxMessageBox("The selected line doesn't support VOICE capabilities");
    goto DeleteBuffers;
}
// Does this line have the capability to make calls?
if (!(lpLineDevCaps->dwLineFeatures & LINEFEATURE_MAKECALL))
{
    AfxMessageBox("The selected line doesn't support MAKECALL capabilities");
    goto DeleteBuffers;
}
// Does this line have the capability for interactive voice?
if (!(lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_INTERACTIVEVOICE))
{
    AfxMessageBox("The selected line doesn't support INTERACTIVE VOICE
      capabilities");
    goto DeleteBuffers;
}

An application uses the lineOpen function to place calls and to monitor
incoming calls. When opening a line for an outgoing call, the DIALIT sample
sets the privilege level to LINECALLPRIVILEGE_NONE to insulate it from incoming
calls and to allow outgoing calls. You can set other privileges, but these are
for call monitoring.

// Open the line for an outgoing call.
do
{
    lReturn = ::lineOpen(m_hLineApp, m_dwDeviceID, &m_hLine,
        m_dwAPIVersion, 0, 0, LINECALLPRIVILEGE_NONE, 0, 0);

    if((lReturn == LINEERR_ALLOCATED) ||(lReturn == LINEERR_RESOURCEUNAVAIL))
    {
        HangupCall();
        OutputDebugString("Line is already in use by a non-TAPI application"
                " or by another TAPI Service Provider.\n");
        goto DeleteBuffers;
    }

    if (HandleLineErr(lReturn))
        continue;
    else
    {
        OutputDebugString("Unable to Use Line\n");
        HangupCall();
        goto DeleteBuffers;
    }
}
while(lReturn != SUCCESS);

Step Three: Placing the Call
That's right. We haven't even placed the call yet. Aren't you glad I've written
a class?

An application uses the lineMakeCall function to place a call. This function
takes the following parameters:

A handle to the open line obtained from the lineOpen call.


A pointer to the handle for the call. This will be filled in by lineMakeCall.


The destination address (the area code and telephone number).


The country code (which can be set to zero to use the default value).


A pointer to line parameters. This allows the application to specify how the
call should be set up (that is, the data rate, dialing parameters, and so on).
If this is set to NULL, the default setup is used.
The lineMakeCall function returns a positive "request ID" (which I saved in the
m_dwRequestID member variable of my class) if the function will be completed
asynchronously, or a negative error number if an error has occurred. The
following function demonstrates how the LPCALLPARAMS structure can be filled in
to support a simple interactive voice call.

LPLINECALLPARAMS CTapiConnection::CreateCallParams (
    LPLINECALLPARAMS lpCallParams, LPCSTR lpszDisplayableAddress)
{
    size_t sizeDisplayableAddress;

    if (lpszDisplayableAddress == NULL)
        lpszDisplayableAddress = "";

    sizeDisplayableAddress = strlen(lpszDisplayableAddress) + 1;

    lpCallParams = (LPLINECALLPARAMS) CheckAndReAllocBuffer(
        (LPVOID) lpCallParams,
        sizeof(LINECALLPARAMS) + sizeDisplayableAddress);

    if (lpCallParams == NULL)
        return NULL;

// This is where we configure the line.
    lpCallParams -> dwBearerMode = LINEBEARERMODE_VOICE;
    lpCallParams -> dwMediaMode  = LINEMEDIAMODE_INTERACTIVEVOICE;

    // This specifies that we want to use only IDLE calls and
    // don't want to cut into a call that might not be IDLE (ie, in use).
    lpCallParams -> dwCallParamFlags = LINECALLPARAMFLAGS_IDLE;

    // If there are multiple addresses on line, use first anyway.
    // It will take a more complex application than a simple tty app
    // to use multiple addresses on a line anyway.
    lpCallParams -> dwAddressMode = LINEADDRESSMODE_ADDRESSID;

    // Address we are dialing.
    lpCallParams -> dwDisplayableAddressOffset = sizeof(LINECALLPARAMS);
    lpCallParams -> dwDisplayableAddressSize = sizeDisplayableAddress;
    strcpy((LPSTR)lpCallParams + sizeof(LINECALLPARAMS),
      lpszDisplayableAddress);

    return lpCallParams;
}

After the lineMakeCall function successfully sets up the call, the application
receives a LINE_REPLY message (the asynchronous reply to lineMakeCall). The
application gets this message through its callback function. This means that a
call at the local end has been established (that is, we have a dial tone). The
LINE_REPLY message also informs the application that the call handle returned
by lineMakeCall is valid. The following code shows the line callback function
that the DIALIT sample uses.

void CALLBACK CTapiConnection::lineCallbackFunc(
    DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
    DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{

    // Handle the line messages.
    switch(dwMsg)
    {
        case LINE_CALLSTATE:
            MyThis->HandleLineCallState(dwDevice, dwMsg, dwCallbackInstance,
                dwParam1, dwParam2, dwParam3);
            break;

        case LINE_CLOSE:
            // Line has been shut down.
            ASSERT(MyThis);
            MyThis->m_hLine = NULL;
            MyThis->m_hCall = NULL;
            MyThis->HangupCall();   // all handles invalidated by this time
            break;

        case LINE_REPLY:
            if ((long) dwParam2 != SUCCESS)
                OutputDebugString("LINE_REPLY error\n");
            else
                OutputDebugString("LINE_REPLY: successfully replied\n");
            break;

        case LINE_CREATE:
            ASSERT(MyThis);
            if (MyThis->m_dwNumDevs <= dwParam1)
                MyThis->m_dwNumDevs = dwParam1+1;
            break;

        default:
            OutputDebugString("lineCallbackFunc message ignored\n");
            break;
    }
    return;
}

Several other messages or notifications are sent to the application's callback
function. For instance, as the call is placed, the call passes through a number
of states, each of which results in a LINE_CALLSTATE message sent to the
callback function. After the message indicating the connected state is received,
 the application can begin sending data. I used a handler for the
LINE_CALLSTATE message that prints debug messages indicating the current call
status. For your application, you may wish to write these messages to a status
bar.

void CTapiConnection::HandleLineCallState(
    DWORD dwDevice, DWORD dwMessage, DWORD dwCallbackInstance,
    DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{

    // Error if this CALLSTATE doesn't apply to our call in progress.
    if ((HCALL) dwDevice != m_hCall)
    {
        OutputDebugString("LINE_CALLSTATE: Unknown device ID");
        return;
    }

    // dwParam1 is the specific CALLSTATE change that is occurring.
    switch (dwParam1)
    {
        case LINECALLSTATE_DIALTONE:
            OutputDebugString("Dial Tone\n");
            break;

        case LINECALLSTATE_DIALING:
            OutputDebugString("Dialing\n");
            break;

        case LINECALLSTATE_PROCEEDING:
            OutputDebugString("Proceeding\n");
            break;

        case LINECALLSTATE_RINGBACK:
            OutputDebugString("RingBack\n");
            break;

        case LINECALLSTATE_BUSY:
            OutputDebugString("Line busy, shutting down\n");
            HangupCall();
            break;

        case LINECALLSTATE_IDLE:
            OutputDebugString("Line idle\n");
            HangupCall();
            break;

        case LINECALLSTATE_SPECIALINFO:
            OutputDebugString("Special Info, probably couldn't dial number\n");
            HangupCall();
            break;

        case LINECALLSTATE_DISCONNECTED:
        {
            LPSTR pszReasonDisconnected;

            switch (dwParam2)
            {
                case LINEDISCONNECTMODE_NORMAL:
                    pszReasonDisconnected = "Remote Party Disconnected";
                    break;

                case LINEDISCONNECTMODE_UNKNOWN:
                    pszReasonDisconnected = "Disconnected: Unknown reason";
                    break;

                case LINEDISCONNECTMODE_REJECT:
                    pszReasonDisconnected = "Remote Party rejected call";
                    break;

                case LINEDISCONNECTMODE_PICKUP:
                    pszReasonDisconnected =
                        "Disconnected: Call was picked up on another phone";
                    break;

                case LINEDISCONNECTMODE_FORWARDED:
                    pszReasonDisconnected = "Disconnected: Forwarded";
                    break;

                case LINEDISCONNECTMODE_BUSY:
                    pszReasonDisconnected = "Disconnected: Busy";
                    break;

                case LINEDISCONNECTMODE_NOANSWER:
                    pszReasonDisconnected = "Disconnected: No Answer";
                    break;

                case LINEDISCONNECTMODE_BADADDRESS:
                    pszReasonDisconnected = "Disconnected: Bad Address";
                    break;

                case LINEDISCONNECTMODE_UNREACHABLE:
                    pszReasonDisconnected = "Disconnected: Unreachable";
                    break;

                case LINEDISCONNECTMODE_CONGESTION:
                    pszReasonDisconnected = "Disconnected: Congestion";
                    break;

                case LINEDISCONNECTMODE_INCOMPATIBLE:
                    pszReasonDisconnected = "Disconnected: Incompatible";
                    break;

                case LINEDISCONNECTMODE_UNAVAIL:
                    pszReasonDisconnected = "Disconnected: Unavailable";
                    break;

                case LINEDISCONNECTMODE_NODIALTONE:
                    pszReasonDisconnected = "Disconnected: No Dial Tone";
                    break;

                default:
                    pszReasonDisconnected =
                        "Disconnected: LINECALLSTATE; Bad Reason";
                    break;

            }

            OutputDebugString(pszReasonDisconnected);
            OutputDebugString("\n");
            HangupCall();
            break;
        }

        case LINECALLSTATE_CONNECTED:   // CONNECTED!!!
            OutputDebugString("Connected!\n");
            break;

        default:
            OutputDebugString("Unhandled LINECALLSTATE message\n");
            break;
    }
}

Step Four: Sending Data
If the DIALIT sample supported data transmission, now would be the time that it
would send the data. The user would specify, through some user interface widget,
 what file or data to send and then initiate the data transmission. The TAPI
functions continue to manage the opened line and the call in progress, but the
actual transmission is started and controlled by non-TAPI functions (that is,
COMM functions). This is the type of transmission that the TAPICOMM sample
demonstrates.

In our case, we are establishing a voice call. TAPI continues to monitor the
line and call, but if there is anything special we need to do, it is up to us.
When a call is connected, the service provider may display a dialog box asking
the user to lift the telephone receiver and click the Talk button.



Figure 2. A Call Status dialog box

In our sample, we use a function (borrowed from the TAPICOMM sample) that
allows us to resynchronize a TAPI line call by waiting for the LINE_REPLY
message. In other words, it waits until a LINE_REPLY is received or the line is
shut down. The function is called from the same thread that made the call to
lineInitialize.

Note   If you try to call this function from a different thread than the thread
that called the lineInitialize function, the PeekMessage function will not be
synchronized with the correct thread. Instead, it will be peeking at the
message pump for the wrong thread. You will remember from reading the "TAP into
the Future" article that TAPI is designed such that a hidden window is created
by TAPI on behalf of the application when the lineInitialize function is
called. All notifications and messages are sent to the callback function
specified in the lineInitialize function. Thus the thread that called
lineInitialize contains the message pump for the messages that will be sent to
the callback function.

If the call was dropped while in a wait state, this function can potentially be
re-entered. We handle this by dropping out of the function and assuming that
the call was canceled. This is the reason for the bReentered flag. This flag is
set to FALSE when the function is entered and TRUE when the function is exited.
If bReentered is ever TRUE during the function processing, then the function
was re-entered.

long CTapiConnection::WaitForReply (long lRequestID)
{
    static BOOL bReentered;
    bReentered = FALSE;

    if (lRequestID > SUCCESS)
    {
        MSG msg;
        DWORD dwTimeStarted;

        m_bReplyReceived = FALSE;
        m_dwRequestedID = (DWORD) lRequestID;

        // Initializing this just in case there is a bug
        // that sets m_bReplyReceived without setting the reply value.
        m_lAsyncReply = LINEERR_OPERATIONFAILED;

        dwTimeStarted = GetTickCount();

        while(!m_bReplyReceived)
        {
            if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }

            // This should only occur if the line is shut down while waiting.
            if ((m_hCall != NULL) &&(!m_bTapiInUse || bReentered))
            {
                bReentered = TRUE;
                return WAITERR_WAITABORTED;
            }

            // It's a really bad idea to timeout a wait for a LINE_REPLY.
            // If we are expecting a LINE_REPLY, we should wait till we get
            // it; it might take a long time to dial (for example).

            // If 5 seconds go by without a reply, it might be a good idea
            // to display a dialog box to tell the user that a
            // wait is in progress and to give the user the capability to
            // abort the wait.
        }

        bReentered = TRUE;
        return m_lAsyncReply;
    }

    bReentered = TRUE;
    return lRequestID;
}

Step Five: Ending the Call
When the user finishes the phone call, the application receives a
LINE_CALLSTATE message telling it that the state of a line device has changed.
At this point the application can disconnect the call. In the DIALIT sample,
the application disconnects the call when the user clicks the Hang Up button.
The sample ends the calls, closes the line, shuts down TAPI, and exits.

Before the application disconnects the call, it checks to see if a call is
already in progress. If not, the application calls the lineDrop function to
place the call in the IDLE state. The call handle must then be released for the
finished call. This is done by the lineDeallocateCall function. Finally,
lineClose is called to close the line. At this point, there will be no more
incoming or outgoing calls using that line handle.

BOOL CTapiConnection::HangupCall()
{
    LPLINECALLSTATUS pLineCallStatus = NULL;
    long lReturn;

    // Prevent HangupCall re-entrancy problems.
    if (m_bStoppingCall)
        return TRUE;

    // If TAPI is not being used right now, then the call is hung up.
    if (!m_bTapiInUse)
        return TRUE;

    m_bStoppingCall = TRUE;

    // If there is a call in progress, drop and deallocate it.
    if (m_hCall)
    {
        pLineCallStatus = (LPLINECALLSTATUS)malloc(sizeof(LINECALLSTATUS));

        if (!pLineCallStatus)
        {
            ShutdownTAPI();
            m_bStoppingCall = FALSE;
            return FALSE;
        }

        lReturn = ::lineGetCallStatus(m_hCall, pLineCallStatus);

        // Only drop the call when the line is not IDLE.
        if (!((pLineCallStatus -> dwCallState) & LINECALLSTATE_IDLE))
            ::lineDrop(m_hCall, NULL, 0);

        // The call is now idle. Deallocate it!
        do
        {
            lReturn = ::lineDeallocateCall(m_hCall);
            if (HandleLineErr(lReturn))
                continue;
            else
            {
                OutputDebugString("lineDeallocateCall unhandled error\n");
                break;
            }
        }
        while(lReturn != SUCCESS);
    }

    // If we have a line open, close it.
    if (m_hLine)
    {
        do
        {
            lReturn = ::lineClose(m_hLine);
            if (HandleLineErr(lReturn))
                continue;
            else
            {
                OutputDebugString("lineClose unhandled error\n");
                break;
            }
        }
        while(lReturn != SUCCESS);
    }

    // Clean up.
    m_hCall = NULL;
    m_hLine = NULL;
    m_bTapiInUse = FALSE;
    m_bStoppingCall = FALSE;   // allow HangupCall to be called again

    // Need to free buffer returned from lineGetCallStatus.
    if (pLineCallStatus)
        free(pLineCallStatus);

    return TRUE;
}

Next, the application calls lineShutdown to end the use of TAPI.

BOOL CTapiConnection::ShutdownTAPI()
{
    long lReturn;

    // If we aren't initialized, then Shutdown is unnecessary.
    if (m_hLineApp == NULL)
        return TRUE;

    // Prevent ShutdownTAPI re-entrancy problems.
    if (m_bShuttingDown)
        return TRUE;

    m_bShuttingDown = TRUE;

    HangupCall();

    lReturn = ::lineShutdown(m_hLineApp);
    if (!HandleLineErr(lReturn))
        OutputDebugString("lineShutdown unhandled error\n");

    m_bTapiInUse = FALSE;
    m_hLineApp = NULL;
    m_hCall = NULL;
    m_hLine = NULL;
    m_bShuttingDown = FALSE;
    return TRUE;
}

At this point the application is finished using TAPI and can continue to do
whatever else it was designed to do. In the DIALIT sample, the application
exits.

Summary
As mentioned at the beginning of this article, this class was written to
provide the most basic TAPI functionality of initialization, ability to obtain
a line, dial voice calls, drop a line, and shut down. It can easily be expanded
to support data modem capabilities or a snazzy user interface (you can
certainly do better than the one I did). The class could even be wrapped into
an OLE Control and used by a Visual Basic? application. So now that you have a
simple class that you can use to include TAPI functionality in your application,
 some sample code, the Win32 SDK, the documentation, and this article, you
really have no excuse to avoid TAPI any more. I've done much of the hard work
for you already, so please make the time spent worthwhile and use the class.

? 1999 Microsoft Corporation. All rights reserved. Terms of use.

--
☆ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: bbs@192.168.11.111]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店