Figure 2   TAPI 3.0 Constants

Media modes TAPIMEDIAMODE_AUDIO
. TAPIMEDIAMODE_VIDEO
. TAPIMEDIAMODE_DATAMODEM
. TAPIMEDIAMODE_G3FAX

Terminal classes CLSID_SpeakerphoneTerminal
. CLSID_MicrophoneTerminal
. CLSID_SpeakersTerminal
. CLSID_MediaStreamTerminal
. CLSID_FileTerminal
. CLSID_BridgeTerminal
. CLSID_DigitTerminal
. CLSID_DataTerminal

Address types LINEADDRESSTYPE_PHONENUMBER
. LINEADDRESSTYPE_SDP
. LINEADDRESSTYPE_EMAILNAME
. LINEADDRESSTYPE_DOMAINNAME
. LINEADDRESSTYPE_IPADDRESS.

Protocols TAPIPROTOCOL_PSTN
. TAPIPROTOCOL_H323
. TAPIPROTOCOL_Multicast


Figure 4   INCOMING Sample Code

incoming.cpp

#define UNICODE
#include <windows.h>
#include <tapi3.h>
#include "incoming.h"
#include "callnot.h"
#include "resource.h"

//////////////////////////////////////////////////////////
// INCOMING.EXE
//
// Sample application that handles incoming TAPI calls. This 
// application will register to recieve calls on 
// all addresses that support at least audioin and audioout.
//
// NOTE:  This application is limited to working with one call at
// a time, and will not work correctly if multiple calls
// are present at the same time.
//////////////////////////////////////////////////////////

HINSTANCE                     ghInst;
HWND                          ghDlg = NULL;
ITTAPI                      * gpTapi;
ITBasicCallControl          * gpCall;
CTAPIEventNotification      * gpTAPIEventNotification = NULL;
ULONG                         gulAdvise;
long                        * gplRegistrationInstances = NULL;
DWORD                         gdwNumRegistrations = 10;

WCHAR gszTapi30[] = L"TAPI 3.0 Incoming Call Sample";

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine,
                   int nCmdShow)
{
    ghInst = hInst;

    // need to coinit
    if (!SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
    {
        return 0;
    }

    // do tapi initialization
    if (S_OK != InitializeTapi())
    {
        return 0;
    }

    // everything is initialized, so
    // start the main dialog box
    DialogBox(ghInst, MAKEINTRESOURCE(IDD_MAINDLG), NULL, MainDialogProc);

    // clean up
    ShutdownTapi();
    
    CoUninitialize();

    return 1;
}

HRESULT
InitializeTapi()
{
    HRESULT         hr;

    // cocreate the TAPI object
    hr = CoCreateInstance(CLSID_TAPI, NULL, CLSCTX_INPROC_SERVER, IID_ITTAPI,
                          (LPVOID *)&gpTapi);

    if ( !SUCCEEDED(hr) )
    {
        DoMessage(L"CoCreateInstance on TAPI failed");
        return hr;
    }

    // call initialize.  this must be called before
    // any other tapi functions are called.
    hr = gpTapi->Initialize();

    if ( !SUCCEEDED(hr) )
    {
        DoMessage(L"TAPI failed to initialize");

        gpTapi->Release();
        gpTapi = NULL;
        
        return hr;
    }

    // Register the event interface
    hr = RegisterTapiEventInterface();

    if ( !SUCCEEDED(hr) )
    {
        ShutdownTapi();
        
        return hr;
    }
    
    // find all address objects that
    // we will use to listen for calls on
    hr = ListenOnAddresses();

    if ( !SUCCEEDED(hr) )
    {
        DoMessage(L"Could not find any addresses to listen on");

        ShutdownTapi();
        
        return hr;
    }

    return S_OK;

}

void
ShutdownTapi()
{
    // if there is still a call,
    // release it
    ReleaseTheCall();

    // release main object.
    if ( NULL != gpTapi )
    {
        IConnectionPointContainer           * pCPC;
        IConnectionPoint                    * pCP;
        DWORD                                 dwCount;

        // unregister all of our call notifications
        if ( NULL != gplRegistrationInstances )
        {
            for ( dwCount = 0; dwCount < gdwNumRegistrations; dwCount++ )
            {
                gpTapi->UnregisterNotifications(
                                                gplRegistrationInstances[dwCount]);
            }

            LocalFree( gplRegistrationInstances );

            gplRegistrationInstances = NULL;
        }

        // unadvise our connection point
        gpTapi->QueryInterface( IID_IConnectionPointContainer, (void**) &pCPC);

        pCPC->FindConnectionPoint(IID_ITTAPIEventNotification, &pCP);

        pCP->Unadvise( gulAdvise );

        pCPC->Release();
        pCP->Release();

        // shutdown tapi
        gpTapi->Shutdown();
        gpTapi->Release();
        gpTapi = NULL;
    }

}

BOOL
CALLBACK
MainDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            // set up dialog
            ghDlg = hDlg;
            
            DisableButton( IDC_ANSWER );
            DisableButton( IDC_DISCONNECT );

            SetStatusMessage( L"Waiting for a call..." );

            return 0;
        }

        case WM_COMMAND:
        {
            switch ( LOWORD(wParam) )
            {
                // answer request
                case IDCANCEL:
                {
                    // quit
                    EndDialog( hDlg, 0 );

                    return 1;
                }
                    
                case IDC_ANSWER:
                {
                    SetStatusMessage(L"Answering...");

                    // answer the call
                    if ( S_OK == AnswerTheCall() )
                    {
                        EnableButton( IDC_DISCONNECT );
                        DisableButton( IDC_ANSWER );
                    }
                    else
                    {
                        DisableButton( IDC_ANSWER );
                        DoMessage(L"Answer failed");
                        SetStatusMessage(L"Waiting for a call...");
                    }

                    return 1;
                }

                // disconnect request
                case IDC_DISCONNECT:
                {
                    SetStatusMessage(L"Disconnecting...");

                    // disconnect
                    if (S_OK != DisconnectTheCall())
                    {
                        DoMessage(L"Disconnect failed");
                    }

                    return 1;
                }

                default:

                    return 0;
            }

            // this is where we handle any TAPI
            // events
            case WM_PRIVATETAPIEVENT:
            {
                OnTapiEvent((TAPI_EVENT) wParam, (IDispatch *) lParam);
            }
        }
        
        default:

            return 0;
    }
}

