Microsoft & .NET.NETReading And Writing Files From WinCE Apps

Reading And Writing Files From WinCE Apps

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Once you find a CE file and open it, it’s time to confront the problem of the portability of data. Specifically, getting text out of files and into controls will very often entail some character format translation. In the GreatWriting example, we’ll see how to read a multibyte character set (MBCS) text and convert it to Unicode for display in an edit control.

Figure 1 The GreatWriting Example Running on an HPC

The file GreatWriting.cpp is provided in an accompanying file, but support files are omitted for the sake of brevity. You can generate these using your development environment tools.

Looking At the Source File for GreatWriting

In the GreatWriting example, the user chooses a writing sample from the file menu, and then the writing is displayed in the edit control of the dialog box. Here is the code that implements menu choice in WndProc:

case IDM_GEISEL:
   DialogBoxParam(hInst, TEXT("WRITERS"), hWnd,
       GreatWritersDlgProc, IDM_GEISEL);
   break;
case IDM_NIETZCHE:
   DialogBoxParam(hInst, TEXT("WRITERS"), hWnd,
       GreatWritersDlgProc, IDM_NIETZCHE);
   break;

Notice that we use DialogBoxParam() to invoke the dialog. This function allows you to pass a value to the dialog box when it receives the WM_INITDIALOG message in the LPARAM. In this case, we pass the ID of the menu choice, which we use to set up the initialization of the edit control.

In GreatWritersDlgProc(), we open one of two files:

case WM_INITDIALOG:
  //get text file and translate
  if( lParam == IDM_GEISEL )
  {
    hAsciiFile = CreateFile( TEXT("My Documentsgeisel.asc"),
                 GENERIC_READ ,
                 FILE_SHARE_READ, NULL, OPEN_EXISTING, 
                 FILE_ATTRIBUTE_NORMAL, NULL);
  }
  else
  {
    hAsciiFile = CreateFile( TEXT("My Documentsnietzche.asc"),
                 GENERIC_READ ,
                 FILE_SHARE_READ, NULL, OPEN_EXISTING, 
                 FILE_ATTRIBUTE_NORMAL, NULL);
  }

The files “geisel.asc” and “nietzche.asc” are ordinary ASCII text files. I gave them the suffix “.asc” here to emphasize the fact that the text in them is in single byte characters. Notice also that they have hard-coded pathnames. I did this so that we wouldn’t need a file open dialog, and because this particular path exists on both the PPC and HPC.

Because both files are very brief, we’ll read them into memory all at once. The parameters to GetFileSize(), in the order shown, are the handle to the file, and the address of the high order DWORD. If this parameter is non null, it is used to store the upper DWORD of the file size.

//get file length
dwFileSize = GetFileSize( hAsciiFile, NULL );
//allocate a buffer for the script file
pszFileBuff = (char*)LocalAlloc( LPTR, dwFileSize );
if( !pszFileBuff )
{
    MessageBox( hDlg, TEXT( "Couldn't Allocate Memory"), 
                TEXT( "Open File Failed" ), MB_OK);
}

Next, we capture the identity of this author, using the static integer variable iAuthor. The static keyword means that this variable retains its value across invocation of this function. We could have made it a global and accomplished the same result, but because it is only used in this function, it is cleaner to declare it inside the scope of the dialog box procedure.

//Capture the identity of this author
//we'll use it if we write a unicode file
iAuthor = lParam;

We read the ASCII file into the newly allocated buffer, but we can’t use this code to initialize the dialog until we translate the ASCII characters to Unicode.

//read the file
ReadFile( hAsciiFile,(LPVOID)pszFileBuff,
          dwFileSize, &dwBytesWritten, NULL)

GetFileSize() reported the file size in bytes, which incidentally corresponds to its size in ASCII characters. Probably the most difficult things to get used to with respect to Unicode is that you can’t treat the name of a Unicode array as an address, and you can’t treat individual characters as bytes. Notice that in calculating the size of the buffer we use to translate the string from ASCII to Uncicode, we multiply the total character count by sizeof(TCHAR).

//allocate a translation buffer
pszUnicodeBuff = (TCHAR*)LocalAlloc( LPTR, 
                         sizeof(TCHAR)*( dwFileSize + 1 ));

Now, we are prepared to translate, and call mbstowcs() to do the job. The name of this function is an acronym for “Multi Byte String To Wide Character String.” The parameters, in the order shown, are the address of a WCS string, a pointer to a constant ASCII string, and the maximum number of source string characters to convert.

//translate to unicode
mbstowcs( pszUnicodeBuff, (const char *)pszFileBuff,
         (size_t)strlen(pszFileBuff) );

Now, we set the translated text into the edit control, release the allocations, and close the file.

//fill the edit control
SetDlgItemText(hDlg, IDC_WRITING_SAMPLE, pszUnicodeBuff);

//release translation buffer
LocalFree(pszUnicodeBuff);

//release file buffer
LocalFree(pszFileBuff);

//close the file
CloseHandle(hAsciiFile);

//it's all good
return TRUE;

The GreatWriting example also allows the user to save the two files as Unicode. We call the function OpenSaveFile() to initialize an OPENFIELNAME structure.

case IDC_SAVE_UNICODE:
//get a filename for writing--
//prompt if this is an overwrite
lpof = OpenSaveFile( hDlg, iAuthor );

There are only a couple of differences between this open file routine and the one we examined in the FileOpen example, so we’ll limit our dissection of OpenSaveFile() to these. Notice that we use a different string for pszOpenFilter in this dialog. This time, the pattern that controls the display of files in the dialog’s list is “*.txt”, which will allow only filenames with the extension of “.txt”.

