www.pudn.com > TAPI会议系统.rar > tapiline.cpp


// ---------------------------------------------------------------------------- 
// IP Office SDK (c) Avaya 2001. All rights reserved. 
// 
// PROJECT:  TapiSample 
// FILE:     tapiline.cpp 
// CREATED:  Geoff Froud, based on previous work by Carl Muller 
// 
// See tapisample.h for an explanation of this program. 
// 
// This is the implementation file for the tapi support classes. 
// See tapiline.h for the definition of this class 
// 
// ---------------------------------------------------------------------------- 
 
#include "stdafx.h" 
#include "tapisample.h" 
#include "tapisampleDlg.h" 
#include "tapidescribe.h" 
 
// ============================================================================ 
// Static functions 
// ============================================================================ 
 
// ---------------------------------------------------------------------------- 
// Get a string from a TAPI structure 
void getTapiString(CString& result, void *ptr, DWORD Size, DWORD Offset) 
{ 
	if (Size > 0) 
	{ 
		char *buffer = result.GetBufferSetLength(Size + 1); 
		memcpy(buffer, &((BYTE *) ptr)[Offset], Size); 
		buffer[Size] = 0; 
		result.ReleaseBuffer(); 
	} 
	else 
		result.Empty(); 
} 
 
// ---------------------------------------------------------------------------- 
// Get device capabilities information from TAPI 
// Note: The calling function must delete the info structure later on! 
HRESULT loopLineGetDevCaps(HLINEAPP hLineApp, DWORD DeviceID, 
	DWORD APIVersion, DWORD ExtVersion, LINEDEVCAPS*& pLineDevCaps) 
{ 
	size_t CurrentSize = 512; // Starting value - usually big enough 
	HRESULT hr; 
 
	for (;;) 
	{ 
		// Allocate some memory for the call 
		pLineDevCaps = (LINEDEVCAPS *) new BYTE[CurrentSize]; 
		ZeroMemory(&pLineDevCaps[0], CurrentSize); 
		pLineDevCaps->dwTotalSize = CurrentSize; 
 
		// Ask TAPI for some information 
		hr = ::lineGetDevCaps(hLineApp, DeviceID, APIVersion, ExtVersion, pLineDevCaps); 
 
		// Cope with variable length structures 
		if (hr == LINEERR_STRUCTURETOOSMALL) 
		{ 
			if (pLineDevCaps->dwNeededSize <= 0) 
				break; 
			CurrentSize = pLineDevCaps->dwNeededSize; 
			delete [] pLineDevCaps; 
			pLineDevCaps = NULL; 
		} 
		else 
			break; 
	} 
	return hr; 
} 
 
// ---------------------------------------------------------------------------- 
// Get call information from TAPI 
// Note: The calling function must delete the info structure later on! 
HRESULT loopLineGetCallInfo(HCALL hCall, LINECALLINFO*& pCallInfo) 
{ 
	size_t CurrentSize = 512; // Starting value - usually big enough 
	HRESULT hr; 
 
	for (;;) 
	{ 
		// Allocate some memory for the call 
		pCallInfo = (LINECALLINFO *) new BYTE[CurrentSize]; 
		ZeroMemory(&pCallInfo[0], CurrentSize); 
		pCallInfo->dwTotalSize = CurrentSize; 
 
		// Ask TAPI for some information 
		hr = ::lineGetCallInfo(hCall, pCallInfo); 
 
		// Cope with variable length structures 
		if (hr == LINEERR_STRUCTURETOOSMALL) 
		{ 
			if (pCallInfo->dwNeededSize <= 0) 
				break; 
			CurrentSize = pCallInfo->dwNeededSize; 
			delete [] pCallInfo; 
			pCallInfo = NULL; 
		} 
		else 
			break; 
	} 
	return hr; 
} 
 
 
// ---------------------------------------------------------------------------- 
// Get identification information from TAPI 
HRESULT loopLineGetID(DWORD& result, HLINE hLine, DWORD AddressID, 
	HCALL hCall, DWORD Select, LPCSTR pszDeviceClass) 
{ 
	VARSTRING* pVarString = NULL; 
	result = 0xffffffff; 
	size_t CurrentSize = 512; // Starting value - usually big enough 
	HRESULT hr; 
 
	for (;;) 
	{ 
		// Allocate some memory for the call 
		pVarString = (VARSTRING *) new BYTE[CurrentSize]; 
		ZeroMemory(&pVarString[0], CurrentSize); 
		pVarString->dwTotalSize = CurrentSize; 
 
		// Ask TAPI for some information 
		hr = ::lineGetID(hLine, AddressID, hCall, Select, pVarString, pszDeviceClass); 
 
		// Cope with variable length structures 
		if (hr == LINEERR_STRUCTURETOOSMALL) 
		{ 
			if (pVarString->dwNeededSize <= 0) 
				break; 
			CurrentSize = pVarString->dwNeededSize; 
			delete [] pVarString; 
			pVarString = NULL; 
		} 
		else 
			break; 
	} 
	if (hr == S_OK) 
		result = ((DWORD *) (((BYTE *) pVarString) + pVarString->dwStringOffset))[0]; 
	delete [] pVarString; 
	return hr; 
} 
 
// ---------------------------------------------------------------------------- 
// Get address capabilities information from TAPI 
HRESULT loopLineGetAddressCaps(HLINEAPP hLineApp, DWORD DeviceID, DWORD AddressID, 
	DWORD APIVersion, DWORD ExtVersion, LINEADDRESSCAPS*& pAddressCaps) 
{ 
	size_t CurrentSize = 512; // Starting value - usually big enough 
	HRESULT hr; 
 
	for (;;) 
	{ 
		// Allocate some memory for the call 
		pAddressCaps = (LINEADDRESSCAPS *) new BYTE[CurrentSize]; 
		ZeroMemory(&pAddressCaps[0], CurrentSize); 
		pAddressCaps->dwTotalSize = CurrentSize; 
 
		// Ask TAPI for some information 
		hr = ::lineGetAddressCaps(hLineApp, DeviceID, AddressID, APIVersion, 
			ExtVersion, pAddressCaps); 
 
		// Cope with variable length structures 
		if (hr == LINEERR_STRUCTURETOOSMALL) 
		{ 
			if (pAddressCaps->dwNeededSize <= 0) 
				break; 
			CurrentSize = pAddressCaps->dwNeededSize; 
			delete [] pAddressCaps; 
			pAddressCaps = NULL; 
		} 
		else 
			break; 
	} 
	return hr; 
} 
 
// Get address capabilities information from TAPI 
HRESULT loopLineGetAddressStatus(HLINE hLine, DWORD AddressID, 
	LINEADDRESSSTATUS*& pAddressStatus) 
{ 
	size_t CurrentSize = 512; // Starting value - usually big enough 
	HRESULT hr; 
 
	for (;;) 
	{ 
		// Allocate some memory for the call 
		pAddressStatus = (LINEADDRESSSTATUS *) new BYTE[CurrentSize]; 
		ZeroMemory(&pAddressStatus[0], CurrentSize); 
		pAddressStatus->dwTotalSize = CurrentSize; 
 
		// Ask TAPI for some information 
		hr = ::lineGetAddressStatus(hLine, AddressID, pAddressStatus); 
 
		// Cope with variable length structures 
		if (hr == LINEERR_STRUCTURETOOSMALL) 
		{ 
			if (pAddressStatus->dwNeededSize <= 0) 
				break; 
			CurrentSize = pAddressStatus->dwNeededSize; 
			delete [] pAddressStatus; 
			pAddressStatus = NULL; 
		} 
		else 
			break; 
	} 
	return hr; 
} 
 