HRESULT
RegisterTapiEventInterface()
{
    HRESULT                       hr = S_OK;
    IConnectionPointContainer   * pCPC;
    IConnectionPoint            * pCP;
    
    // create a notification object
    gpTAPIEventNotification = new CTAPIEventNotification;

    if ( NULL == gpTAPIEventNotification )
    {
        DoMessage(L"Failed to create event interface");

        return E_FAIL;
    }
    
    // get the connectionpointcontainer interface
    // off the tapi object
    hr = gpTapi->QueryInterface(IID_IConnectionPointContainer, (void **)&pCPC);

    if ( !SUCCEEDED(hr) )
    {
        return hr;
    }

    // get the correct connection point
    hr = pCPC->FindConnectionPoint(IID_ITTAPIEventNotification, &pCP);
    pCPC->Release();
        
    if ( !SUCCEEDED(hr) )
    {
        return hr;
    }

    hr = pCP->Advise(gpTAPIEventNotification, &gulAdvise);

    pCP->Release();

    
    return hr;

}

//////////////////////////////////////////////////////////////////////
// ListenOnAddresses
//
// This procedure will find all addresses that support audio
// and will call ListenOnThisAddress to start listening on it
//////////////////////////////////////////////////////////////////////
HRESULT
ListenOnAddresses()
{
    HRESULT             hr = S_OK;
    IEnumAddress      * pEnumAddress;
    ITAddress         * pAddress;
    ITMediaSupport    * pMediaSupport;
    VARIANT_BOOL        bSupport;
    DWORD               dwCount = 0;
    

    gplRegistrationInstances = (long *) LocalAlloc(
                                       LPTR, sizeof(long) * gdwNumRegistrations);

    if ( NULL == gplRegistrationInstances )
    {
        return E_FAIL;
    }
    
    // enumerate the addresses
    hr = gpTapi->EnumerateAddresses( &pEnumAddress );

    if (S_OK != hr)
    {
        return hr;
    }

    while ( TRUE )
    {
        // get the next address
        hr = pEnumAddress->Next( 1, &pAddress, NULL );

        if (S_OK != hr)
        {
            break;
        }

        pAddress->QueryInterface(IID_ITMediaSupport, (void **)&pMediaSupport);

        // does it support Audio?
        pMediaSupport->QueryMediaType(TAPIMEDIAMODE_AUDIO, &bSupport);

        if (bSupport)
        {
            // If it does then we'll listen.
            hr = ListenOnThisAddress(pAddress, gplRegistrationInstances, dwCount);
            
            if ( !SUCCEEDED(hr) )
            {
                DoMessage(L"Listen failed on an address");
            }
            else
            {
                dwCount++;
            }
        }

        pMediaSupport->Release();
        pAddress->Release();

        if (dwCount == gdwNumRegistrations)
        {
            ResizeRegistration();
        }
        
    }

    pEnumAddress->Release();

    return S_OK;
}

