The Console in Your Pocket

Rainer Urian

SUMMARY

This article shows how PocketConsole integrates into the Windows CE® operating system

Introduction

It is well known that some Windows CE devices have text-mode console support. For instance, on a Windows CE H/PC one can start up a DOS-like command prompt (CMD.exe) which eventually uses a console for input and output. On the other hand, it is widely believed that the Pocket PC platforms have no native console support. That is only half of the truth. Indeed, the only missing part on Pocket PC platforms is a console device driver dll with the suggestive name "console.dll". This dll performs the connection between the text mode i/o and the Windows CE native Graphics, Windowing, and Events Subsystem (GWES). For example, characters written by the printf function eventually have to be painted via GDI to a platform dependent window. The console device driver has to be individually designed for every Windows CE platform. For some unknown reason, Microsoft decided not to develop such a driver for the Pocket PC platforms. That is the reason why PocketConsole has been developed. PocketConsole is a full Windows CE conform implementation of a console device driver for Pocket PCs.

This document will first show how basic console support is implemented in the Windows CE operating system and how the c-runtime (CRT) library will make use of it. After that it will point out how PocketConsole together with the PortLib libary will improve this basic behaviour.

Through the rest of this document, the term CRT subsystem refers to the Windows CE c-runtime library and the underlying kernel functions.

Console.dll

Console.dll is a Windows CE stream device driver dll with device name prefix "CON". (The file system recognizes file names as stream device files if the file names consist of exactly three uppercase letters, a single digit, and a colon (:). This format follows the convention established in the Microsoft® MS-DOS® operating system for serial and parallel ports.) This implies that Windows CE can host up to 10 independent consoles simultaneously. This also implies that you can use the console device directly with the Windows CE native file system API:

//
// Example: Using the console with native file system API calls 
//

// You may be wondering that the CreateFile and CloseHandle calls are missing.
// The reason is that the fileno calls will implicitely perform the CreateFile 
// call on the CONX: device 
// On process termination, CloseHandle will be called implicitely on this device 
//
#include "windows.h"

int main(int argc, char* argv[])
{
  DWORD dwNumRead, dwNumWritten;
  char inp[256];
  HANDLE hConOut = fileno(stdout);
  HANDLE hConIn = fileno(stdin);
  char hello[] = "Hello World, how are you?";
  WriteFile(hConOut, hello, strlen(hello), &dwNumWritten, NULL);
  ReadFile(hConIn, inp, 256, &dwNumRead, NULL);
  WriteFile(hConOut, inp, strlen(inp), &dwNumWritten, NULL);
  return 0;
}

Figure 1: Using the console with native file system API calls

How the CRT subsystem uses console.dll

When an application makes the first call to one of the stdio functions (e.g. printf, puts, ...) the CRT subsystem first checks whether the application inherits the parent's console or has to create a console of its own. (This can be controlled with the CREATE_NEW_CONSOLE flag in the CreateProcess call.) As an example for an inherited console think about starting a console program from a command prompt. If the application has to create its own console, the CRT subsystem tries to register a new console device (CON1: to CON9:) with the Windows CE kernel by calling the RegisterDevice function. If this call failed, the stdio call simply returns by doing nothing. This happens, for instance, if console.dll is missing or if to much consoles have already been opened. For some reason, the CRT subsystem doesn't use the CON0: device. After successful registration, the console.dll driver will be mapped in the device.exe address space and the CON_Init function of console.dll will be called by the Windows CE kernel. This driver entry point is responsible for creating and initialzing the console window. On the first usage of a stdout stream, the CRT subsystem calls the CreateFile function with the registered or inherited console device name. If the stream hasn't been redirected to a normal file, this call will be routed by the kernel to the console drivers CON_Open function. Now the CRT subsystem has a valid Windows handle to the console. The same happens on first usage of the stderr and stdin streams. The CON_Open function will be used to perform some additional initialization on a per process and per stream basis. Now the CRT subsystem has a standard Windows handle to a console which can be used to for read/write access.

If the application calls one of the stdout functions to output some characters on the console, the CRT subsystem routes the characters to the WriteFile function with a handle of the previously opened stdout stream. This in turn will be routed by the kernel to the CON_Write function of console.dll. The CON_write driver function is eventually responsible for storing the bytes on the consoles screen buffer. In the same way, if the application calls one of the stdin functions, the CRT subsystem will call the filesystem's ReadFile function with a handle to the previously opened stdin stream. This will be routed to the CON_Read driver function, which is responsible for getting keyboard input out of the Windows message loop.

If the application is about to terminate, the CRT subsytem will call the CloseHandle function for every handle which has been associated with a stdio stream. If the stream was associated with a console device, this call will be routed to the driver's CON_Close function. After that, the CRT subsytem checks whether another process (e.g. the parent process) has still handles open to this console. If this is not the case, the CRT subsystem calls the DeregisterDevice function. This call will be routed to the drivers CON_Deinit entry point, which is responsible for destoying the console.