// ---------------------------------------------------------------------------- 
// Get call status information from TAPI 
HRESULT loopLineGetCallStatus(HCALL hCall, LINECALLSTATUS*& pCallStatus) 
{ 
	size_t CurrentSize = 512; // Starting value - usually big enough 
	HRESULT hr; 
 
	for (;;) 
	{ 
		// Allocate some memory for the call 
		pCallStatus = (LINECALLSTATUS *) new BYTE[CurrentSize]; 
		ZeroMemory(&pCallStatus[0], CurrentSize); 
		pCallStatus->dwTotalSize = CurrentSize; 
 
		// Ask TAPI for some information 
		hr = ::lineGetCallStatus(hCall, pCallStatus); 
 
		// Cope with variable length structures 
		if (hr == LINEERR_STRUCTURETOOSMALL) 
		{ 
			if (pCallStatus->dwNeededSize <= 0) 
				break; 
			CurrentSize = pCallStatus->dwNeededSize; 
			delete [] pCallStatus; 
			pCallStatus = NULL; 
		} 
		else 
			break; 
	} 
	return hr; 
} 
 
 
// ---------------------------------------------------------------------------- 
// Line Event Handling 
static void CALLBACK TapiLineCallback( 
	DWORD   dwDevice, 
	DWORD   nMsg, 
	DWORD   dwInstance, 
	DWORD   dwParam1, 
	DWORD   dwParam2, 
	DWORD   dwParam3) 
{ 
	switch( nMsg ) 
	{ 
	case LINE_CREATE: 
		// We have been asked to create a line.  We can't do this, the number 
		// of lines is dictated by the IP Office Telephony Service Provider, so 
		// we'll ignore this request. 
		break; 
 
	case LINE_REQUEST: 
		// We have received an assisted telephony request.  This application 
		// doesn't handle assisted telephony requests, so we'll ignore it. 
		break; 
 
	default: 
		// We have received an event relevant to an existing line 
		TapiLine* pLine = (TapiLine*) dwInstance; 
		if (pLine) 
			pLine->OnEvent(dwDevice, nMsg, dwParam1, dwParam2, dwParam3); 
		break; 
	} 
} 
 
 
// ============================================================================ 
// TAPI line class 
// ============================================================================ 
 
// ---------------------------------------------------------------------------- 
// Create a TAPI line wrapper object 
TapiLine::TapiLine(TapiApplication &Parent) : 
	m_Parent(Parent) 
{ 
	m_LineID = 0; 
	m_hLine = 0; 
 
	m_hConnectedCall = 0; 
	m_hWaitingCall = 0; 
	m_hHeldCall = 0; 
	m_hPendingCall = 0; 
	m_hConferenceCall = 0; 
	m_hConsultationCall = 0; 
} 
 
// ---------------------------------------------------------------------------- 
// Destroy a TAPI line wrapper object 
TapiLine::~TapiLine() 
{ 
	if (m_hLine) 
		::lineClose(m_hLine); 
	m_hLine = 0; 
} 
 
// ---------------------------------------------------------------------------- 
// Ask TAPI to make a call - only works if we are not on a call. 
void TapiLine::MakeCall(LPCTSTR pszAddress) 
{ 
	if ((m_hConnectedCall == 0) && (m_hWaitingCall == 0)) // No currently active call 
	{ 
		// Calculate the size of the parameter structure, and allocate it. 
		size_t cbDestAddress = strlen(pszAddress) + 1; 
		size_t cbCallParams = sizeof(LINECALLPARAMS) + cbDestAddress; 
		LINECALLPARAMS* pCallParams = (LINECALLPARAMS*)(new BYTE[cbCallParams]); 
		// Setup the parameters 
		if (pCallParams) 
		{ 
			ZeroMemory(pCallParams, cbCallParams); 
			pCallParams->dwTotalSize = cbCallParams; 
			pCallParams->dwBearerMode = LINEBEARERMODE_VOICE; 
			pCallParams->dwMinRate = 0;   // Use device default 
			pCallParams->dwMaxRate = 0;   // Use device default 
			pCallParams->dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE; 
			pCallParams->dwCallParamFlags = 0; // No flags 
			pCallParams->dwAddressMode = LINEADDRESSMODE_ADDRESSID; 
			pCallParams->dwAddressID = 0; // Use the main (and only) line address 
			// Variable length strings 
			pCallParams->dwDisplayableAddressSize = cbDestAddress; 
			pCallParams->dwDisplayableAddressOffset = sizeof(LINECALLPARAMS); 
			char* pszDisplayableAddress = (char*)((BYTE*)pCallParams + sizeof(LINECALLPARAMS)); 
			strcpy(pszDisplayableAddress, pszAddress); 
		} 
		// Ask TAPI to make the call 
		HRESULT tr = ::lineMakeCall(m_hLine, &m_hWaitingCall, pszAddress, 0, pCallParams); 
		delete[] pCallParams; 
		if (tr > 0) 
		{ 
			// Store the request details so that we can match up the  
			// asynchronous reply from TAPI. 
			REQUEST_INFO ri = { "MakeCall", m_hWaitingCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "MakeCall"); 
	} 
	else 
		m_Parent.m_Dlg.AddText("Error(MakeCall: There is already an Active call)"); 
} 
 
// ---------------------------------------------------------------------------- 
// Drop the line - only works if a call is connected or an outgoing call is 
// alerting 
void TapiLine::DropCall() 
{ 
	if (m_hWaitingCall) // Outgoing call 
	{ 
		HRESULT tr = ::lineDrop(m_hWaitingCall, NULL, 0); 
		if (tr > 0) 
		{ 
			// Store the request details so that we can match up the  
			// asynchronous reply from TAPI. 
			REQUEST_INFO ri = { "DropCall", m_hWaitingCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "DropCall"); 
	} 
	else if (m_hConnectedCall) // Active call 
	{ 
		HRESULT tr = ::lineDrop(m_hConnectedCall, NULL, 0); 
		if (tr > 0) 
		{ 
			// Store the request details so that we can match up the  
			// asynchronous reply from TAPI. 
			REQUEST_INFO ri = { "DropCall", m_hConnectedCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "DropCall"); 
	} 
	else 
		m_Parent.m_Dlg.AddText("Error(DropCall: No Active Call)"); 
} 
 
 
// ---------------------------------------------------------------------------- 
// Hold the current call - only works if call is currently pending 
void TapiLine::AnswerCall() 
{ 
	if (m_hPendingCall) 
	{ 
		HRESULT tr = ::lineAnswer(m_hPendingCall, NULL, 0); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "AnswerCall", m_hPendingCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "AnswerCall"); 
	} 
	else 
		m_Parent.m_Dlg.AddText("Error(AnswerCall: No Pending Call)"); 
} 
 
// ---------------------------------------------------------------------------- 
// Hold the current call - only works if call is connected 
void TapiLine::HoldCall() 
{ 
	if (m_hConnectedCall) 
	{ 
		HRESULT tr = ::lineHold(m_hConnectedCall); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "HoldCall", m_hConnectedCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "HoldCall"); 
	} 
	else 
		m_Parent.m_Dlg.AddText("Error(HoldCall: No Active Call)"); 
} 
 
// ---------------------------------------------------------------------------- 
// Retrieve the call from hold - only works if call is currently proceeding 
void TapiLine::UnholdCall() 
{ 
	if (m_hHeldCall) 
	{ 
		HRESULT tr = ::lineUnhold(m_hHeldCall); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "UnholdCall", m_hHeldCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "UnholdCall"); 
	} 
	else 
		m_Parent.m_Dlg.AddText("Error(UnholdCall: No Held Call)"); 
} 
 