LPOPENFILENAME OpenSaveFile(HWND hDlg, int iAuthor)
{
    TCHAR*  pszFileName;
    const LPTSTR pszOpenFilter = TEXT ("Text Documents
                                       (*.txt)*.txt");
    LPOPENFILENAME lpof;

We’ll skip over the bit of code where we allocate space to store the filename. The pointer to this buffer, which was returned by LocalAlloc(), is pszFileName. Notice that we initialize the buffer with a default save file name this time. We use wcscpy() for this initialization because the filename we pass to CreateFile() must be a Unicode string.

// init the  filename in the ctl.
if( iAuthor == IDM_GEISEL )
{
    wcscpy( pszFileName, TEXT( "geisel"));
}
else
{
    wcscpy( pszFileName, TEXT( "nietzche"));
}

Also, notice that this time we initialize the lpstrDefExt member of the OPENFILENAME structure. We have exercised fairly rigid control over how and where the user can save this file, because we’ve limited the display of filenames to those with text extensions, set the initial directory, provided a default save file name, and specified a default save file extension.

// Initialize File Open structure.
lpof->lStructSize = sizeof (OPENFILENAME);
lpof->hwndOwner   = hDlg;
lpof->lpstrFile   = pszFileName;
lpof->nMaxFile    = MAX_PATH;
lpof->lpstrFilter = pszOpenFilter;
lpof->lpstrDefExt = TEXT("txt");

Finally, notice that we use a different function to invoke the File Save common dialog.

if( !GetSaveFileName (lpof))
{
       lpof = NULL;
}

Now, back to GreatWritersDlgProc(), where we were writing out a Unicode version of the ASCII file. We open the file using the access specifier GENERIC_WRITE, set the sharing mode to FILE_SHARE_WRITE, and use the file creation flag CREATE_ALWAYS. This last parameter ensures that if the file exists, it will be reinitialized—its contents will be completely overwritten and its attributes will be reset.

//open the file
hUnicodeFile = CreateFile( lpof->lpstrFile,
               GENERIC_WRITE ,
               FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
               FILE_ATTRIBUTE_NORMAL, NULL);

Now, we get the length in characters of the content of the edit control, allocate a buffer sized for the appropriate number of Unicode characters, and retrieve the text from the control.

//get length in characters of the edit control content
hWndEdit = GetDlgItem( hDlg, IDC_WRITING_SAMPLE );
dwFileSize = SendMessage( hWndEdit, WM_GETTEXTLENGTH, 0,0 );

//allocate a buffer
pszUnicodeBuff = (TCHAR*)LocalAlloc( LPTR, 
                         (dwFileSize + 1) * sizeof(TCHAR));
//Copy the text from the control
SendMessage( hWndEdit, WM_GETTEXT, dwFileSize + 1,
             (LPARAM)pszUnicodeBuff );

Finally, we call WriteFile() to write a byte string to the file, and the number of bytes successfully written are returned in the dwBytesWritten parameter.

//write to the file
WriteFile( hUnicodeFile, (LPCVOID)pszUnicodeBuff,
          ( dwFileSize + 1 ) * sizeof(TCHAR),
          &dwBytesWritten, NULL);

Now, we clean up by releasing allocations and closing file handles.

//release the text buffer
LocalFree( pszUnicodeBuff );
//release the file name buffer stored in the of struct
LocalFree(lpof->lpstrFile );
//release the of struct
LocalFree(lpof );
//close the file
CloseHandle(hUnicodeFile );
break;

The main difficulty of Unicode is not that it’s arcane or mysterious in any way—it’s that using Unicode requires you to unlearn some of the earliest and most closely held ideas you assimilated as a C (or C++) programmer. A Unicode datum consists of a byte range, so you can’t treat its name as its address. For example, incrementing a PBYTE type won’t correctly iterate a wide character string. It’s critical that you be consistent in your use of pointer types and pointer arithmetic operations.

Porting Tip: Remember these things about Unicode strings:

  • To calculate the size of an allocation for a Unicode string, multiply the total character count by sizeof(TCHAR)
  • Make sure you are consistent in typing pointers to Unicode strings if you use the increment operator to walk a string
  • Don’t try to treat the name of a Unicode string as its address. Rather, initialize a correctly typed pointer
  • Use string handling functions that begin with “wcs” to manipulate Unicode strings
  • Use mbstowcs() to translate between char and TCHAR types

Figure 8-2 Some Useful Unicode Text Manipulation Functions

Unicode Character Handling Function Behavior
mbstowcs Convert from single or multibyte characters to Unicode characters
wcstombs Convert from Unicode to multibyte characters
wcscat Catenates two strings
wcschr Find the first occurrence of a character
wcscmp Lexically compare two strings
wcscpy Copy a source string to a destination string
wcscspn  
wcslen Return the length of a string in characters
wcsncat Catenate n characters of a source string to a destination string
wcsncmp Compare n characters of a source string to a destination string
wcsncpy Copy n characters of a source string to a destination string
wcspbrk Scan a string for the first occurrence of any of a group of characters
wcsrchr Find the first occurrence of a character, scanning backwards thru the string
wcsstr Find a substring
wcstok Tokenize a string, replacing the token with a null
_wcsdup Duplicate a string
_wcsicmp Lexically compare without regard to case
_wcsnicmp Lexically compare n characters without regard to case
_wcslwr Convert to lower case
_wcsupr Convert to upper case

Looking Ahead

In our next lesson, we’ll explore the use of memory mapped files on CE devices.

About the Author

Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, and entry time data validation.

In addition to writing for Developer.com, she has written several books, including Making Win 32 Applications Mobile.

# # #

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories