`
`Chapter 10 • RAP I: How the Outside World Talks to CE
`
`The Pascal unit you'll use to create our Delphi RAPI application is a freeware
`unit, generously provided by Scott Crossen. Crossen created a Pascal-/Delphi(cid:173)
`based import of all of the functions, types, and constants listed in Rapi . h. This in
`itself is no small feat-many of the structures in Rapi . h are quite complex and
`were probably very difficult to port. But the best thing about the Rapi . pas unit
`that Crossen created is that all of the functions are loaded dynamically.
`
`In C/C++, when you use the default Rapi . h, the compiler statically links
`Rapi. lib into your program. When the program starts on the user's machine
`and they're not running CE Services, it will crash before it even gets off the
`ground.
`
`What Crossen did differently was to attempt to find and load Rapi . dll at run(cid:173)
`time. If you can't find Rapi . dll, you exit gracefully and return an error code. In
`contrast to linking in rapi . lib, Crossen's implementation first checks to see if
`RAPI is available. If RAPI isn't available on the user's machine, the program
`won't crash; it can still perform every non-RAPI feature just fine.
`
`If you look at the code to do this, you'll see it starts with a call to Loadl i brary(),
`passing in 1 RAPI. DLL 1 as the library to load:
`RapiModule := LoadLibrary('RAPI.DLL 1);
`Check to make sure that the library was successfully loaded:
`if RapiModule > HINSTANCE_ERROR then
`begin
`Result := True;
`
`Next, proceed to retrieve the addresses of all of the RAPI functions, one at
`a time:
`
`@mCeRapiinit := GetProcAddress(RapiModule, 1CeRapiinit 1);
`// ... and repeat for each RAPI function
`@mCeGetSystemPowerStatusEx:= GetProcAddress(RapiModule 1 'CeGet-
`SystemPowerStatusEx1);
`
`end
`else
`Result := False;
`end;
`
`You've managed to successfully dynamically load all of the RAPI functions at
`once. The amazing thing about the "any Desktop development tool" aspect of
`
`Page 00326
`
`
`
`r
`
`Using Other languages
`
`301
`
`RAPI is that the code to retrieve all of the databases looks virtually the same in
`Pascal as it does in C/C++.
`
`For instance, when you retrieve the list of databases in your Delphi application,
`the code is virtually identical to that of the C/C++ version:
`
`hEnumContext := CeFindFirstDatabase(DBType);
`if hEnumContext = INVALID_HANDLE_VALUE then
`begin
`ShowMessage('Error retrieving DB Info');
`Exit;
`end;
`for i := 1 to TVCEDB.Items.Count-1 do
`if Assigned(TVCEDB.Items[i] .Data) then
`Dispose(TVCEDB.Items[i].Data);
`TVCEDB.Items.Clear;
`Node := TVCEDB.Items.Add(nil,
`Node.Imageindex := 0;
`Node.Selectedlndex := 0;
`ObjiD := CeFindNextDatabase(hEnumContext);
`while ((ObjiD <> 0) and (ObjiD <> ERROR_NO_MORE_ITEMS) and (ObjiD <>
`ERROR_INVALID_PARAMETER)) do
`begin
`CeOidGetinfo(ObjiD, CeOIDinfo);
`if CeOIDinfo.wObjType <> OBJTYPE_DATABASE then
`begin
`ObjiD := CeFindNextDatabase(hEnumContext);
`Continue;
`end;
`Application.ProcessMessages;
`
`'Device Databases');
`
`The only difference comes when you actually add the items to your Tree View,
`in that you're adding some data along with the actual text:
`
`with Node, CeOIDinfo.infDatabase do
`begin
`Node := TVCEDB.Items.AddChild(TVCEDB.Items[O], String(szDbase-
`Name));
`Data := new(PCeOIContainerStruct);
`TCeOIContainerStruct(DataA).OID := ObjiD;
`TCeOIContainerStruct(DataA).OIDinfo := CeOIDinfo;
`Imagelndex := 1;
`Selectedindex := 3;
`
`Page 00327
`
`
`
`302
`
`Chapter 10 • RAPI: How the Outside World Talks to CE
`
`end;
`ObjiD := CeFindNextDatabase(hEnumContext);
`end;
`if ObjiD = ERROR_INVALID_PARAMETER then
`ShowMessage('An Error occured while retrieving information from the
`CE device.');
`TVCEDB.Items[O] . Expand(True);
`
`With that one simple exception, the two sets of source code are virtually the same.
`
`By using some free source code, you can give your Delphi applications access to
`the entire RAPI library. And, since Inprise (formerly Borland) does not appear to
`have plans for a Delphi that compiles a true CE-based executable, this is definitely
`the next best thing.
`
`Summary
`
`RAPI helps extend the CE application into the Desktop by giving the other machines
`in your system access to the data and files on the CE device. This is especially
`important given that the data on these devices wouldn't be worth very much if
`you couldn't get that data circulated to other machines and other pieces of soft(cid:173)
`ware. In this chapter, you saw how you could get a Desktop program to perform the
`very same types of data access possible on the CE devices themselves. In addi(cid:173)
`tion, you saw how it was possible to extend even non-Microsoft development
`products so that they, too, could access and talk to a CE device.
`
`Page 00328
`
`
`
`Page 00329
`
`Page 00329
`
`
`
`Page 00330
`
`Page 00330
`
`
`
`CHAPT{R
`
`How CE Talks to the
`Outside World
`
`• Serial Communications Issues
`
`• Modem-Based Communications Issues
`
`• PC Card/PCMCIA Communications Issues
`
`• Winsock Commmunications Issues
`
`Page 00331
`
`
`
`306
`
`Chapter 11 • How CE Talks to the Outside World
`
`In the last chapter, you saw how Desktop programs can talk to a CE device. In
`this chapter, we'll be looking at how to get your CE device talking to the outside
`world, including all of your other devices, Desktop machines, or any other piece
`of equipment you might have in mind. We'll explore all areas of CE-based com(cid:173)
`munication from serial I/O to CE's Winsock support, starting with a brief overview
`of your options when it comes to CE communications. Then we'll look at each of
`the options in detail.
`
`What's in the Box?
`
`There are two aspects to Windows CE-based communication:
`
`• The hardware aspect
`
`• The software aspect
`
`Although this may seem like an obvious distinction, it's not. That's because
`tmder Windows 98/NT, an application could open a serial port or begin a Winsock
`operation without paying much attention to the underlying hardware. Windows
`CE is closer to the hardware level, however, so you generally have no such lux(cid:173)
`ury. And, as you've seen in previous chapters, each manufacturer may expose dif(cid:173)
`ferent features.
`
`The Hardware Aspect
`When it comes to hardware, there are only two ports you can count on:
`
`• A serial port
`
`• An Infrared (IR) port
`
`In addition to these ports, there is an entire set of uncertain hardware, including:
`
`• Modems
`
`•
`
`PCMCIA cards
`
`Page 00332
`
`
`
`What's in the Box?
`
`307
`
`The Serial Port
`
`The default serial port is usually the same one used to connect to the Desktop
`computer, and it is usually COMl. For the most part, it behaves like a serial port
`on a Desktop machine. Just as in Windows 98/NT, you can open the port in CE
`with a call to CreateFi 1 e(), read from it with ReadFi l e(), and so on. Most of
`your existing Windows-based serial communications code should port to CE
`rather easily.
`
`TheIR Port
`
`The IR port is more of a gray area, however. Although all commercial CE devices
`offer an IR port, the port number (i.e., COMl, COM2, etc.) changes from one device
`to the next.
`
`Also, some manufacturers configure their devices so that both the serial port
`and theIR port appear as COMl. In this case, the only way to specify which one
`you want is to open the port and then try to set it into IR mode.
`
`And, as if it isn't confusing enough already, some devices allow you to open a
`serial port and an IR port at the same time. This goes against everything we've
`ever been told about CE allowing only one serial connection at a time.
`
`Modems and PCMCIA Cards
`
`As for devices that may or may not be available to you, some devices, such as the
`HP Jornada, have a built-in modem; other devices offer it as an upgrade; and
`some don't offer a modem at all. Similarly, the PCCard (PCMCIA) slot opens the
`device up to such hardware as networking cards, cell phone modems, additional
`serial ports, or higher-speed modems.
`
`TIP
`
`If your application calls for additional serial ports, your best bet is either the serial
`1/0 card or the dual serial I/O card offered by Socket Communications of Newark,
`California (http: I /www. socketcom. com). Both are PCMCIA cards that instantly
`add one or two RS-232 ports to your CE device.
`
`All of this hardware variety only makes it more of a challenge to design reliable
`communications-related software.
`
`Page 00333
`
`
`
`308
`
`Chapter 11 • How CE Talks to the Outside World
`
`The Software Aspect
`Obviously, with so much possible hardware available on aCE device, there's
`some complexity to the software as well. When it comes to communications, Win(cid:173)
`dows CE supports a mix of everything from Win32 serial communications func(cid:173)
`tions to Winsock to special blends of Winsock and IR.
`
`Serial Communications
`
`Serial communications haven't changed very much from Windows 98/NT to
`Windows CE. CE supports 16 of the 23 communications-related API calls, and the
`ones that aren't there probably won't affect your applications much, if at all. The
`seven unsupported functions are
`
`• Buil dCommDCB()
`• BuildCommDCBAndTimeouts()
`• CommConfigDialog()
`• GetCommConfig()
`• GetDefaultCommConfig()
`• SetCommConfig()
`• SetDefaultCommConfig()
`
`It is possible to do almost everything these functions do with the functions
`that are supported by Windows CE. For instance, most of the functionality of
`GetCommConfi g() can easily be replaced with the function GetCommState(),
`which is supported by CE.
`
`The following is a list of the 16 functions that are supported under Windows CE:
`• ClearCommBreak()
`• ClearCommError()
`• EscapeCommFunction()
`• GetCommMask()
`• GetCommModemStatus()
`• GetCommProperties()
`
`Page 00334
`
`
`
`What's in the Box?
`
`309
`
`• GetCommState()
`• GetCommTimeouts()
`• PurgeComm()
`• SetCommBreak()
`• SetCommMas k ()
`• SetCommState()
`• SetCommTimeouts()
`• SetupComm()
`• TransmitCommChar()
`• WaitCommEvent()
`
`With this many supported functions, it's clear that the few functions that are
`missing shouldn't affect you too much.
`
`NOTE
`
`For more information on some of the missing API functions, see Chapter 3.
`
`IR Communications
`
`IR communications on a CE device are available in three classes:
`
`• RawiR
`
`•
`
`•
`
`IrCOMM
`
`Infrared Sockets (IrSock)
`
`Raw IR Raw IR means that you'll be using theIR port as though it was any other
`serial port. There is no special handshaking and no error handling.
`
`The most difficult part about using the IR port as a raw serial port comes from
`the fact that, as noted above, every manufacturer seems to assign a different port
`number to the IR port. With some of the earlier CE devices, it was possible to
`query a certain key in the registry to determine the IR port number, but it seems
`that all manufacturers do not store that information in the same place.
`
`Page 00335
`
`
`
`310
`
`Chapter 11 • How CE Talks to the Outside World
`
`The only truly reliable way to determine the logical designation of theIR port is
`to loop through all of the ports and ask each one if it's theIR port.
`
`The way to ask a port whether it is an IR port or a standard serial port is to call
`EscapeCommFun cti on () and pass in the handle to the open port and a flag
`(SETIR) indicating that you want to put the port into IR mode:
`
`EscapeCommFunction(hPort, SETIR);
`
`Since this function only returns TRUE if the port is an IR port, it's safe to
`assume that if the call fails, the port is not an IR port, and you need to keep look(cid:173)
`ing. The most efficient and effective way to use this trick is probably to create a
`routine to detect the presence of theIR port. To do this, first initialize the result of
`the function to 0 to indicate that an IR port could not be found and then allocate
`memory to store the "name" (COMl, COM2, etc.) of the comm port:
`result = 0; //zero indicates error, could not find port
`szPort = (TCHAR ''')LocalAlloc(LMEM_ZEROINIT, MAX_PATH);
`
`Then, loop from 1-the lowest comm port number-to MAX_PORTS, a prede(cid:173)
`fined constant indicating the highest port number to test:
`for (i=l; i <= MAX_PORTS;
`
`i++)
`
`WARNING
`
`The MAX_PORTS value differs greatly from one device to another. On some devices,
`such as the Casio E-1 0, the highest comm port number is COM3; on the Casio 2400,
`the highest comm port number is COM4; and on the HP Jornada, the highest comm
`port number is COM6!
`
`Next, construct the name of the comm port using the value of i as the number
`of the comm port:
`
`_tcscpy(szPort, TEXT("COM"));
`_itow(i, szNum, 10); //convert
`_tcscat(szPort, szNum);
`_tcscat(szPort, TEXT(":"));
`
`to string
`
`NOTE
`
`In the above code, there is a call to the RTL function _ i tow(), which only works with
`Unicode-based strings. Ideally, the function to use is_ itot(), which works
`with the generic-string types. However,_ i tot() is not supported on all of the CE
`platforms. See Appendix A for more information.
`
`Page 00336
`
`
`
`--
`
`What's in the Box?
`
`311
`
`WARNING
`
`You'll notice that the last step in the above code is to append a colon to the port
`name. Although Windows 98 and Windows NT do not require that the port name
`be followed with a colon, Windows CE does.
`
`The next step is to open the port whose name you've just created. Just as you
`would on Windows 98/NT, do this using the CreateFi 1 e() function:
`hPort = CreateFile(szPort, GENERIC_READ I GENERIC_WRITE,
`0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
`if (hPort != INVALID_HANDLE_VALUE)
`{
`
`Now, if you've been able to successfully open the port, you can apply the trick
`of attempting to set the port to IR mode. If it returns TRUE, that means you found
`theIR port:
`
`if (EscapeCommFunction(hPort, SETIR))
`{
`
`//Ir Port Found!!!
`result = i ;
`CloseHandle(hPort);
`break;
`
`The rest of the function is just clean up and remembering to return the numeric
`value representing theIR comm port:
`
`CloseHandle(hPort);
`}
`LocalFree(szPort);
`return result;
`
`When fully assembled (and with some function calls to display the status of
`your search in a list box), the full function looks like this:
`
`int DetectiRPort(void)
`{
`
`int i, result;
`TCHAR * szPort;
`TCHAR szNum[4];
`HANDLE hPort;
`
`Page 00337
`
`
`
`312
`
`Chapter 11 • How CE Talks to the Outside World
`
`result = 0; //zero indicates error, could not find port
`szPort = (TCHAR ''')Local A 11 oc(LMEM_ZEROINIT, MAX_PATH);
`for (i=l; i <= MAX_PORTS; i++)
`{
`
`_tcscpy(szPort, TEXT("COM"));
`_itow(i, szNum, 10); //convert i to string
`_tcscat(szPort, szNum);
`_tcscat(szPort, TEXT(":"));
`SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("Now testing
`port:"));
`SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)szPort);
`SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(" ... "));
`hPort = CreateFile(szPort, GENERIC_READ I GENERIC_WRITE,
`0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
`if (hPort != INVALID_HANDLE_VALUE)
`{
`
`if (EscapeCommFunction(hPort, SETIR))
`{
`
`SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)TEXT( "IR Port
`Found! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! "));
`SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)szPort);
`result = i;
`CloseHandle(hPort);
`break;
`
`CloseHandle(hPort);
`}
`LocalFree(szPort);
`return result;
`
`When integrated into a sample test application, the result looks like the applica(cid:173)
`tion shown in Figure 11.1.
`
`lrCOMM In addition to the raw IR, which we've just examined, there is another
`kind of IR communication available under Windows CE, IrCOMM. This is a more
`reliable form of IR serial communication that provides handshaking, error handling,
`and many other features that make it an attractive alternative to raw IR.
`
`Page 00338
`
`
`
`What's in the Box?
`
`313
`
`FIGURE 11.1:
`
`The lrDetect application
`
`IRDetect- queues serial ports t ... l3
`
`Now tBsting port:
`COMl:
`...
`lR Port Found!!!!!!!!!!ll!l!!!
`COMl:
`
`The best part about all of these additional features is that they're completely
`transparent-as far as your code is concerned, the IrCOMM port is just like any
`other serial port. In other words, the IrCOMM port can be used as though it were
`a standard serial port. That means you can call CreateFi l e(), ReadFi l e(), etc.,
`while still enjoying the benefits of a more reliable connection.
`
`In fact, the only difference between raw IR and IrCOMM that you'll actually
`notice is that the IrCOMM port is on a different logical comm port number than
`the raw IR port! To ensure that you open the correct IrCOMM port, you'll need to
`consult the registry. The key containing the information about the IrCOMM port
`is HKEY _ LOCAL_MACHINE\Dri vers\Bui l tin\IrCOMM. The actual port number is
`contained in the value Index under this key. Or, as it would appear in code:
`
`HKEY hKeyiR;
`if (ERROR_SUCCESS == RegOpenKeyEx (HKEY_LOCAL_MACHINE,
`TEXT("Drivers\\Builtin\\IrCOMM"), 0, KEY_READ, &hKeyiR))
`{
`
`DWORD dwiRport;
`DWORD dwSizeData = sizeof (DWORD);
`if (ERROR_SUCCESS == RegQueryValueEx (hKeyiR, TEXT("Index "),
`NULL, NULL, (LPBYTE)&dwiRport, &dwSizeData))
`
`WCHAR wszMsg[64];
`
`Page 00339
`
`
`
`~14
`
`Chapter 11 • How CE Talks to the Outside World
`
`wsprintf (wszMsg, TEXT"IrCOMM port number is: %u"),
`dwiRport);
`MessageBox (NULL, wszMsg, TEXT("IR Port Info "), MB_OK);
`
`Infrared Sockets (lrSock) Infrared Sockets is a Winsock-like wrapper for
`communicating over the Infrared port. Refer to the "Winsock-Based Communica(cid:173)
`tions" section later in this chapter to see what it takes to convert a simple
`Winsock-based application to an Infrared Sockets application.
`
`Modem-Based Communications
`There are really three classes of modems when it comes to CE:
`
`• Standard external modems connected via the serial port
`
`• Built-in modems
`
`•
`
`PC Card/PCMCIA modems
`
`Standard External Modems Standard external modems are just that: stan(cid:173)
`dard. There is nothing special about opening a serial port and dialing a modem
`connected to that serial port when working with Windows 98/NT, and that's true
`under Windows CE as well.
`
`Built-in Modems It's tempting to conclude that built-in modems work just like
`external modems-and they do. The trick, however, is finding the internal modem,
`or more correctly, finding the comm port of the internal modem. The way to do this
`is to iterate through the HKEY _LOCAL_MACHINE\ExtModems key of the registry.
`Under this key, there will be a key for each of the modems the device knows
`about. For example, all CE devices have a default Hayes Compatible modem set(cid:173)
`ting that refers to any external modem on COMl. Therefore, under the HKEY _LOCAL_
`MACHIN E\ExtModems key, there is a Hayes Compati b 1 e key. To get the actual
`comm port name, which you can then use to open the port, simply retrieve the
`string stored in the Port value of the Hayes Compati b 1 e key. Or, in code, open
`the ExtModems key under HKEY _LOCAL_MACHINE as the first step:
`
`RegOpenKeyEx (HKEY_LOCAL_MACHINE, TEXT("ExtModems"), 0, KEY_READ,
`&hKey);
`if (hKey)
`
`Page 00340
`
`
`
`What's in the Box?
`
`315
`
`Next, create awhile loop that retrieves the names of the subkeys under the
`ExtModems key (i.e., the subkeys for each of the different modems).
`
`retCode = ERROR_SUCCESS;
`i = 0;
`while (retCode == ERROR_SUCCESS)
`{
`
`cbName = MAX_PATH;
`memset(szSubKeyName, 0, MAX_PATH);
`retCode = RegEnumKeyEx(hKey, i, szKeyName,&cbName, NULL, NULL,
`NULL, NULL);
`i++;
`
`If you are able to successfully enumerate the key, then try to open the key so
`you can retrieve the values you're after:
`
`if (retCode == (DWORD)ERROR_SUCCESS)
`{
`
`memset(szModeminfo, 0, MAX_PATH);
`memset(szPortinfo, 0, MAX_PATH);
`memset(szDevName, 0, MAX_PATH);
`RegOpenKeyEx (hKey, szKeyName, 0, KEY_READ, &hSubKey);
`
`If you are able to open the key, you can retrieve the Port value and the
`Fri endl yName of the modem:
`
`if (hSubKey)
`{
`
`dwBytes = MAX_PATH;
`RegQueryValueEx (hSubKey, TEXT("Port"), NULL, &dwType,
`(LPBYTE)szPortlnfo, &dwBytes);
`dwBytes = MAX_PATH;
`RegQueryValueExW(hSubKey, TEXT("FriendlyName"), NULL, &dwType,
`(LPBYTE)szDevName, &dwBytes);
`
`Optionally, you could then format the two strings retrieved and add the for(cid:173)
`matted string to a ComboBox:
`
`wsprintf (szModeminfo, TEXT("%s %s"), szPortinfo, szDevName);
`SendDlgitemMessage(hwnd, IDC_CBOMODEMS, CB_ADDSTRING, 0,
`(LPARAM)szModeminfo);
`
`Finally, clear out your strings for the run through the next subkey:
`
`memset(szModeminfo, 0, MAX_PATH);
`memset(szPortlnfo, 0, MAX_PATH);
`
`Page 00341
`
`
`
`316
`
`Chapter 11 • How CE Talks to the Outside World
`
`memset(szDevName, 0, MAX_PATH);
`RegCloseKey(hSubKey);
`
`}
`}
`}
`}
`
`If you then create a simple dialog with a ComboBox and put this code into, say,
`a WM_INITDIALOG message handler, the final assembled message handler looks
`like this:
`
`case WM_INITDIALOG:
`{
`
`HKEY hKey, hSubKey;
`DWORD dwDisposition;
`DWORD dwType;
`DWORD dwBytes = 0;
`TCHAR *szSubKeyName;
`TCHAR tcszKeyName;
`TCHAR ClassName[MAX_PATH] =TEXT( "" ); I I Buffer for class name .
`DWORD cbName ;
`TCHAR
`''szPortinfo;
`TCHAR >r s zDevName;
`TCHAR *szModeminfo;
`DWORD retCode;
`i nt i;
`
`szSubKeyName = (TCHAR *)LocalAlloc(LMEM_ZEROINIT, MAX_PATH);
`szKeyName = (TCHAR ''' )LocalAlloc(LMEM_ZEROINIT, MAX_PATH);
`szPortlnfo = (TCHAR ''')LocalAlloc(LMEM_ZEROINIT, MAX_PATH);
`szDevName = (TCHAR *)LocalAlloc(LMEM_ZEROINIT, MAX_PATH);
`szModeminfo = (TCHAR *)LocalAlloc(LMEM_ZEROINIT, MAX_PATH);
`
`RegOpenKeyEx (HKEY_LOCAL_MACHINE, TEXT("ExtModems "), 0, KEY_READ,
`&hKey);
`if (hKey)
`{
`
`retCode = ERROR_SUCCESS;
`i = 0;
`while (retCode == ERROR_SUCCESS)
`{
`
`cbName = MAX_PATH;
`memset(szSubKeyName, 0, MAX_PATH);
`
`Page 00342
`
`
`
`What's in the Box?
`
`.. 317 J
`
`retCode = RegEnumKeyEx(hKey, i, szKeyName,&cbName, NULL,
`NULL, NULL, NULL);
`
`i++;
`if (retCode == (DWORD)ERROR_SUCCESS)
`{
`
`memset(szModeminfo, 0, MAX_PATH);
`memset(szPortlnfo, 0, MAX_PATH);
`memset(szDevName, 0, MAX_PATH);
`RegOpenKeyEx (hKey, szKeyName, 0, KEY_READ, &hSubKey);
`if (hSubKey)
`{
`
`dwBytes = MAX_PATH;
`RegQueryValueEx (hSubKey, TEXT("Port"), NULL, &dwType,
`(LPBYTE)szPortinfo, &dwBytes);
`dwBytes = MAX_PATH;
`RegQueryValueExW(hSubKey, TEXT("FriendlyName"), NULL,
`&dwType, (LPBYTE)szDevName, &dwBytes);
`wsprintf (szModeminfo, TEXT("%s %s"), szPortinfo,
`szDevName);
`SendDlgltemMessage(hwnd, IDC_CBOMODEMS, CB_ADDSTRING, 0,
`(LPARAM)szModeminfo);
`memset(szModeminfo, 0, MAX_PATH);
`memset(szPortinfo, 0, MAX_PATH);
`memset(szDevName, 0, MAX_PATH);
`RegCloseKey(hSubKey);
`
`LocalFree(szSubKeyName);
`LocalFree(szKeyName);
`LocalFree(szPortinfo);
`Loca1Free(szDevName);
`LocalFree(szModeminfo);
`return TRUE;
`
`The resulting Modem Selector dialog looks something like the one pictured in
`Figure 11.2.
`
`Page 00343
`
`
`
`318
`
`Chapter 11 • How CE Talks to the Outside World
`
`FIGURE 11.2:
`
`The Modem Selector dialog
`
`Modem Selector
`
`r:::J 13
`
`PC Cards/PCMCIA Modems PCMCIA modems are really just another kind of
`PCMCIA device. In the next section, we'll be looking at all PCMCIA devices as a
`group, regardless of whether the actual card is a modem, a serial I/ 0 card, or a
`network interface card.
`
`PC Cards/PCMCIA Cards and Communications
`
`PC cards/PCMCIAcards are fairly transparent when it comes to communica(cid:173)
`tions. Your application could open a comm port and never really know that the
`port it just opened was really, say, a PCMCIA modem.
`
`However, there are two occasions when your application will care about the
`PCMCIA slot:
`
`• When the application starts and needs to find out if the desired card/port is
`inserted into the slot and available for use
`
`• When the application is running and the card in the slot changes (i.e., a card
`is added or removed)
`
`When the Application Starts Officially, the Microsoft documentation says
`that when your application starts, you can use a function called EnumPnpids() to
`retrieve a double-NULL terminated list of strings representing the device(s) cur(cid:173)
`rently inserted in the PCMCIA slot(s). However, there is one problem with this
`function: it doesn't exist in any of the . h files for any versions of Windows CE!
`
`TIP
`
`The Pnp in EnumPnpids() stands for Plug and Play
`
`This doesn't mean you can't get a list of available PCMCIA cards when your
`program starts, however; you just can't get that list using the EnumPnpids()
`
`Page 00344
`
`
`
`What's in the Box?
`
`319
`
`function. Instead, you can create a function that does the exact same thing by
`querying the registry.
`
`In this case, the key you're interested in is HKEY_LOCAL_MACHINE\Drivers\
`Active. In this key you will find a set of double-digit subkeys numbered 00 to nn,
`where nn is a double-digit integer.
`
`Each of these double-digit subkeys specifies a different driver or hardware compo(cid:173)
`nent that is currently being used by the device. The values of nn are assigned
`somewhat sequentially, with the OEM system components taking the lower num(cid:173)
`bers first. For instance, on many systems, the 00 entry contains information about
`the sound component, WAVl: . The value of nn assigned to a PCMCIA card
`depends on the number of times any cards have been inserted since the device
`was last reset. If the OEM has used, say 00 through 09 for system devices, the first
`PCMCIA card inserted will get an nn value of 10, the second card a value of 11,
`and so on. Also, if a card is inserted, removed, and then inserted at a later time
`(but before a reset) that card may or may not be assigned a different value than
`the one it had the last time it was used.
`
`The way this set of double-digit subkeys helps make it possible to get informa(cid:173)
`tion about the PCMCIA cards currently available on the device is that all PCMCIA(cid:173)
`related subkeys will have a value called Pnpid. If an entry has this value and a
`Name value containing the string COM, then it is a serial communications card of
`some kind.
`
`Note that this method also detects compact flash cards, so if your application
`expects a comm port in the form of a compact flash card, this technique will work
`to detect its presence as well.
`
`Using the same logic employed above in the modem detection routine, you can
`start by opening the HKEY _LOCAL_MACHINE\Dri ve rs\Acti ve key:
`
`RegOpenKeyEx (HKEY_LOCAL_MACHINE, TEXT("Drivers\\Active"), 0, KEY_READ,
`&hKey);
`
`We present only a portion of the full code here so as to avoid duplicating the
`modem detection code above. The full code for the Pnpid enumeration is on the
`CD for this book, in the directory for this chapter.
`
`NOTE
`
`NOTE
`
`NOTE
`
`Page 00345
`
`
`
`320
`
`Chapter 11 • How CE Talks to the Outside World
`
`Just as before, you can enumerate the nn subkeys:
`
`RegEnumKeyEx(hKey, i, szKeyName,&cbName, NULL, NULL, NULL, NULL);
`
`And for each subkey enumerated, you can attempt to retrieve the Pnpid and
`the device name (i.e., COMl, COM2, etc.):
`
`I I ...
`RegQueryValueEx (hSubKey, TEXT("Pnpid"), NULL, &dwType,
`(LPBYTE)szPnpld, &dwBytes);
`// ...
`RegQueryValueExW(hSubKey, TEXT("Name "), NULL, &dwType,
`(LPBYTE)szDevName, &dwBytes);
`
`If the string retrieved from the Pnpid value is non-NULL, add this item to your
`list box of PCMCIA devices:
`
`if (_tcscmp(szPnpld, TEXT( "" )) != 0) / / there was a Pnpid
`
`wsprintf (szDevName, TEXT(" %s %s"), szDevName, szPnpld);
`SendDlgitemMessage(hwnd, IDC_LSTPNPIDS, LB_ADDSTRING, 0,
`(LPARAM)szDevName);
`
`When this code is hooked up to a dialog box with a ListBox on it, the result is
`something like Figure 11.3.
`
`FIGURE 11.3:
`
`PCMCIA-Pnpids
`application
`
`CMCIA·Pnplds
`
`m:J 13
`
`PCMCIA-Pnplds (c) 1999 Terence Gogg1n from
`"The Windows CE Developer's Handboo~."
`
`TIP
`
`It turns out that the EnumPnplds( )actually does exist, it's just completely undocu(cid:173)
`mented. John Psuik, technical editor of this book, has included a sample on the
`CD documenting how to call this function, if you prefer to go the undocu mented
`route.
`
`Page 00346
`
`
`
`What's in the Box?
`
`321
`
`When the Card in the Slot Changes If you are doing serial communications
`in your application, you're going to have to know when the card in the PCMCIA
`slot changes.
`
`Why? Well, first you have the issue of error prevention or detection. If your
`application is in the middle of a file transfer over a PCMCIA-based serial port, and
`the user pulls the card out, your application will immediately know an error has
`occurred. However, your application will be much more robust if it is able to deter(cid:173)
`mine that the source of the error was the user removing the card and not, say, a
`problem with the other computer.
`
`Second, even if your application is not actually using a cornrn port at the moment
`a card is removed or added, you'll probably want to update the cornrn port options
`that you offer the user. If the user inserted a modern or a serial 1/0 card, it's a safe
`guess they want to use that port with your program, and they don't want to have to
`restart your application in order to refresh the list of available cornrn ports.
`
`Now that we know the advantages of monitoring changes in the PCMCIA slot,
`let's investigate how to do it. Like the techniques outlined in the section above,
`detecting a change in the current PCMCIA card requires some undocumented-or
`at least under-docurnented--CE h·icks. Here again, the documentation says that an
`application need only respond to the WM_DEVICECHANGE message in order to be
`notified when a PCMCIA card is inserted or removed. However, there appears to
`be a small problem with this in that other portions of the documentation indicate
`that WM_DEVICECHANGE doesn't even exist onCE!
`
`So what's the truth?
`
`The WM_DEVICECHANGE message does exist, but it's not defined in the wi nuser. h
`header file where all of the other WM_ messages are defined. Instead, the
`WM_DEVICECHANGE message and a number of related constants and structures are
`all defined in a separate header file called dbt. h.
`
`The first step, then, in handling the WM_DEVICECHANGE message is including this
`header file.
`
`The second step is correctly interpreting the wParam value that the
`WM_DEVICECHANGE message passes to your application. From testing, it appears
`that there are only two values that matter under Windows CE:
`
`•
`
`DBT_ DEVICEARRIVAL A card has just been inserted into the slot.
`
`• DBT _DEVICEREMOVECOMPLETE A card has just been removed from the slot.
`
`Page 00347
`
`
`
`322
`
`Chapter 11 • How CE Talks to the Outside World
`
`Code to handle the wParam of the message, then, might look like this:
`
`switch(wParam)
`{
`case DBT_DEVICEARRIVAL:
`//Card was inserted
`break;
`case DBT_DEVICEREMOVECOMPLETE:
`//Card was removed
`break;
`
`The l Par am of WM_DEVICECHANGE is a pointer to a structure that should tell you
`a little bit of information about the device. The trick to using the l Par am is that it
`points to one of several possible structures, depending on what type of card has
`been inserted. In order to determine what type of card and, therefore, which
`structure l Param is pointing to, you must first cast the l Param to a generic struc(cid:173)
`ture and read one of that structure's members.
`
`The generic structure is called DEV_BROADCAST _HEADER and is defined as follows:
`
`struct _DEV_BROADCAST_HEADER
`{
`
`DWORD
`DWORD
`DWORD
`} ;
`
`dbcd_size;
`dbcd_devicetype;
`dbcd_reserved;
`
`In order to determine the type of card that was just inserted or removed, you
`must examine the value of the structure's dbcd_devi cetype member. The possible
`values for dbcd_devi cetype are
`
`•
`•
`•
`
`DBT_DEVTYP _OEM Unspecified OEM type card
`
`DBT _DEVTYP _PORT Serial or parallel port
`
`DBT_DEVTYP _NET Network resource
`
`NOTE
`
`There are other possible values for the dbcd_devi cetype member, but they do
`not appear to have meaning under Windows CE.
`
`Page 00348
`
`
`
`What's in the Box?
`
`323
`
`Once you've tested the dbcd_devi cetype value, you can cast the l Param to a
`more detailed structure specific to the card's type. For some reason, however, it
`appears that CE reports all PCMCIA cards as being DBT _DEVTYP _ PORT cards-in
`other words, it considers all cards to be serial- or parallel-port cards.
`
`The positive side of this is that you only have to worry about casting the l Par am
`to one type of structure; the negative side is that you'll have to work even harder to
`differentiate the cmmn port cards from the other types of cards.
`
`The comm port-specific structure is called DEV _BROADCAST _PORT and is defined
`as follows:
`
`typedef struct DEV_BROADCAST_PORT __ W
`{
`
`DWORD
`DWORD
`DWORD
`wchar_t
`} ;
`
`dbcp_size;
`dbcp_devicetype;
`dbcp_reserved;
`dbcp_name[l];
`
`As you may have guessed, the member of this structure that actually makes it
`possible to determine whether or not the card in question is a comm port is
`dbcp_ name [1]. If the card is a comm port, the name will contain the text "COM "
`followed by a number and a colon. Th