// ---------------------------------------------------------------------------- 
// Conference the current call with the call on hold - only works if call 
// is currently connected and there is a call on hold. 
void TapiLine::ConferenceCall() 
{ 
	if (m_hConnectedCall && m_hHeldCall) 
	{ 
		HRESULT tr = ::lineCompleteTransfer(m_hHeldCall, m_hConnectedCall, &m_hConferenceCall, 
			LINETRANSFERMODE_CONFERENCE); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "ConferenceCall", m_hConferenceCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "ConferenceCall"); 
		if (m_hConferenceCall == 0) 
			m_Parent.m_Dlg.AddText("Error(ConferenceCall: No Conference Call Handle returned)"); 
 
	} 
	else 
	{ 
		if (!m_hConnectedCall) 
			m_Parent.m_Dlg.AddText("Error(ConferenceCall: No Active Call)"); 
		if (!m_hHeldCall) 
			m_Parent.m_Dlg.AddText("Error(ConferenceCall: No Held Call)"); 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// Transfer the call on hold to the current call 
// - only works if call is currently connected 
void TapiLine::BlindTransferCall(LPCTSTR pszAddress) 
{ 
	if (m_hConnectedCall) 
	{ 
		HRESULT tr = ::lineBlindTransfer(m_hConnectedCall, pszAddress, 0); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "BlindTransferCall", m_hConnectedCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "BlindTransferCall"); 
	} 
	else 
		m_Parent.m_Dlg.AddText("Error(BlindTransferCall: No Active Call)"); 
} 
 
// ---------------------------------------------------------------------------- 
// If the current call is connected, then lineSetupTransfer will put the call 
// on hold and create a new consultation call.  If the current call is already 
// on hold, then lineSetupTransfer will just create a new consultation call. 
void TapiLine::SetupTransfer() 
{ 
	if (m_hConnectedCall) 
	{ 
		HRESULT tr = ::lineSetupTransfer(m_hConnectedCall, &m_hConsultationCall, 0); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "SetupTransfer", m_hConnectedCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "SetupTransfer"); 
	} 
	else if (m_hHeldCall)  
	{ 
		HRESULT tr = ::lineSetupTransfer(m_hHeldCall, &m_hConsultationCall, 0); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "SetupTransfer", m_hHeldCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "SetupTransfer"); 
	} 
	else 
		m_Parent.m_Dlg.AddText("Error(lineSetupTransfer: No Active or Held Call)"); 
} 
 
// ---------------------------------------------------------------------------- 
// Dial will dial the given number on the currently active call, where the call 
// has been created using lineSetupTransfer. 
void TapiLine::Dial(LPCTSTR pszAddress) 
{ 
	if (!m_hHeldCall) 
		m_Parent.m_Dlg.AddText("Error(lineSetupTransfer: No Held Call)"); 
	else if (!m_hConsultationCall) 
		m_Parent.m_Dlg.AddText("Error(lineSetupTransfer: No Consultation Call)"); 
	else 
	{ 
		HRESULT tr = ::lineDial(m_hConsultationCall, pszAddress, 0); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "Dial", m_hConnectedCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "Dial"); 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// CompleteTransfer will transfer the held call to the currently connected call 
void TapiLine::CompleteTransfer() 
{ 
	if (!m_hHeldCall) 
		m_Parent.m_Dlg.AddText("Error(lineCompleteTransfer: No Held Call)"); 
	else  
	if (!m_hConsultationCall) 
	{ 
		if(!m_hConnectedCall) 
		{ 
			m_Parent.m_Dlg.AddText("Error(lineSetupTransfer: No Consultation Call)"); 
			return; 
		} 
		else m_hConsultationCall = m_hConnectedCall; 
	} 
 
	HRESULT tr = ::lineCompleteTransfer(m_hHeldCall, m_hConsultationCall, NULL, LINETRANSFERMODE_TRANSFER); 
	if (tr > 0) 
	{ 
		REQUEST_INFO ri = { "CompleteTransfer", m_hConnectedCall }; 
		m_Requests.SetAt(tr, ri); 
	} 
	m_Parent.CheckError(tr, "CompleteTransfer"); 
} 
 
// ---------------------------------------------------------------------------- 
// SwapHold puts the currently active call on hold and retrieves the held call. 
void TapiLine::SwapHold() 
{ 
	if (!m_hHeldCall) 
		m_Parent.m_Dlg.AddText("Error(lineSwapHold: No Held Call)"); 
	else if (!m_hConnectedCall) 
		m_Parent.m_Dlg.AddText("Error(lineSwapHold: No Active Call)"); 
	else 
	{ 
		HRESULT tr = ::lineSwapHold(m_hConnectedCall, m_hHeldCall); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "SwapHold", m_hConnectedCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "SwapHold"); 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// Park will park a call. Note that only a directed park will succeed on  
// IP Office 
void TapiLine::Park(LPCTSTR pszAddress) 
{ 
	if (!m_hConnectedCall) 
		m_Parent.m_Dlg.AddText("Error(linePark: No Active Call)"); 
	else 
	{ 
		HRESULT tr = 0; 
		if (pszAddress) 
			tr = ::linePark(m_hConnectedCall, LINEPARKMODE_DIRECTED, pszAddress, NULL); 
		else 
		{ 
			DWORD size = 512; 
			VARSTRING* pAddress = (VARSTRING*) new char[size]; // This should be big enough 
			ZeroMemory(&pAddress[0], size); 
			pAddress->dwTotalSize = size; 
 
			tr = ::linePark(m_hConnectedCall, LINEPARKMODE_NONDIRECTED, NULL, pAddress); 
		} 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "Park", m_hConnectedCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "Park"); 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// Unpark retrieves a parked call from the given park address 
void TapiLine::Unpark(LPCTSTR pszAddress) 
{ 
	HRESULT tr = ::lineUnpark(m_hLine, 0, &m_hConnectedCall, pszAddress); 
	if (tr > 0) 
	{ 
		REQUEST_INFO ri = { "Unpark", m_hConnectedCall }; 
		m_Requests.SetAt(tr, ri); 
	} 
	m_Parent.CheckError(tr, "Unpark"); 
} 
 
// ---------------------------------------------------------------------------- 
// This function redirects an alerting call to the given extension 
void TapiLine::Redirect(LPCTSTR pszAddress) 
{ 
	if (!m_hPendingCall) 
		m_Parent.m_Dlg.AddText("Error(linePark: No Offering Call)"); 
	else 
	{ 
		HRESULT tr = ::lineRedirect(m_hPendingCall, pszAddress, 0); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "Redirect", m_hPendingCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "Redirect"); 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// AddToConference adds the connected call to a held conference 
void TapiLine::AddToConference() 
{ 
	if (m_hConnectedCall && m_hHeldCall)//m_hConferenceCall) 
	{ 
		HRESULT tr = ::lineAddToConference(m_hHeldCall/*m_hConferenceCall*/, m_hConnectedCall); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "AddToConference", m_hConferenceCall }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "AddToConference"); 
 
		m_hLastCallIntoConf = m_hConnectedCall; 
	} 
	else 
	{ 
		if (!m_hConnectedCall) 
			m_Parent.m_Dlg.AddText("Error(lineAddToConference: No Active Call)"); 
		if (!m_hConferenceCall) 
			m_Parent.m_Dlg.AddText("Error(lineAddToConference: No Conference Call)"); 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// RemoveFromConference removes the last call added to the conference 
void TapiLine::RemoveFromConference() 
{ 
	if (m_hLastCallIntoConf) 
	{ 
		HRESULT tr = ::lineRemoveFromConference(m_hLastCallIntoConf); 
		if (tr > 0) 
		{ 
			REQUEST_INFO ri = { "RemoveFromConference", m_hLastCallIntoConf }; 
			m_Requests.SetAt(tr, ri); 
		} 
		m_Parent.CheckError(tr, "RemoveFromConference"); 
	} 
	else 
	{ 
		if (!m_hLastCallIntoConf) 
			m_Parent.m_Dlg.AddText("Error(lineRemoveFromConference: No Active Call)"); 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// Retrieves the address status for the TAPI line (IP Office lines only have 
// one address) and displays the details. 
void TapiLine::AddressStatus() 
{ 
	LINEADDRESSSTATUS* pAddressStatus = NULL; 
	loopLineGetAddressStatus(m_hLine, 0, pAddressStatus); 
 
	if (pAddressStatus) 
	{ 
		CString txt; 
		txt.Format("lineAddressStatus: NumInUse<%d> NumActiveCalls<%d> NumOnHoldCalls<%d> NumOnHoldPendCalls<%d> NumRingsNoAnswer<%d>", 
			pAddressStatus->dwNumInUse, pAddressStatus->dwNumActiveCalls, 
			pAddressStatus->dwNumOnHoldCalls, pAddressStatus->dwNumOnHoldPendCalls, 
			pAddressStatus->dwNumRingsNoAnswer); 
		m_Parent.m_Dlg.AddText(txt); 
 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_FORWARD) 
			m_Parent.m_Dlg.AddText("lineAddressStatus: The address can be forwarded."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_MAKECALL) 
			m_Parent.m_Dlg.AddText("lineAddressStatus: An outgoing call can be placed on the address."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_PICKUP) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: A call can be picked up at the address."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_PICKUPDIRECT) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: The linePickup function can be used to pick up a call on a specific address."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_PICKUPGROUP) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: The linePickup function can be used to pick up a call in the group."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_PICKUPHELD) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: The linePickup function (with a null destination address) can be used to pick up a call that is held on the address.");  
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_PICKUPWAITING) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: The linePickup function (with a null destination address) can be used to pick up a call waiting call.");  
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_SETMEDIACONTROL) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: Media control can be set on this address."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_SETTERMINAL) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: The terminal modes for this address can be set."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_SETUPCONF) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: A conference call with a NULL initial call can be set up at this address."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_UNCOMPLETECALL) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: Call completion requests can be canceled at this address."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_UNPARK) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: Calls can be unparked using this address."); 
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_FORWARDDND) 
 			m_Parent.m_Dlg.AddText("lineAddressStatus: The lineForward function (with an empty destination address) can be used to turn on the Do Not Disturb feature on the address. LINEADDRFEATURE_FORWARD will also be set.");  
		if (pAddressStatus->dwAddressFeatures & LINEADDRFEATURE_FORWARDFWD) 
			m_Parent.m_Dlg.AddText("lineAddressStatus: The lineForward function can be used to forward calls on the address to other numbers."); 
	} 
	delete [] pAddressStatus; 
} 
 
// ---------------------------------------------------------------------------- 
// Retrieves call info for one of the call handles (see the order below) and 
// displays the details. 
void TapiLine::GetCallInfo() 
{ 
	HCALL hCall = NULL; 
	if (m_hConnectedCall) 
		hCall = m_hConnectedCall; 
	else if (m_hWaitingCall) 
		hCall = m_hWaitingCall; 
	else if (m_hHeldCall) 
		hCall = m_hHeldCall; 
	else if (m_hPendingCall) 
		hCall = m_hPendingCall; 
	else if (m_hConferenceCall) 
		hCall = m_hConferenceCall; 
	else if (m_hConsultationCall) 
		hCall = m_hConsultationCall; 
 
	if (hCall) 
	{ 
		OnCallInfo(hCall, 0); 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// Open a specified TAPI line 
HRESULT TapiLine::Open(DWORD LineID, DWORD CallPrivilege, DWORD MediaModes) 
{ 
	// Reset the line 
	ASSERT(m_hLine == 0); 
	m_hLine = 0; 
 
	// Ask TAPI for a new line 
	HLINE hNewLine; 
	HRESULT tr = ::lineOpen(m_Parent.m_hLineApp, LineID, 
		&hNewLine, m_Parent.m_ApiVersions[LineID], 
		0, (DWORD)this, CallPrivilege, MediaModes, 0); 
	m_Parent.CheckError(tr, "Open"); 
	if (S_OK == tr) 
	{ 
		// Use the results 
		m_hLine = hNewLine; 
		m_LineID = LineID; 
		ASSERT(m_hLine); 
 
		// Set all status messages 
		tr = ::lineSetStatusMessages(m_hLine, LINEDEVSTATE_ALL, LINEADDRESSSTATE_ALL); 
		m_Parent.CheckError(tr, "SetStatusMessages"); 
	} 
	return tr; 
} 
 
// ---------------------------------------------------------------------------- 
// Retrieve the status of one of the calls (see order below) and display the 
// details. 
void TapiLine::GetCallStatus() 
{ 
	LINECALLSTATUS* pCallStatus; 
	HCALL hCall = 0; 
	if (m_hConnectedCall) 
		hCall = m_hConnectedCall;  
	if (m_hWaitingCall) 
		hCall = m_hWaitingCall;  
	if (m_hHeldCall) 
		hCall = m_hHeldCall;  
	if (m_hPendingCall) 
		hCall = m_hPendingCall;  
	if (m_hConferenceCall) 
		hCall = m_hConferenceCall;  
 
	if (hCall) 
	{ 
		HRESULT tr = loopLineGetCallStatus(hCall, pCallStatus); 
 
		if (tr < 0) 
			m_Parent.CheckError(tr, "lineGetCallStatus"); 
		else 
		{ 
			CString state; 
			DescribeCallState(state, pCallStatus->dwCallState); 
			CString txt; 
			txt.Format("Call Status: %s", (LPCTSTR)state); 
			m_Parent.m_Dlg.AddText(txt); 
			delete [] pCallStatus; 
		} 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// Log On an extension 
void TapiLine::LogOn(LPCTSTR pszAddress) 
{ 
	TCHAR buffer[10]; 
	buffer[0] = 8; 
	UINT length = strlen(pszAddress); 
	for (UINT i=0; i < length; i++) 
	{ 
		buffer[i + 1] = pszAddress[i]; 
	} 
	buffer[length + 1] = 0; // NULL terminate 
 
	HRESULT tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,length + 2); 
	m_Parent.CheckError(tr, "LogOn"); 
} 
 
// ---------------------------------------------------------------------------- 
// Log Off an extension 
void TapiLine::LogOff() 
{ 
	TCHAR buffer[3]; 
	buffer[0] = 9; 
	buffer[1] = 47; 
	buffer[2] = 0; // NULL terminate 
 
	HRESULT tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,3); 
	m_Parent.CheckError(tr, "LogOff"); 
} 
 
// ---------------------------------------------------------------------------- 
// Display the IP Office TAPI Config Dialog 
void TapiLine::ConfigDialog() 
{ 
	HRESULT tr = ::lineConfigDialog(m_LineID, m_Parent.m_Dlg.m_hWnd, NULL); 
	m_Parent.CheckError(tr, "ConfigDialog"); 
} 
 
// ---------------------------------------------------------------------------- 
// Set the divert destination 
void TapiLine::DivertDestination(LPCTSTR pszAddress) 
{ 
	TCHAR buffer[10]; 
	buffer[0] = 9; 
	buffer[1] = 6; 
	UINT length = strlen(pszAddress); 
	for (UINT i=0; i < length; i++) 
	{ 
		buffer[i + 2] = pszAddress[i]; 
	} 
	buffer[length + 2] = 0; // NULL terminate 
 
	HRESULT tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,length + 3); 
	m_Parent.CheckError(tr, "DivertDestination"); 
} 
 
// ---------------------------------------------------------------------------- 
// Set the divert settings, i.e. forwarding or Do Not Disturb 
void TapiLine::SetDivertSettings(BOOL FwdAll, BOOL FwdBusy, BOOL FwdNoAnsw, BOOL DND) 
{ 
	// Call lineDevSpecific for each of the four divert settings 
	TCHAR buffer[3]; 
 
	// The first and last byte are the same for all four 
	buffer[0] = 9; 
	buffer[2] = 0; // NULL terminate 
 
	// Forward All 
	buffer[1] = (char)((FwdAll) ? 0 : 1); 
	HRESULT tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,3); 
	m_Parent.CheckError(tr, "SetDivertSettings"); 
 
	// Forward Busy 
	buffer[1] = (char)((FwdBusy) ? 2 : 3); 
	tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,3); 
	m_Parent.CheckError(tr, "SetDivertSettings"); 
 
	// Forward No Answer 
	buffer[1] = (char)((FwdNoAnsw) ? 4 : 5); 
	tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,3); 
	m_Parent.CheckError(tr, "SetDivertSettings"); 
 
	// Do Not Disturb 
	buffer[1] = (char)((DND) ? 7 : 8); 
	tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,3); 
	m_Parent.CheckError(tr, "SetDivertSettings"); 
} 
 