Startup code

In Windows c-programs, the first function an application programmer will see is the WinMain (resp wWinMain) function. However, before control can enter this function some initialization code has to be performed. For example, static variables have to be initialized and constructors of global C++ objects have to be called. Also, after return of the WinMain function, some termination/cleanup code has to be executed. For instance, destructors of global C++ objects have to be called. This is the job of the startup functions. The startup functions for the WinMain and wWinmMain functions reside in corelibc.lib and have the following signatures:

WinMainCRTStartup(HANDLE hInstance, 
                  HANDLE hPrevInstance, 
                  LPSTR lpszCmdParam, 
                  int nCmdShow ) 
                
wWinMainCRTStartup(HANDLE hInstance, 
                   HANDLE hPrevInstance, 
                   LPWSTR lpszCmdParam, 
                   int nCmdShow ) 
You have to tell the linker the proper startup function with the /entry: switch.

Also nowhere documented, there are two additional startup functions in the corelibc static library for starting up console programs:

mainACRTStartup(HANDLE hInstance, 
                HANDLE hPrevInstance, 
                LPSTR lpszCmdParam, 
                int nCmdShow ) 
                
mainWCRTStartup(HANDLE hInstance, 
                HANDLE hPrevInstance, 
                LPWSTR lpszCmdParam, 
                int nCmdShow ) 
The former is the ascii variant which calls the users main(int argc char* argv[]) function and the latter is the unicode version which calls the wmain(int argc wchar_t* argv[]) functions. Both startup functions are also responsible for cooking the  argc and argv parameters.

PocketConsole/Portlib extensions

PocketConsole in combination with the Portlib library extends the Windows CE console device driver model by providing the native Console API functions and some additional c-runtime functions from the Windows NT platform. For more details on those functions see the Port SDK help file. The following section shows how this has been implemented in PocketConsole and Portlib.

Portlib Console API support

Every Console API function in the Portlib library is actually only a thin wrapper around the DeviceIoControl function with a unique IOCTL number. When the application makes a call to a Console API function, the corresponding Portlib function sets up the necessary i/o buffers and calls DeviceIoControl with the corresponding IOCTL code. This in turn will be routed by the kernel to the CON_IOControl driver entry point. PocketConsole's console.dll has implementations for every IOCTL code corresponding to a Console API functions.

Portlib startup code

Since Portlib has to perfrom some additional initialization code, it has to provide its own startup functions. The Portlib startup function are called mainCRTStartup for starting up the main function and wmainCRTStartup for starting up the wmain function. Those functions have the same signatures as thier corresponding Windows CE counterparts. The Portlib startup code first forces Windows CE to create a console. This means, if you link against Portlib and set the linker's /entry: switch to mainCRTStartup (resp. wmainCRTStartup), a console will appear even before control enters the main (wmain) function. The mainCRTStartup (resp. wmainCRTStartup) code encloses the proper MainACRTStartup (resp. MainWCRTStartup) code with a __try __except construct. This is mainly done for implementing the signal function. If an exception occurs it will be catched by the _XcptFilter function, which in turn is resonsible to setup the signal function.

#ifdef UNICODE
void __cdecl  wmainCRTStartup(
#else
void __cdecl  mainCRTStartup(
#endif
        HANDLE hInstance,         
        HANDLE hPrevInstance,
        LPTSTR  lpszCmdParam,
        int    nCmdShow  )
{

  __try
  {
    __consoleinit();  /* do some portlib initialization, e.g. create the console */
#ifdef UNICODE
    mainWCRTStartup(hInstance, hPrevInstance, lpszCmdParam, nCmdShow);
#else
    mainACRTStartup(hInstance, hPrevInstance, lpszCmdParam, nCmdShow);
#endif
    __consoleexit(); /* do some portlib clean up */
  }
  __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
  {
     /*
      * Should never reach here
      */
    __consoleexit();
    _exit( GetExceptionCode() );
    
  }  /* end of try - except */
}

Figure 2: Portlib startup code

Portlib signal and control-c handler

The Portlib signal and control-c handlers are one of the most advanced issues in PocketConsole/Portlib. The control-c handler tries to mimic the behaviour of the Windows NT control-c handler, while the SIGINT signal handler has been modeled after the Unix version. On Windows NT, the signal function will create a new thread which calls SIGINT's handler function. This is in contrast to Unix systems, where the SIGINT handler runs in the same thread as the thread who has issued the signal. This leads to much headache in porting Unix console applications to the Windows NT platform. Since the portlib SIGINT handler has been implemented in the Unix style, porting Unix console applications from Unix to PocketConsole will be mad more easy. You can see the difference of handling SIGINT for Unix, Windows NT and PocketConsole by looking at the source code to SCM.