Windows提供了一类API来读、写和管理磁盘文件。
使用API函数读写文件时,首先要使用CreateFile函数创建文件对象,调用成功会返回文件句柄。我们以此句柄为参数调用ReadFile和WriteFile函数,进行实际的读写操作;最后调用CloseHandle函数关闭不再使用的文件对象句柄。
1)打开和关闭文件:
CreateFile是一个功能强大的函数,Windows下的底层设备几乎都由它打开。它可以创建或打开文件、目录、物理磁盘、控制台缓冲区、油槽或管道等:
HANDLE CreateFile(
LPCTSTR lpFileName, //要创建或打开的对象的名称
DWORD dwDesiredAccess, //文件的存取方式
DWORD dwShareMode, //共享属性
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //安全属性
DWORD dwCreationDisposition, //文件存在或不存在时系统采取的行动
DWORD dwFlagsAndAttributes, //新文件的属性
HANDLE hTemplateFile //一个文件模板的句柄
);
lpFileName—如果是打开文件,直接指定文件名称即可;如果操作对象是第一个串口,则指定“COM1”为文件名,然后就可以像操作文件一样操作串口了;如果要打开本地电脑上的一个服务,要以//./服务名称为文件名,其中”.”代表本地机器;如果要打开网络中其他主机的文件,则以//主机名/共享目录名/文件名为文件名。
dwDesiredAccess—GENERIC_READ(只读)、GENERIC_WRITE(只写)、两者组合。
dwShareMode—表示文件打开后是否允许其他代码以某种方式再次打开这个文件,可以是下面的组合:
0—不允许文件再被打开。C语言中的fopen函数就是这种方式;
FILE_SHARE_DELETE—允许其他程序代码删除文件;
FILE_SHARE_READ—允许其他程序代码以读方式打开文件;
FILE_SHARE_WRITE—允许其他程序代码以写方式打开文件;
dwCreationDisposition—指定当文件已存在或不存在时系统采取的动作:
CREATE_ALWAYS—创建新文件,若文件存在,则覆盖它,清除存在的属性;
CREATE_NEW—创建新文件。若文件存在,函数执行失败;
OPEN_ALWAYS—如果文件存在则打开它,不存在则创建新文件;
OPEN_EXISTING—打开存在的文件。若文件不存在,函数执行失败;
TRUNCATE_EXISTING—打开文件并将文件截断为0,当文件不存在时函数执行失败。
dwFlagsAndAttributes—指定新建文件属性和标志,可以是以下各项的组合:
FILE_ATTRIBUTE_ARCHIVE—标记归档属性;
FILE_ATTRIBUTE_HIDDEN—标记隐藏属性;
FILE_ATTRIBUTE_READONLY—标记只读属性;
FILE_ATTRIBUTE_SYSTEM—标记系统属性;
FILE_ATTRIBUTE_TEMPORARY—临时文件,操作系统会尽量把所有文件的内容保持在内存中以加快存取速度。使用完后要尽快将它删除。
FILE_FLAG_DELETE_ON_CLOSE—文件关闭后系统立即自动删除它;
FILE_FLAG_OVERLAPPED—使用异步读写文件的方式;
FILE_FLAG_WRITE_THROUGH—系统不会对文件使用缓冲,文件的任何改变都会被系统立即写入硬盘。
hTemplateFile—指定一个文件模板句柄。系统会复制文件模板的所有属性到当前创建的文件中。
打开或创建文件成功时,函数返回文件句柄,失败时返回INVALID_HANDLE_VALUE。
2)移动文件指针:
SetFilePointer函数:系统为每个打开的文件维护一个文件指针,指定对文件的下一个读写操作从什么位置开始。随着数据的读出或写入,文件指针也随之移动。当文件刚被打开时,文件指针处于文件的头部。有时候需要随机读取文件内容,这就需要先调整文件指针:
DWORD SetFilePointer(
HANDLE hFile, //文件句柄
LONG lDistanceToMove, //要移动的距离
PLONG lpDistanceToMoveHigh, //移动距离的高32位,一般位置为NULL
DWORD dwMoveMethod //移动的模式
);
dwMoveMethod参数指明从什么地方开始移动:
FILE_BEGIN—开始移动的位置为0,即从文件头部开始移动;
FILE_CURRENT—开始移动位置是文件指针的当前值;
FILE_END—开始移动位置是文件的结尾,即从文件尾开始移动。
文件指针也可以移动到所有数据后面,比如现在文件长度是200KB,但可以成功地将文件指针移动到1000KB的位置。这样做可以达到扩展文件长度的目的。
SetEndOfFile函数就可以达到截断或者扩展文件的功能。该函数移动指定文件的结束标志(EOF)到文件指针指向的位置。如果文件扩展,旧的EOF位置和新的EOF位置间的内容是未定义的:
BOOL SetEndOfFile(HANDLE hFile);
截断或者扩展文件时,要首先调用SetFilePointer移动文件指针,然后再调用SetFilePointer函数设置新的文件指针位置为EOF。
3)读写文件:
ReadFile和WriteFile函数既可以同步读写文件,也可以异步读写文件;而函数ReadFileEx和WriteFileEx只能异步读写文件。
BOOL ReadFile(
HANDLE hFile, //文件句柄
LPVOID lpBuffer, //指向一个缓冲区,函数会将读出的数据返回到这里
DWORD nNumberOfBytesToRead, //要求读入的字节数
LPDWORD lpNumberOfBytesRead, //指向一个DWORD类型的变量,用于返回实际读入的字节数
LPOVERLAPPED lpOverlapped //一般设为NULL
);
BOOL WriteFile(
HANDLE hFile, //文件句柄
LPVOID lpBuffer, //指向一个缓冲区,包含了要写入文件的数据
DWORD nNumberOfBytesToWrite, //要求写入的字节数
LPDWORD lpNumberOfBytesWritten, //指向一个DWORD类型的变量,用于返回实际写入的字节数
LPOVERLAPPED lpOverlapped //一般设为NULL
);
使用WriteFile写文件时,写入的数据通常被Windows暂存在内部的高速缓存中,等合适的时候再一并写入磁盘。可以使用FlushFileBuffers函数来强制系统清空缓冲区:
BOOL FlushFileBuffers(HANDLE hFile);
4)锁定文件:
当我们对文件数据的一致性要求较高时,为了防止程序在写入过程中其他进程刚好在读取写入区域的数据,可以对已打开文件的某个部分加锁,加锁和解锁的函数分别是LockFile和UnlockFile:
BOOL LockFile(
HANDLE hFile, //文件句柄
DWORD dwFileOffsetLow, //加锁的开始位置
DWORD dwFileOffsetHigh,
DWORD nNumberOfBytesToLockLow, //加锁区域的大小
DWORD nNumberOfBytesToLockHigh
);
BOOL UnlockFile(
HANDLE hFile, //文件句柄
DWORD dwFileOffsetLow, //解锁的开始位置
DWORD dwFileOffsetHigh,
DWORD nNumberOfBytesToLockLow, //解锁区域的大小
DWORD nNumberOfBytesToLockHigh
);
5)获取文件类型:
Windows下许多对象都称为文件,如果想知道一个文件句柄到底是什么对象,可是使用函数GetFileType:
DWORD GetFileType(HANDLE hFile);
返回值如下:
FILE_TYPE_CHAR---指定文件时字符文件,通常是LPT设备或控制台;
FILE_TYPE_DISK---指定文件时磁盘文件;
FILE_TYPE_PIPE---指定文件是套接字,一个命名的或未命名的管道;
FILE_TYPE_UNKNOWN—不能识别指定文件,或者函数调用失败。
获取文件大小:
如果确定操作的对象时磁盘文件,可以使用GetFileSize函数取得这个文件的长度:
DWORD GetFileSize(
HANDLE hFile, //文件句柄
LPDWORD lpFileSizeHigh //用于返回文件长度的高字节,可以指定这个参数为NULL
);
函数执行成功将返回文件大小的低双字,如果lpFileSizeHigh不为NULL,函数将文件大小的高双字放入它指向的DWORD变量中。
如果返回值是INVALID_FILE_SIZE,应用程序必须调用GetLastError来确定函数调用是否成功。因为当lpFileSizeHigh不为NULL或者文件大小为oxffffffff时,函数虽然调用成功,但依然会返回INVALID_FILE_SIZE,这种情况下,GetLastError会返回NO_ERROR来响应成功。
6)获取文件属性:
如果要查看文件或者目录的属性,可以使用GetFileAttributes函数,它返回一系列FAT风格的属性信息:
DWORD GetFileAttributes(LPCTSTR lpFileName); //lpFileName指定了文件或者目录的名称
返回值是下列各项的组合:
FILE_ATTRIBUTE_ARCHIVE
FILE_ATTRIBUTE_COMPRESSED
FILE_ATTRIBUTE_DIRECTORY
FILE_ATTRIBUTE_HIDDEN
FILE_ATTRIBUTE_NORMAL
FILE_ATTRIBUTE_READONLY
FILE_ATTRIBUTE_SYSTEM
FILE_ATTRIBUTE_TEMPORARY
这些属性对目录同样适用,函数执行失败返回INVALID_FILE_ATTRIBUTES。
设置文件属性的函数是SetFileAttributes:
BOOL SetFileAttributes(
LPCTSTR lpFileName, //目标文件名称
DWORD dwFileAttributes //要设置的属性值
);
下面是快速检查某个文件或目录是否存在的自定义函数:
BOOL FileExists(LPCTSTR lpszFileName, BOOL bIsDirCheck)
{
//取得文件的属性
DWORD dwAttributes = GetFileAttributes(lpszFileName);
if(dwAttributes == INVALID_FILE_ATTRIBUTES)
{
return FALSE;
}
if((dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
{
if(bIsDirCheck) //检查目录
return TRUE;
else
return FALSE;
}
else
{
if(!bIsDirCheck) //检查文件
return TRUE;
else
return FALSE;
}
}
7)拷贝文件:
函数CopyFile和CopyFileEx的作用是复制一个存在的文件到一个新文件中:
BOOL CopyFile(
LPCTSTR lpExistingFileName, //指定已存在的文件的名称
LPCTSTR lpNewFileName, //指定新文件的名称
BOOL bFailIfExists //如果指定的新文件存在是否按出错处理
);
CopyFileEx函数的附加功能是允许指定一个回调函数,在拷贝过程中,函数每拷贝完一部分数据,就会调用回调函数。用户在回调函数中可以指定是否停止拷贝,还可以显示进度条来指示拷贝的进度。
8)删除文件:
BOOL DeleteFile(LPCTSTR lpFileName);
用DeleteFile函数删除的文件不会被放进回收站,它们将永远丢失。
自定义函数RecursiveDelete用于删除指定目录下所有的文件和子目录:
void RecursiveDelete(CString szPath)
{
CFileFind ff; //MFC将查找文件的API封装到了CFileFind类中
CString strPath = szPath;
//说明要查找此目录下的所有文件
if(strPath.Right(1) != "//")
strPath += "//";
strPath += "*.*";
BOOL bRet;
if(ff.FindFile(strPath))
{
do
{
bRet = ff.FindNextFile();
if(ff.IsDots()) //目录为"."或者".."
continue;
strPath = ff.GetFilePath();
if(!ff.IsDirectory()) //是文件了
{
//删除此文件
::SetFileAttributes(strPath, FILE_ATTRIBUTE_NORMAL);
::DeleteFile(strPath);
}
else //还是目录
{
//递归调用
RecursiveDelete(strPath);
//删除此目录(RemoveDirectory只能删除空目录)
::SetFileAttributes(strPath, FILE_ATTRIBUTE_NORMAL);
::RemoveDirectory(strPath);
}
}while(bRet);
}
}
9)移动文件:
函数MoveFile和MoveFileEx的功能是移动一个存在的文件或目录:
BOOL MoveFile(
LPCTSTR lpExistingFileName, //存在的文件或目录
LPCTSTR lpNewFileName //新的文件或目录
);
当需要指定如何移动文件时,使用MoveFileEx:
BOOL MoveFileEx(
LPCTSTR lpExistingFileName, //存在的文件或目录
LPCTSTR lpNewFileName, //新的文件或目录
DWORD dwFlags
);
dwFlags参数是下列值的组合:
MOVEFILE_DELAY_UNTIL_REBOOT—函数并不立即执行,而是在操作系统下一次重启时才移动此文件。在AUTOCHK执行之后,系统立即移动文件,这是在创建任何分页文件之前进行的。因此,这个值能够使函数删除上一次运行时使用的分页文件。
MOVEFILE_REPLACE_EXISTING—如果目标文件已存在的话,就将它替换掉;
MOVEFILE_WRITE_THROUGH—直到文件实际从磁盘移除之后函数才返回。