// ---------------------------------------------------------------------------- 
// Retrieve and display the Divert Settings 
void TapiLine::GetDivertSettings() 
{ 
	TCHAR buffer[sizeof(LINEDEVSTATUS) + 100]; 
	LPLINEDEVSTATUS lpLineDevStatus = reinterpret_cast(buffer); 
	lpLineDevStatus->dwTotalSize = sizeof(LINEDEVSTATUS) + 100; 
	HRESULT tr = ::lineGetLineDevStatus(m_hLine, lpLineDevStatus); 
	m_Parent.CheckError(tr, "GetDivertSettings"); 
 
	// Set the pointer to the start of the dev specific data 
	TCHAR* pDevSpecific = (TCHAR*)lpLineDevStatus + lpLineDevStatus->dwDevSpecificOffset; 
 
	CString txt; 
	CString phoneExt(pDevSpecific); 
	txt = CString("Divert Settings: Phone Ext(") + phoneExt + CString(")"); 
 
	// Move the pointer past the phone number 
	pDevSpecific += phoneExt.GetLength() + 1; 
	if (pDevSpecific[0]) // Forward On Busy 
		txt += CString(", Fwd On Busy(On)"); 
	else 
		txt += CString(", Fwd On Busy(Off)"); 
 
	if (pDevSpecific[1]) // Forward On No Answer 
		txt += CString(", Fwd On No Answer(On)"); 
	else 
		txt += CString(", Fwd On No Answer(Off)"); 
 
	if (pDevSpecific[2]) // Forward All 
		txt += CString(", Fwd All(On)"); 
	else 
		txt += CString(", Fwd All(Off)"); 
 
	if (pDevSpecific[4]) // Do Not Disturb 
		txt += CString(", DND(On)"); 
	else 
		txt += CString(", DND(Off)"); 
 
	pDevSpecific += 26; // Move past the forward settings and other reserved data 
 
	CString userExt(pDevSpecific); 
	txt += CString(", User Ext(") + userExt + CString(")"); 
	 
	// Move the pointer past the user number 
	pDevSpecific += userExt.GetLength() + 1; 
 
	CString locale(pDevSpecific); 
	txt += CString(", Locale(") + locale + CString(")"); 
 
	// Move the pointer past the locale 
	pDevSpecific += locale.GetLength() + 1; 
 
	CString divertDestination(pDevSpecific); 
	txt += CString(", Divert Dest(") + divertDestination + CString(")"); 
 
	m_Parent.m_Dlg.AddText(txt); 
} 
 
// ---------------------------------------------------------------------------- 
// Set the App Specific value for a call. See below for the order in which the 
// call is selected. 
void TapiLine::SetAppSpecific(DWORD num) 
{ 
	HCALL hCall = NULL; 
	if (m_hConnectedCall) 
		hCall = m_hConnectedCall; 
	else if (m_hWaitingCall) 
		hCall = m_hWaitingCall; 
	else if (m_hHeldCall) 
		hCall = m_hHeldCall; 
	else if (m_hPendingCall) 
		hCall = m_hPendingCall; 
	else if (m_hConferenceCall) 
		hCall = m_hConferenceCall; 
	else if (m_hConsultationCall) 
		hCall = m_hConsultationCall; 
 
	if (hCall) 
	{ 
		HRESULT tr = ::lineSetAppSpecific(hCall, num); 
		m_Parent.CheckError(tr, "GetDivertSettings"); 
	} 
	else 
	{ 
		m_Parent.m_Dlg.AddText("SetAppSpecific: No call"); 
	} 
} 
 
void TapiLine::SetMsgWaitLamp(DWORD num) 
{ 
	TCHAR buffer[50]; 
 
	buffer[0] = 9; 
	buffer[1] = 73; // DisplayMsg 
	strcpy(&(buffer[2]),";Mailbox Msgs="); 
	buffer[16] = num + '0'; 
	buffer[17] = 0; // NULL terminate 
 
	// Set Message Waiting Lamp 
	HRESULT tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,18); 
	m_Parent.CheckError(tr, "lineDevSpecific"); 
} 
 
void TapiLine::SetInGroup(LPCTSTR pszGroup) 
{ 
	TCHAR buffer[50]; 
	DWORD len = 3; 
 
	buffer[0] = 9; 
	buffer[1] = 76; // HuntGroupEnable 
//	if ((pszGroup[0] == '0') || (pszGroup[0] == 0)) 
		buffer[2] = 0; // NULL terminate 
//	else 
//	{ 
//		strcpy(&(buffer[2]),pszGroup); 
//		buffer[2 + strlen(pszGroup)] = 0; // NULL terminate 
//		len = 2 + _tcslen(pszGroup); 
//	} 
 
	// Set In Group status 
	HRESULT tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,len); 
	m_Parent.CheckError(tr, "lineDevSpecific"); 
} 
 
void TapiLine::SetOutGroup(LPCTSTR pszGroup) 
{ 
	TCHAR buffer[50]; 
	DWORD len = 3; 
 
	buffer[0] = 9; 
	buffer[1] = 77; // HuntGroupDisable 
//	if ((pszGroup[0] == '0') || (pszGroup[0] == 0)) 
		buffer[2] = 0; // NULL terminate 
//	else 
//	{ 
//		strcpy(&(buffer[2]),pszGroup); 
//		buffer[2 + strlen(pszGroup)] = 0; // NULL terminate 
//		len = 2 + _tcslen(pszGroup); 
//	} 
 
	// Set In Group status 
	HRESULT tr = ::lineDevSpecific(m_hLine,0,NULL,buffer,len); 
	m_Parent.CheckError(tr, "lineDevSpecific"); 
} 
 
void TapiLine::SetCallData(LPCTSTR pszData) 
{ 
	HCALL hCall = NULL; 
	if (m_hConnectedCall) 
		hCall = m_hConnectedCall; 
	else if (m_hWaitingCall) 
		hCall = m_hWaitingCall; 
	else if (m_hHeldCall) 
		hCall = m_hHeldCall; 
	else if (m_hPendingCall) 
		hCall = m_hPendingCall; 
	else if (m_hConferenceCall) 
		hCall = m_hConferenceCall; 
	else if (m_hConsultationCall) 
		hCall = m_hConsultationCall; 
 
	if (hCall) 
	{ 
		HRESULT tr = ::lineSetCallData(hCall,(void*)pszData,_tcslen(pszData)); 
		m_Parent.CheckError(tr, "SetCallData"); 
	} 
	else 
	{ 
		m_Parent.m_Dlg.AddText("SetCallData: No call"); 
	} 
	 
} 
 