///////////////////////////////////////////////////////////////////
// ListenOnThisAddress
//
// the app must call RegisterCallNotifications
// for the address that it wants calls on
///////////////////////////////////////////////////////////////////
HRESULT
ListenOnThisAddress(ITAddress * pAddress, long * plRegistrations, 
                    DWORD dwRegistrationCount)
{
    HRESULT                     hr = S_OK;
    long                        lMediaTypes;
    ITMediaSupport            * pMediaSupport;
    


    // listen for all media types that the address
    // supports.
    hr = pAddress->QueryInterface(IID_ITMediaSupport, (void**)&pMediaSupport);

    hr = pMediaSupport->get_MediaTypes( &lMediaTypes );

    pMediaSupport->Release();
    
    hr = gpTapi->RegisterCallNotifications(pAddress,
                                           VARIANT_TRUE, // monitor
                                           VARIANT_TRUE, // owner
                                           lMediaTypes,  // media to listen for
                                           0,            // callback instance
                                                         // registration instance
                                           &(plRegistrations[dwRegistrationCount])
                                           );

    return hr;

}
                    
/////////////////////////////////////////////////////////
// GetVideoRenderTerminal
//
// Creates a Terminal for the VideoIn mediatype 
// This is a dynamic terminal type.
/////////////////////////////////////////////////////////
HRESULT
GetVideoRenderTerminal(ITTerminalSupport * pTerminalSupport, 
                       ITTerminal ** ppTerminal)
{
    HRESULT             hr = S_OK;
    BSTR                bstrTerminalClass;
    LPOLESTR            lpTerminalClass;


    // need to pass in the terminal class
    // as a BSTR
    StringFromIID(CLSID_VideoWindowTerm, &lpTerminalClass);

    bstrTerminalClass = SysAllocString ( lpTerminalClass );

    CoTaskMemFree( lpTerminalClass );

    // try to create the terminal
    hr = pTerminalSupport->CreateTerminal(bstrTerminalClass, TAPIMEDIAMODE_VIDEO,
                                          TD_RENDER, ppTerminal);

    SysFreeString( bstrTerminalClass );

    return hr;

}

/////////////////////////////////////////////////////////////////
// CreateTerminals
//
// Create audioin and audioout terminals. Videoin and videoout
// terminals are created only when the address supports them.
//
// This function assumes that ppTerminals has a size no less than
// four.
/////////////////////////////////////////////////////////////////
HRESULT
CreateTerminals(ITAddress * pAddress, ITTerminal ** ppTerminals,
                DWORD * pdwNumTerminals)
{
    DWORD                   dwCount = 0;
    HRESULT                 hr;
    ITTerminalSupport     * pTerminalSupport;
    ITMediaSupport        * pMediaSupport;
    VARIANT_BOOL            bSupport;
    TERMINAL_DIRECTION      td;

    // get the terminal support interface of the
    // address object
    hr = pAddress->QueryInterface(IID_ITTerminalSupport,
                                  (void **) &pTerminalSupport);

    if ( !SUCCEEDED(hr) )
    {
        return hr;
    }

    // get the default audio render terminal
    hr = pTerminalSupport->GetDefaultTerminal(TAPIMEDIAMODE_AUDIO, TD_RENDER,
                                              &(ppTerminals[dwCount]));

    if ( SUCCEEDED(hr) )
    {
        // does the terminal actually support TD_BOTH?
        (ppTerminals[dwCount])->get_Direction( &td );

        dwCount++;
    }
    
    // if not, get the audio capture terminal also
    if ( (SUCCEEDED(hr) && (TD_BOTH != td)) || !SUCCEEDED(hr) )
    {
        hr = pTerminalSupport->GetDefaultTerminal(TAPIMEDIAMODE_AUDIO,
                                            TD_CAPTURE, &(ppTerminals[dwCount]));

        if ( SUCCEEDED(hr) )
        {
            dwCount++;
        }
    }

    // Find out if the address supports video.
    pAddress->QueryInterface(IID_ITMediaSupport, (void **)&pMediaSupport);

    // does it support VideoIn?
    pMediaSupport->QueryMediaType(TAPIMEDIAMODE_VIDEO, &bSupport);

    pMediaSupport->Release();
    
    if (bSupport)
    {
        //
        // get the video render terminal
        //
        hr = GetVideoRenderTerminal(pTerminalSupport, &(ppTerminals[dwCount]));

        if ( SUCCEEDED(hr) )
        {
            dwCount++;
        }

        // get the Terminal for Video capture
        hr = pTerminalSupport->GetDefaultTerminal(TAPIMEDIAMODE_VIDEO, TD_CAPTURE,
                                                  &(ppTerminals[dwCount]));

        if ( SUCCEEDED(hr) )
        {
            // If we're using the Video capture terminal, also enable
            // the preview window
            ITPreviewConfig         * pPreviewConfig;
            
            hr = (ppTerminals[dwCount])->QueryInterface(IID_ITPreviewConfig,
                                                        (void**)&pPreviewConfig);

            if ( SUCCEEDED(hr) )
            {
                pPreviewConfig->put_EnablePreview(VARIANT_TRUE);
                pPreviewConfig->Release();
            }

            dwCount++;
        }
    }

    // clean up
    pTerminalSupport->Release();

    // return # of terminals
    *pdwNumTerminals = dwCount;

    return S_OK;
}

void
ReleaseTerminals(ITTerminal ** ppTerminals, DWORD dwNumTerminals)
{
    DWORD       dwCount;
    
    for (dwCount = 0; dwCount < dwNumTerminals; dwCount++)
    {
        if (ppTerminals[dwCount])
        {
            ppTerminals[dwCount]->Release();
        }
    }
}

HRESULT
AnswerTheCall()
{
    HRESULT                 hr;
    ITCallInfo *            pCallInfo;
    ITAddress *             pAddress;
    ITTerminal *            ppTerminals[MAXTERMINALS];
    DWORD                   dwNumTerminals;
    DWORD                   dwCount;

    if (NULL == gpCall)
    {
        return E_UNEXPECTED;
    }

    // get the address object of this call
    gpCall->QueryInterface( IID_ITCallInfo, (void**)&pCallInfo );
    pCallInfo->get_Address( &pAddress );
    pCallInfo->Release();

    // create the media terminals for this call
    hr = CreateTerminals(pAddress, ppTerminals, &dwNumTerminals);


    // release the address
    pAddress->Release();
    
    if (S_OK != hr)
    {
        ReleaseTheCall();

        return hr;
    }

    for (dwCount = 0; dwCount < dwNumTerminals; dwCount++)
    {

        // For each terminal, call SelectTerminal
        hr = gpCall->SelectTerminal(ppTerminals[dwCount]);
    }
    
    ReleaseTerminals(ppTerminals, dwNumTerminals);

    if (S_OK != hr)
    {
        ReleaseTheCall();

        return hr;
    }

    // answer the call
    hr = gpCall->Answer();

    return hr;
}

HRESULT
DisconnectTheCall()
{
    HRESULT         hr = S_OK;

    if (NULL != gpCall)
    {
        hr = gpCall->Disconnect( DC_NORMAL );

        ReleaseTheCall();
        
        return hr;
    }

    return S_FALSE;
}

void
ReleaseTheCall()
{
    if (NULL != gpCall)
    {
        gpCall->Release();
        gpCall = NULL;
    }
}

void
DoMessage(LPWSTR pszMessage)
{
    MessageBox(ghDlg, pszMessage, gszTapi30, MB_OK);
}

void
SetStatusMessage(LPWSTR pszMessage)
{
    SetDlgItemText(ghDlg, IDC_STATUS, pszMessage);
}

void
EnableButton(int ID)
{
    SendDlgItemMessage(ghDlg, ID, BM_SETSTYLE, BS_DEFPUSHBUTTON, 0);
    EnableWindow(GetDlgItem( ghDlg, ID ), TRUE);
    SetFocus(GetDlgItem( ghDlg, ID ));
}

void
DisableButton(int ID)
{
    SendDlgItemMessage(ghDlg, ID, BM_SETSTYLE, BS_PUSHBUTTON, 0);
    EnableWindow(GetDlgItem( ghDlg, ID ), FALSE);
}

