Windows Programming/Input-Output
From Wikibooks, the open-content textbooks collection
Many of the previous chapters have attempted to shed some light on the Windows graphical interface, but this chapter is going to start a detour into the inner-workings of the Windows operating system foundations. In this chapter, we are going to talk about Input and Output routines. This includes (but is not limited to) File I/O, Console I/O, and even device I/O.
Contents |
[edit] File API
Files, like everything else in a windows platform, are managed by handles. When you want to read a file or write to one, you must first open a handle to that file. Once the handle is open, you may use the handle in read/write operations. In fact, this is the same with all I/O, including console I/O and device I/O: you must open a handle for reading/writing, and you must use the handle to perform your operations.
[edit] CreateFile
We will start with a function that we will see frequently in this chapter: CreateFile. CreateFile is the generic function used to open I/O handles in your system. Even though the name doesn't indicated it, CreateFile is used to open Console Handles and Device Handles as well. As the MSDN documentation says:
The CreateFile function creates or opens a file, file stream, directory, physical disk, volume, console buffer, tape drive, communications resource, mailslot, or named pipe. The function returns a handle that can be used to access an object.
Now, this is a powerful function, and with the power comes a certain amount of difficulty in using the function. Needless to say, CreateFile is a little more involved than the standard C STDLIB fopen.
HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
As can be guessed, the "lpFileName" parameter is the name of the file to be opened. "dwDesiredAccess" specifies the desired access permissions for the file handle. In the most basic sense, for a file, this parameter can specify a read operation, a write operation, or an execute operation. However, don't be fooled, there are many many different options that can be used here, for different applications. The most common operations are GENERIC_READ, GENERIC_WRITE, and GENERIC_EXECUTE. These can be bitwise-OR'd to have read+write access, if needed.
File handles can be optionally shared or locked. A shared file can be simultaneously opened and accessed by other processes. If a file is not shared, then other programs attempting to access the file will fail. The "dwShareMode" specifies whether or not the file can be accessed by other applications. Setting dwShareMode to zero means that the file access cannot be shared, and other applications attempting to access the file, while the file handle is open, will fail. Other common values are FILE_SHARE_READ and FILE_SHARE_WRITE which allow other programs to open read handles and write handles, respectfully.
The lpSecurityAttributes is a pointer to a SECURITY_ATTRIBUTES structure. This structure can help to secure the file against unwanted accesses. We will discuss security attributes in a later chapter. For now, you can always set this field to NULL.
The dwCreationDisposition member would be better named "dwCreateMode" or something similar. This bit flag allows you to determine how the file is to be opened, according to different flag values:
- CREATE_ALWAYS
- Always creates a new file. If the file exists already, it will be deleted, and overwritten. If the file does not exist, it is created.
- CREATE_NEW
- If the file exists, the function fails. Otherwise, creates a new file.
- OPEN_ALWAYS
- Opens the file, without erasing the contents, if the file exists. Creates a new file if the file does not exist.
- OPEN_EXISTING
- Opens the file, without erasing the contents, only if the file exists already. If the file does not exist, the function fails.
- TRUNCATE_EXISTING
- Opens the file, only if the file exists. When the file is opened, all the contents are deleted, and the file is set to 0 bytes long. If the file does not exist, the function fails. When opening with TRUNCATE_EXISTING, you must specify a GENERIC_WRITE flag as the access mode, or the function will fail.
The dwFileAttributes member specifies a series of flags for controlling File I/O. If the CreateFile function is being used to create something that isn't a File handle, this parameter is not used and may be set to 0. For accessing a normal file, the flag FILE_ATTRIBUTE_NORMAL should be used. However, there are also options for FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_ARCHIVE, etc.
Finally, the hTemplateFile member can be specified if you want the new file handle to mimic the properties of an existing file handle. This can be set to NULL if not used.
[edit] ReadFile WriteFile
Once a file handle is opened, ideally we would like to interact with the specified file. We can do this most directly by using the ReadFile and WriteFile functions. Both of them take similar parameters:
BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
In both, the hFile parameter is the handle to the file that we obtained with CreateFile. The lpOverlapped parameter is used only for a special I/O mode known as "Overlapped I/O Mode", which we will discuss later. For simple I/O, the lpOverlapped parameter can be set to NULL.
In ReadFile, the lpBuffer is a pointer to a generic buffer to receive the data. This data may not be character data, so we don't call it a LPSTR type. "nNumberofBytesToRead" is the number of bytes that should be read, and "lpNumberOfBytesRead" is the actual number of bytes that were read. If lpNumberOfBytesRead is zero, the file has no more data in it.
In WriteFile, the lpBuffer parameter points to the data that should be written into the file. Again, it isn't specifcally character data. nNumberOfBytesToWrite is the maximum number of bytes to write, and the lpNumberOfBytesWritten returns the number of bytes that were actually written to the file.
[edit] CloseHandle
When you are done with a file handle, you should close it with the CloseHandle function. CloseHandle only takes one parameter, the file handle you wish to close. If you do not close your handle, Windows will automatically close the handle when the program closes. However, it is a more expensive operation for Windows to do it for you, and can waste time on your system. It is a good idea to always explicitly close all your handles before you exit your program.
Failure to close a handle is known as "handle leaks", and are a common form of memory leakage that can cause your program, and your entire system, to lose resources and operate more slowly. The handle itself occupies only 32-bits of information, but internally the kernal maintains a large amount of data and storage for every handle. Failure to close a handle means that the kernal must maintain all the associated information about the handle. It also costs the kernel additional time and resources to check through all the old unused handles when it is looking for information about a current handle.
[edit] Memory-Mapped Files
Memory-Mapped files provides a mechanism to read and write to a file using regular pointers and array constructs. Instead of reading from the file using ReadFile, you can read from the file using a memory pointer. The system does this by reading in the file to a memory page, and then writing changes to that page onto the physical disk. There is a certain amount of additional overhead to read the file into memory at first, and to write it back after the mapping is completed. However, if there are many accesses to the file, it can be much more convenient in the long run.
[edit] Overlapped I/O
"Overlapped" I/O is the term Microsoft uses to describe asynchronous I/O. When you want to do I/O, either to a file or to an external device, you have two options:
- Synchronous (non-overlapped)
- You request the I/O from the system, and wait till the I/O has completed. The program will stop running until the I/O has completed.
- Asynchronous (overlapped)
- You send a request to the system, and the system completes that request in parallel with your program. Your program can continue to do processing work, and the system will automatically send notification when your request has been completed.
Synchronous I/O is much easier to use, and is much more straight forward. In synchronous I/O, things happen sequentially, and when the I/O function has returned, you know that the transaction is complete. However I/O is typically much slower then any other operation in your program, and waiting on a slow file read, or a slow communications port can waste lots of valuable time. In addition, if your program is waiting for a slow I/O request, the graphical interface will appear to hang and be non-responsive, which can annoy the user.
Asynchronous I/O is more complicated to use: It requires the use of the OVERLAPPED structure, and the creation of a handler function that will be called automatically by the system when the I/O is complete. However, the benefits are obvious in the efficiency of the method. Your program can request multiple transactions without having to wait for any of them to complete, and it can also perform other tasks while the system is performing the required task. This means that the programs will appear more responsive to the user, and that you can spend more time on data processing, and less time waiting for data.
[edit] Console API
[edit] Allocating a Console
[edit] Getting a Console Handle
[edit] High Level I/O
[edit] Low Level I/O
[edit] Colors and Features
[edit] Device IO API
Interaction between a program and a device driver can be complicated. However, there are a few standard device drivers that may be used to access standard ports and hardware. In most instances, interacting with a port or a piece of hardware is as easy as opening a handle to that device, and then reading or writing to it like a file. In most instances, these ports and devices can be opened using the CreateFile function, by calling the name of the device instead of the name of a file.
[edit] Getting a Device Handle
[edit] Device IO Functions
[edit] Warnings about Device IO
[edit] Completion Ports
This page of the Windows Programming book is a stub. You can help by expanding it.