// ---------------------------------------------------------------------------- 
// Handle messages from TAPI 
void TapiLine::OnEvent( 
	DWORD   Device, 
	DWORD   Msg, 
	DWORD   Param1, 
	DWORD   Param2, 
	DWORD   Param3) 
{ 
	switch (Msg) 
	{ 
		case LINE_REPLY: // Reply to an asynchronous TAPI function 
			OnReply(Param1, Param2); 
			break; 
 
		case LINE_CLOSE: // A line has closed 
			OnClose(); 
			break; 
 
		case LINE_ADDRESSSTATE: // The address state has changed 
			ASSERT(m_hLine == (HLINE) Device); 
			OnAddressState(Param1, Param2); 
			break; 
 
		case LINE_CALLINFO: // The call info has changed 
			OnCallInfo((HCALL) Device, Param1); 
			break; 
 
		case LINE_CALLSTATE: // The call state has changed 
			OnCallState((HCALL) Device, Param1, Param2, Param3); 
			GetCallStatus(); 
			break; 
 
		case LINE_LINEDEVSTATE: // The device state has changed 
			ASSERT(m_hLine == (HLINE) Device); 
			OnLineDevState(Param1, Param2, Param3); 
			break; 
 
		case LINE_APPNEWCALL: // There is a new incoming call 
			OnNewCall(Param1, (HCALL) Param2, Param3); 
			// The following demonstrates screen pop!!! 
			m_Parent.m_Dlg.ShowWindow(SW_SHOWNORMAL); 
			break; 
 
		case LINE_DEVSPECIFIC: // The device specific data has changed 
		case LINE_DEVSPECIFICFEATURE: // The device specific features have changed 
			TRACE("Device Specific Event\n"); 
			break; 
 
		case LINE_GATHERDIGITS: // A digit has been received 
			TRACE("Gather Digits Event\n"); 
			break; 
 
		case LINE_GENERATE: // A tone has been generated 
			TRACE("Generate Event\n"); 
			break; 
 
		case LINE_MONITORDIGITS: // A digit has been received 
			TRACE("Monitor Digits Event\n"); 
			break; 
 
		case LINE_MONITORMEDIA: 
			TRACE("Monitor Media Event\n"); 
			break; 
 
		case LINE_MONITORTONE: // A tone has been detected 
			TRACE("Monitor Tone Event\n"); 
			break; 
 
		case LINE_REQUEST: 
			TRACE("Assisted Telephony Request Event\n"); 
			break; 
 
		case PHONE_BUTTON: // Phone events are not supported 
		case PHONE_CLOSE: 
		case PHONE_DEVSPECIFIC: 
		case PHONE_REPLY: 
		case PHONE_STATE: 
		case PHONE_CREATE:  
		case PHONE_REMOVE: 
			TRACE("Phone Event\n"); 
			break; 
 
		case LINE_CREATE: // Not supported 
			TRACE("Line Create Event\n"); 
			break; 
 
		case LINE_AGENTSPECIFIC: // Not Supported 
		case LINE_AGENTSTATUS:  // Not supported 
			TRACE("Agent Event\n"); 
			break; 
 
		case LINE_PROXYREQUEST: // Not supported 
			TRACE("Proxy Request Event\n"); 
			break; 
 
		case LINE_REMOVE: // Not supported 
			TRACE("Line Remove Event\n"); 
			break; 
 
		default: 
			ASSERT(FALSE); 
			break; 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// Handle a LINE_REPLY message from TAPI 
void TapiLine::OnReply(LONG RequestID, HRESULT Result) 
{ 
	CString info; 
	REQUEST_INFO ri = { "Unknown", 0 }; 
	if (m_Requests.Lookup(RequestID, ri)) 
		m_Requests.RemoveKey(RequestID); 
 
	if (Result == 0) 
		info.Format("Reply(%s: OK)", ri.Name); 
	else 
	{ 
		// Describe the error message returned from TAPI 
		info.Format("Reply(%s: 0x%08lx = %s)", ri.Name, 
			Result, DescribeError(Result)); 
	} 
	m_Parent.m_Dlg.AddText(info); 
} 
 
// ---------------------------------------------------------------------------- 
// Handle a LINE_CLOSE message from TAPI 
void TapiLine::OnClose() 
{ 
	// This line (us) has been closed down 
	m_hLine = 0; 
	// Describe this event 
	CString info; 
	info.Format("Close()"); 
	m_Parent.m_Dlg.AddText(info); 
} 
 
 
// ---------------------------------------------------------------------------- 
// Handle a LINE_ADDRESSSTATE message from TAPI 
void TapiLine::OnAddressState(DWORD AddressID, DWORD AddressState) 
{ 
	CString state; 
	CString info; 
	info.Format("Address(%d, %s)", AddressID, 
		DescribeAddressStatus(state, AddressState)); 
	m_Parent.m_Dlg.AddText(info); 
 
	// Get further information about the address change 
	LINEADDRESSSTATUS AddressStatus = { sizeof(LINEADDRESSSTATUS) }; 
	HRESULT hr = ::lineGetAddressStatus(m_hLine, AddressID, &AddressStatus); 
	m_Parent.CheckError(hr, "GetAddressStatus"); 
	info.Format("[NumInUse=%d, NumActiveCalls=%d, NumOnHoldCalls=%d, " 
		"NumOnHoldPendCalls=%d]", 
		AddressStatus.dwNumInUse, AddressStatus.dwNumActiveCalls, 
		AddressStatus.dwNumOnHoldCalls, AddressStatus.dwNumOnHoldPendCalls); 
	m_Parent.m_Dlg.AddText(info); 
} 
 
 
// ---------------------------------------------------------------------------- 
// Handle a LINE_CALLINFO message from TAPI 
void TapiLine::OnCallInfo(HCALL hCall, DWORD CallInfoState) 
{ 
	CString state, which, info; 
	// Which call is this information about? 
	which.Format("%d/", hCall); 
	if (hCall == m_hConnectedCall) which += "Active"; 
	else if (hCall == m_hWaitingCall) which += "Waiting"; 
	else if (hCall == m_hHeldCall) which += "Held"; 
	else if (hCall == m_hPendingCall) which += "Pending"; 
	else if (hCall == m_hConferenceCall) which += "Conference"; 
	else which += "Unknown"; 
	// Put the information into the dialog box 
	info.Format("CallInfo(%s: %s)", LPCTSTR(which), DescribeCallInfo(state, CallInfoState)); 
	m_Parent.m_Dlg.AddText(info); 
 
	// Get further information about the call info 
	CString s1, s2, Trunk, CallerID, CalledID, ConnectedID, RedirectingID, RedirectionID, CallData; 
	LINECALLINFO* pCallInfo = NULL; 
	/*HRESULT hr = */loopLineGetCallInfo(hCall, pCallInfo); 
	if (pCallInfo->dwTrunk == 0xffffffff) 
		Trunk = "Unknown"; 
	else 
		Trunk.Format("%ld", pCallInfo->dwTrunk); 
	getTapiString(CallerID, pCallInfo, pCallInfo->dwCallerIDSize, pCallInfo->dwCallerIDOffset); 
	getTapiString(CalledID, pCallInfo, pCallInfo->dwCalledIDSize, pCallInfo->dwCalledIDOffset); 
	getTapiString(ConnectedID, pCallInfo, pCallInfo->dwConnectedIDSize, pCallInfo->dwConnectedIDOffset); 
	getTapiString(RedirectingID, pCallInfo, pCallInfo->dwRedirectingIDSize, pCallInfo->dwRedirectingIDOffset); 
	getTapiString(RedirectionID, pCallInfo, pCallInfo->dwRedirectionIDSize, pCallInfo->dwRedirectionIDOffset); 
	getTapiString(CallData, pCallInfo, pCallInfo->dwCallDataSize, pCallInfo->dwCallDataOffset); 
	info.Format("[AddressID=%d, Origin=%s, Reason=%s, Trunk=%s, " 
		"CallerID=%s, CalledID=%s, ConnectedID=%s, RedirectingID=%s, RedirectionID=%s, CallData=%s]", 
		pCallInfo->dwAddressID, 
		DescribeCallOrigin(s1, pCallInfo->dwOrigin), 
		DescribeCallReason(s2, pCallInfo->dwReason), 
		LPCTSTR(Trunk), LPCTSTR(CallerID), LPCTSTR(CalledID), LPCTSTR(ConnectedID), 
		LPCTSTR(RedirectingID), LPCTSTR(RedirectionID), 
		LPCTSTR(CallData)); 
	m_Parent.m_Dlg.AddText(info); 
	delete [] pCallInfo; 
} 
 
// ---------------------------------------------------------------------------- 
// Handle a LINE_CALLSTATE message from TAPI 
void TapiLine::OnCallState(HCALL hCall, DWORD CallState, DWORD CallStateDetail, DWORD CallPrivilege) 
{ 
	CString info, which, s1, s2; 
 
	// Which call is this information about? 
	which.Format("%d/", hCall); 
	if (hCall == m_hConnectedCall) which += "Active"; 
	else if (hCall == m_hWaitingCall) which += "Waiting"; 
	else if (hCall == m_hHeldCall) which += "Held"; 
	else if (hCall == m_hPendingCall) which += "Pending"; 
	else if (hCall == m_hConferenceCall) which += "Conference"; 
	else if (hCall == m_hConsultationCall) which += "Consultation"; 
	else which += "Unknown"; 
	// Describe the event to the dialog box 
	info.Format("CallState(%s: %s, %s, %s)", 
		LPCTSTR(which), 
		DescribeCallState(s1, CallState), 
		DescribeCallStateDetail(CallState, CallStateDetail), 
		DescribePrivilege(s2, CallPrivilege)); 
	m_Parent.m_Dlg.AddText(info); 
 
	// Handle releasing resources when they go idle 
	// This is necessary to allow another call to be made! 
	if (CallState & LINECALLSTATE_DISCONNECTED) 
		// DropCall(); // This seems to hang up the wrong call!!! 
		; 
	else if (CallState & LINECALLSTATE_IDLE) 
	{ 
		HRESULT Result = ::lineDeallocateCall(hCall); 
 
		// Describe the error message returned from TAPI 
		if (Result) 
		{ 
			info.Format("LineDeallocate(0x%08lx = %s)", 
				Result, DescribeError(Result)); 
			m_Parent.m_Dlg.AddText(info); 
		} 
		else 
		{ 
			if (hCall == m_hConnectedCall) m_hConnectedCall = 0; 
			else if (hCall == m_hWaitingCall) m_hWaitingCall = 0; 
			else if (hCall == m_hHeldCall) m_hHeldCall = 0; 
 
			else if (hCall == m_hPendingCall) m_hPendingCall = 0; 
			else if (hCall == m_hConferenceCall) m_hConferenceCall = 0; 
		} 
	} 
	// Has a call been answered / retrieved? 
	else if (CallState & LINECALLSTATE_CONNECTED) 
	{ 
		if (hCall == m_hWaitingCall) m_hWaitingCall = 0; 
		if (hCall == m_hPendingCall) m_hPendingCall = 0; 
		if (hCall == m_hHeldCall) m_hHeldCall = 0; 
		if (hCall == m_hConferenceCall) m_hConferenceCall = 0; 
		m_hConnectedCall = hCall; 
	} 
	// Has a call been put on hold? 
	else if (CallState & (LINECALLSTATE_ONHOLDPENDTRANSFER | LINECALLSTATE_ONHOLD 
		| LINECALLSTATE_ONHOLDPENDCONF)) 
	{ 
		if (hCall == m_hConnectedCall) m_hConnectedCall = m_hHeldCall; 
		m_hHeldCall = hCall; 
	} 
	// Is a call proceeding? 
	else if (CallState & (LINECALLSTATE_DIALING | LINECALLSTATE_PROCEEDING)) 
	{ 
		m_hWaitingCall = hCall; 
	} 
	m_Parent.m_Dlg.CheckButtons(); 
} 
 
 
// ---------------------------------------------------------------------------- 
// Handle a LINE_APPNEWCALL message from TAPI 
void TapiLine::OnNewCall(DWORD AddressID, HCALL hCall, DWORD CallPrivilege) 
{ 
	// We can answer this if we want 
	m_hPendingCall = hCall; 
	m_Parent.m_Dlg.CheckButtons(); 
 
	// Mention the new call 
	CString info, s1; 
	info.Format("NewCall(%d, %s)", AddressID, DescribePrivilege(s1, CallPrivilege)); 
	m_Parent.m_Dlg.AddText(info); 
 
	// Get further information about the address change 
	LINEADDRESSSTATUS AddressStatus = { sizeof(LINEADDRESSSTATUS) }; 
	HRESULT hr = ::lineGetAddressStatus(m_hLine, AddressID, &AddressStatus); 
	m_Parent.CheckError(hr, "GetAddressStatus"); 
	info.Format("[NumInUse=%d, NumActiveCalls=%d, NumOnHoldCalls=%d, " 
		"NumOnHoldPendCalls=%d]", 
		AddressStatus.dwNumInUse, AddressStatus.dwNumActiveCalls, 
		AddressStatus.dwNumOnHoldCalls, AddressStatus.dwNumOnHoldPendCalls); 
	m_Parent.m_Dlg.AddText(info); 
 
} 
 
// ---------------------------------------------------------------------------- 
// Handle a LINE_LINEDEVSTATE message from TAPI 
void TapiLine::OnLineDevState(DWORD DeviceState, DWORD DeviceStateDetail1, DWORD DeviceStateDetail2) 
{ 
	CString state; 
	CString info; 
	info.Format("DeviceState(%s, %d, %d)", 
		DescribeDeviceStatus(state, DeviceState), 
		DeviceStateDetail1, DeviceStateDetail2); 
	m_Parent.m_Dlg.AddText(info); 
} 
 
 
// ============================================================================ 
// TAPI application class 
// ============================================================================ 
 
// ---------------------------------------------------------------------------- 
// Create a TAPI application interface class 
TapiApplication::TapiApplication(CTapisampleDlg& Dlg) : 
	m_Dlg(Dlg) 
{ 
	m_extension = 0; 
	m_hLineApp = 0; 
	m_NumDevs = 0; 
	m_ApiVersions = 0; 
	m_CurrentLine = -1; 
} 
 
// ---------------------------------------------------------------------------- 
// Destroy a TAPI application interface class 
TapiApplication::~TapiApplication() 
{ 
	if (m_ApiVersions) 
		delete [] m_ApiVersions; 
} 
 
// ---------------------------------------------------------------------------- 
// Shutdown our channel to TAPI 
void TapiApplication::ShutdownTAPI() 
{ 
	if (m_pLines) 
	{ 
		DWORD LineID; 
		for (LineID = 0; LineID < m_NumDevs; LineID++) 
		{ 
			delete m_pLines[LineID]; 
		} 
		delete [] m_pLines; 
		m_pLines = NULL; 
	} 
	if (m_hLineApp ) 
	{ 
		::lineShutdown(m_hLineApp); 
		m_hLineApp = 0; 
	} 
} 
 
// ---------------------------------------------------------------------------- 
// Initialise TAPI 
void TapiApplication::InitialiseTAPI() 
{ 
	const DWORD dwLoVersion = 0x00020000;    // TAPI v2.0 
	const DWORD dwHiVersion = TAPI_CURRENT_VERSION; 
	HLINEAPP    hLineApp; 
	DWORD   dwAPIVersion = dwHiVersion; 
	LINEINITIALIZEEXPARAMS  params = { sizeof(LINEINITIALIZEEXPARAMS) }; 
	params.dwOptions = LINEINITIALIZEEXOPTION_USEHIDDENWINDOW; 
	HINSTANCE hInst = ::AfxGetInstanceHandle(); 
	LPCTSTR pszAppName = ::AfxGetAppName(); 
	HRESULT tr = ::lineInitializeEx(	&hLineApp, 
										hInst, 
										TapiLineCallback, 
										pszAppName, 
										&m_NumDevs, 
										&dwAPIVersion, 
										¶ms			); 
	if (S_OK == tr) 
	{ 
		// Save the handle 
		m_hLineApp = hLineApp; 
		// Negotiate the API versions 
		// (Necessary to get proper notifications) 
		TRACE("Number of devices: %d\n", m_NumDevs); 
		if (m_NumDevs > 0) 
		{ 
			m_ApiVersions = new DWORD[m_NumDevs]; 
			if (m_ApiVersions) 
			{ 
				LINEEXTENSIONID dummy; 
				for (DWORD LineID = 0; LineID < m_NumDevs; LineID++) 
				{ 
					if (S_OK != ::lineNegotiateAPIVersion(	m_hLineApp, 
															LineID, 
															dwLoVersion, 
															dwHiVersion, 
															&m_ApiVersions[LineID], 
															&dummy) ) 
					{ 
						m_ApiVersions[LineID] = 0; 
					} 
					//TRACE("API Ver: %X\n", m_ApiVersions[LineID]); 
				} 
			} 
		} 
	} 
} 
 
 
// ---------------------------------------------------------------------------- 
// Open valid TAPI lines 
void TapiApplication::OpenValidLines() 
{ 
	// Get and open valid lines 
	DWORD nLines = m_NumDevs; 
	DWORD nOpenedLines = 0; 
 
	m_pLines = new PTAPILINE[nLines]; 
	if (m_pLines != NULL) 
	{ 
		memset(m_pLines, 0, nLines * sizeof(PTAPILINE)); 
 
		for (DWORD LineID = 0; LineID < nLines; LineID++ ) 
		{ 
			LINEDEVCAPS *pDevCaps = NULL; 
			HRESULT tr = loopLineGetDevCaps(m_hLineApp, LineID, 
				m_ApiVersions[LineID], 0, pDevCaps); 
 
			// This is how you are supposed to select TAPI lines,  
			// i.e. according to the line's capabilities 
			if( (S_OK == tr) && 
				(pDevCaps->dwBearerModes & LINEBEARERMODE_VOICE) && 
				(pDevCaps->dwMediaModes & LINEMEDIAMODE_INTERACTIVEVOICE) && 
				(pDevCaps->dwLineFeatures & LINEFEATURE_MAKECALL) ) 
			{ 
				// Retrieve the name of the TAPI service provider 
				CString info, s1, s2; 
				getTapiString(s1, pDevCaps, pDevCaps->dwProviderInfoSize, 
					pDevCaps->dwProviderInfoOffset); 
				info.Format("[Provider = %s]", LPCTSTR(s1)); 
 
				// Skip TSPs that we are not interested in by looking for the 
				// Provider name of the IP Office TAPI driver 
				if (s1 != CString("Avaya")) 
					continue; 
 
				TRACE(info); 
				TRACE("API version: %X\n", m_ApiVersions[LineID]); 
//				m_Dlg.AddText(info); 
 
				getTapiString(s1, pDevCaps, pDevCaps->dwLineNameSize, 
					pDevCaps->dwLineNameOffset); 
				TRACE("[Line Name = %s]\n", LPCTSTR(s1)); 
 
				// Instantiate a TapiLine class for every line that we open 
				m_pLines[LineID] = new TapiLine(*this); 
				if (m_pLines[LineID]) 
				{ 
					tr = m_pLines[LineID]->Open(LineID, 
						LINECALLPRIVILEGE_OWNER, 
						LINEMEDIAMODE_INTERACTIVEVOICE /*| LINEMEDIAMODE_UNKNOWN*/); 
					if (S_OK == tr) 
					{ 
						nOpenedLines++; 
						// The following code looks up and displays the line ID 
						// However, this is the same as the LineID variable 
						//DWORD id; 
						//loopLineGetID(id, m_pLines[LineID]->m_hLine, 0, 0, 
						//	LINECALLSELECT_LINE, "tapi/line"); 
						//info.Format("[Line ID = %ld]", id); 
						//m_Dlg.AddText(info); 
 
						LINEADDRESSSTATUS* pAddressStatus = NULL; 
						loopLineGetAddressStatus(m_pLines[LineID]->m_hLine, 0, pAddressStatus); 
 
						// Tell the user about the capabilities of this line 
						info.Format("[Supported Line States = %s]", 
							DescribeDeviceStatus(s1, pDevCaps->dwLineStates)); 
//						m_Dlg.AddText(info); 
						// Find out about each address 
						for (DWORD AddressID = 0; AddressID < pDevCaps->dwNumAddresses; 
							AddressID++) 
						{ 
							// Find out its capabilities 
							LINEADDRESSCAPS *pAddressCaps = NULL; 
							tr = loopLineGetAddressCaps(m_hLineApp, 
								LineID, AddressID, 
								m_ApiVersions[LineID], 0, pAddressCaps); 
							// Find and use the name of the extension we control 
							getTapiString(s1, pAddressCaps, pAddressCaps->dwAddressSize, pAddressCaps->dwAddressOffset); 
							info.Format("[Extn ID = %s]", s1); 
							TRACE("[Extn ID = %s]\n",s1); 
//*							m_Dlg.AddText(info); 
 
							DWORD ext = atoi(s1); 
							m_pLines[LineID]->m_extension = ext; 
							if (m_CurrentLine == -1) 
							{ 
								m_CurrentLine = LineID; 
								m_extension = ext; 
								m_Dlg.SetExtension(s1); 
							} 
							// Tell the user about the capabilities of this address 
/*							info.Format("[MaxNumActive = %d, MaxNumOnHold = %d, " 
								"MaxNumOnHoldPending = %d, MaxNumConference = %d, " 
								"MaxNumTransConf = %d]", 
								pAddressCaps->dwMaxNumActiveCalls, 
								pAddressCaps->dwMaxNumOnHoldCalls, 
								pAddressCaps->dwMaxNumOnHoldPendingCalls, 
								pAddressCaps->dwMaxNumConference, 
								pAddressCaps->dwMaxNumTransConf); 
							m_Dlg.AddText(info); 
							info.Format("[Supported Address() = %s]", 
								DescribeAddressStatus(s1, pAddressCaps->dwAddressStates)); 
							m_Dlg.AddText(info); 
							info.Format("[Supported CallState() = %s]", 
								DescribeCallState(s1, pAddressCaps->dwCallStates)); 
							m_Dlg.AddText(info); 
							info.Format("[Supported CallInfo() = %s]", 
								DescribeCallInfo(s1, pAddressCaps->dwCallInfoStates)); 
							m_Dlg.AddText(info); 
							info.Format("[Call Completion Capabilities = %s]", 
								DescribeCallCompletion(s1, pAddressCaps->dwCallCompletionModes)); 
							m_Dlg.AddText(info); 
							info.Format("[Call Features = %s %s]", 
								DescribeCallFeatures(s1, pAddressCaps->dwCallFeatures), 
								DescribeCallFeatures2(s2, pAddressCaps->dwCallFeatures2) ); 
							m_Dlg.AddText(info); 
							info.Format("[Address Capabilities = %s]", 
								DescribeAddressCapabilities(s1, pAddressCaps->dwAddrCapFlags)); 
							m_Dlg.AddText(info);*/ 
							delete [] pAddressCaps; 
						} 
					} 
					else 
					{ 
						delete m_pLines[LineID]; m_pLines[LineID] = NULL; 
					} 
				} 
			} 
			delete [] pDevCaps; 
		} 
	} 
 
	TRACE("Number of Open Lines: %d\n", nOpenedLines); 
	if( !nOpenedLines ) 
	{ 
		delete[] m_pLines; 
		m_pLines = NULL; 
	} 
	m_Dlg.CheckButtons(); 
} 
 
// ---------------------------------------------------------------------------- 
// Note an immediate error message 
void TapiApplication::CheckError(HRESULT hr, LPCTSTR pszCommand) 
{ 
	if (hr < 0) 
	{ 
		CString info; 
		info.Format("Error(%s:0x%lx)", pszCommand, hr); 
		m_Dlg.AddText(info); 
	} 
}