HRESULT
ResizeRegistration()
{
    long * plTemp;

    plTemp = (long *)LocalAlloc(LPTR, sizeof(long) * gdwNumRegistrations * 2);

    if ( NULL == plTemp )
    {
        return E_FAIL;
    }

    CopyMemory(plTemp, gplRegistrationInstances, 
               sizeof(long) * gdwNumRegistrations);

    gdwNumRegistrations *= 2;

    LocalFree( gplRegistrationInstances );

    gplRegistrationInstances = plTemp;

    return S_OK;
}
callnot.cpp
//////////////////////////////////////////////////////////////////////
// callnot.cpp
//
// Implementation of the ITTAPIEventNotification interface.
//
// This is an outgoing interface that is defined by TAPI 3.0.  This
// is basically a callback function that TAPI 3.0 calls to inform
// the application of events related to calls (on a specific address)
//
// Please refer to COM documentation for information on outgoing
// interfaces.
// 
// An application must implement and register this interface in order
// to receive calls and events related to calls
//
//////////////////////////////////////////////////////////////////////


#include "windows.h"
#include "tapi3.h"
#include "incoming.h"
#include "callnot.h"
#include "resource.h"
#include "control.h"
#include "strmif.h"

extern ITBasicCallControl * gpCall;
extern HWND ghDlg;
extern BSTR gbstrVideo;

///////////////////////////////////////////////////////////////////
//
// CTAPIEventNotification::Event
//
// The only method in the ITCallEventNotification interface.  This gets
// called by TAPI 3.0 when there is a call event to report
//
// We don't do any processing here, just post a message to our
// main UI thread to handle this.  In general, an MTA application should
// do as little as possible on the thread that the callback
// is fired in
//
///////////////////////////////////////////////////////////////////
HRESULT
STDMETHODCALLTYPE
CTAPIEventNotification::Event(TAPI_EVENT TapiEvent, IDispatch * pEvent)
{
    // addref the event so it doesn't go away
    pEvent->AddRef();

    // post a message to ourself
    PostMessage(hDlg, WM_PRIVATETAPIEVENT, (WPARAM) TapiEvent, (LPARAM) pEvent);

    return S_OK;
}

///////////////////////////////////////////////////////////////////
// OnTapiEvent
//
// This is the real event handler.  This is called from our
// msg proc when the WM_PRIVATETAPIEVENT message is received
///////////////////////////////////////////////////////////////////
HRESULT
OnTapiEvent(TAPI_EVENT TapiEvent, IDispatch * pEvent)
{
    HRESULT hr;

    switch ( TapiEvent )
    {
        case TE_CALLNOTIFICATION:
        {
            HandleCallNotificationEvent( pEvent );
            
            break;
        }
        
        case TE_CALLSTATE:
        {
            HandleCallStateEvent( pEvent );

            break;
        }

        default:

            // not handling other events

            break;
            
    }

    // make sure to release the event
    // we addref'd in in CTAPIEventNotification::Event()
    pEvent->Release();
    
    return S_OK;
}

void HandleCallNotificationEvent( IDispatch * pEvent )
{
    ITCallNotificationEvent         * pNotify;
    CALL_NOTIFICATION_EVENT           cne;
    ITCallInfo                      * pCall;
    HRESULT                           hr;

    // CET_CALLNOTIFICATION means that the application is being notified
    // of a new call.
    //
    // Note that we don't answer the call at this point.  The application
    // should wait for a CS_OFFERING CallState message before answering
    // the call.
    
    // get the correct interface on the event
    hr = pEvent->QueryInterface(IID_ITCallNotificationEvent, (void **)&pNotify);

    if (S_OK != hr)
    {
        DoMessage( L"Incoming call, but failed to get the event interface");

        return;
    }

    // check to see if we own the call
    pNotify->get_Event( &cne );

    if ( CNE_OWNER != cne )
    {
        return;
    }

    // get the call
    pNotify->get_Call( &pCall );

    // save the BasicCallControl interface of this
    // call in the global
    pCall->QueryInterface( IID_ITBasicCallControl, (void**)&gpCall );

    // release our local reference
    pCall->Release();

    // release the event object
    pNotify->Release();

    // update UI
    SetStatusMessage(L"New Owner Call");

    return;
}

void MakeWindowsVisible(ITCallStateEvent * pCallStateEvent)
{
    HRESULT                   hr;
    ITCallInfo              * pCallInfo = NULL;
    IEnumTerminal           * pEnumTerm = NULL;
    ITTerminal              * pTerminal = NULL;
    IVideoWindow            * pVideoWindow = NULL;

    // Get the call this event is for