Once a call has been made, an application can hand over a voice call to the user to complete, or for a data call it can start transferring data. The format of the data depends on the media selected to transfer the data, and this dictates the API functions used to send and receive data. For example, if you were writing a Windows CE device to send recorded voice through a telephone call you might use the Wave audio API functions. In the example presented in this chapter, a serial communications handle is obtained from TAPI to allow ReadFile and WriteFile to be used to read and send digital data. This technique would allow you to use TAPI to make a telephone call to a modem on a remote computer, and then once connected, use ReadFile and WriteFile to communicate with a computer. Obtaining a Communications Port HandleIn Chapter 9 ("Serial Communications"), the function CreateFile was called to open a serial port and obtain a file handle, and then ReadFile and WriteFile were used to transfer data. Finally, CloseHandle was called to close the port. Instead of calling CreateFile directly, you can make a call using a modem on a communications port using TAPI, and then call the lineGetID ( Table 11.8) function to obtain a file handle.
Data for a given media format can be obtained for an open line, an address on that line, or an open call handle. In the example given below in Listing 11.5a, a handle to an open line is passed and the file handle for the serial communications device is returned in lpVarString, a pointer to a VARSTRING structure. TAPI allows a communications handle to be returned for an open line, not just a line with an open telephone call. This allows data to be transferred between an application and a modem device before the call is made. Listing 11.5a Getting a communications port handleHANDLE GetCommPort() { DWORD dwSize = sizeof(VARSTRING) + 1024; DWORD dwReturn; LPVARSTRING lpVarString = (LPVARSTRING)LocalAlloc(LPTR, dwSize); if(lpVarString == NULL) return NULL; lpVarString->dwTotalSize = dwSize; dwReturn = lineGetID(g_hLine, // handle to open line 0, // address ID ignored NULL, // call handle ignored // we're only passing a line handle LINECALLSELECT_LINE, lpVarString, _T("comm/datamodem")); if(dwReturn != 0) { cout _T("Could not get line ID") endl; return NULL; } LPHANDLE lpHandle = (HANDLE*)((LPBYTE) lpVarString + lpVarString->dwStringOffset); HANDLE hComm = *lpHandle; cout _T("Port handle: ") (DWORD)hComm endl; cout _T("Communications port: ") (LPTSTR)((LPBYTE)lpVarString + lpVarString->dwStringOffset + sizeof(HANDLE)) endl; return hComm; } The VARSTRING structure allows variable amounts of data to be returned from lineGetID; this data can either be binary or string. The format of the data depends on the media format that is requested, and the data is returned at the end of the VARSTRING structure. The members in VARSTRING allow negotiation of the required size of the structure in the same way the structure LINETRANSLATEOUTPUT was used in Listing 11.3b. The VARSTRING member dwTotalSize is set to the actual size of the structure on calling lineGetID, and the dwNeededSize returns the actual number of bytes required to return all the data. In Listing 11.5a the application assumes that the size of the structure passed in is sufficient for the data returned. In a production system the size should be checked and negotiated. The VARSTRING structure returns two pieces of information for the "comm/modem" media data type:
The VARSTRING member dwStringOffset specifies where to start looking for the returned data. The following lines of code obtain a pointer to the first byte of data in the VARSTRING structure, cast this to a HANDLE*, and assign it to lpHandle. Then, the contents of the pointer's destination is copied into the hComm variable: LPHANDLE lpHandle = (HANDLE*)((LPBYTE) lpVarString + lpVarString->dwStringOffset); HANDLE hComm = *lpHandle; The device's name follows the handle, and a pointer to this name is returned with the following code, which adds the size of a HANDLE to the dwStringOffset: cout _T("Communications port: ") (LPTSTR)((LPBYTE)lpVarString + lpVarString->dwStringOffset + sizeof(HANDLE)) endl; Sending and Receiving DataOnce the handle has been obtained from lineGetID, the functions ReadFile and WriteFile can be used. These are described in Chapter 9 ("Serial Communications"). In Listing 11.5b the SendAndReceive function is passed a string to the written to the communications port (in lpszSend) using WriteFile. This is converted from Unicode to ANSI. The function then goes on to read returned data from the communications port using ReadFile. This is converted into Unicode and returned in the szReceive parameter. Listing 11.5b Sending and receiving dataBOOL SendAndReceive(HANDLE hComm, LPTSTR lpszSend, LPTSTR lpszReceive) { DWORD dwBytesWritten, dwBytesRead; DWORD dwBytesToWrite; char szmbsSend[1024], szmbsReceive[1024]; dwBytesToWrite = wcstombs(szmbsSend, lpszSend, 1024); if(!WriteFile(hComm, szmbsSend, dwBytesToWrite, &dwBytesWritten, NULL)) { cout _T("Could not write file: ") GetLastError() endl; return FALSE; } if(!ReadFile(hComm, szmbsReceive, 1024, &dwBytesRead, NULL)) { cout _T("Could not read file: ") GetLastError() endl; return FALSE; } lpszReceive[dwBytesRead] = '\0'; mbstowcs(lpszReceive, szmbsReceive, 1024); cout _T("Bytes Read: ") dwBytesRead endl; cout lpszReceive endl; return TRUE; } void Listing11_5() { HANDLE hComm; TCHAR szReceive[1024]; if(g_hLine == NULL) { cout _T("No open line") endl; return; } hComm = GetCommPort(); SendAndReceive(hComm, _T("\n"), szReceive); } The function Listing11_5 in Listing 11.5b shows how the GetCommPort function can be called to obtain a serial communications port handle. Next, a call is made to SendAndReceive, which will send a new line character to the connected host and wait for data to come back. Notice that CloseHandle is not called doing so will terminate the call, which may not be desirable. The handle will be closed by TAPI when a lineClose function call is executed.
|