Win32文件系统编程
一丶了解什么是文件系统
文件系统是抽象的.是windows在软件层面提供的一层虚拟的数据结构.
文件系统分为NTFS 跟 FAT32. 具体看看两者的区别吧.
磁盘分区容量.
单个文件容量. 意思就是一个文件可以是多大的. NTFS 是可以4G以上的大文件. FAT32则不可以.
EFS加密. 这个加密主要针对当前用户的(例如Admins 管理员账户) 具体可以 点击一个文件. 文件->属性 -> 高级 -> 加密保护文件内容.
如果在当前用户则不会有什么结果. 但是如果换了用户访问.则不可以访问这个加密文件了.
加密后的文件.
文件颜色都会改变.
磁盘配额 意思就是可以限制别的用户访问这个硬盘多少G内存.
具体设置 xp下 盘符属性-> 配额
关于上面的讲解我们只需要了解即可.不深究.具体的的是学习API. API为我们封装好了.我们并不用关心NTFS 或者FAT32
二丶Windows提供的操作 "文件" 的 API
标题中文件为什么添加了引号. 意思是不光可以操作文件. 在windows系统中.一切东西都虚拟为了文件. 例如管道 等等.. 都可以使用这些API.
api具体介绍
1.了解卷 文件 跟目录的关系. 卷API
卷指的就是我们的的逻辑硬盘. 例如C盘.
目录则是C盘里面的文件夹. 文件夹里面可能还是以文件夹. 也可能是文件.
卷操作API 很简单. 常用的就四个.
DWORD GetLogIcalDrives() 获取卷 返回值是10进制.我们需要转化成16进制.然后转换成二进制查看.每一位为1代表有这个磁盘.否则则没有
GetLogIcalDriveStrings(buffsize,buf) 获取一个卷的盘符的字符串
GetDriveType(""目录名称") 获取卷的类型
GetVolumeInformation() 获取卷的类型
如以下代码例子:
// A.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <Windows.h> int main()
{
//1.获取磁盘逻辑驱动卷
DWORD dwGetDrives = GetLogicalDrives();
/*
例如我的返回值是 252
转化为16进制 FC
转化为二进制 11111100 代表我们有六个磁盘.
CDEFGH 而我的恰好就是 CDEFGH 盘.
*/ //2.获取磁盘卷字符串.
TCHAR wszBuf[] = { NULL };
DWORD dwSize = sizeof(TCHAR) * ;
GetLogicalDriveStringsW(dwSize, wszBuf);
/*
逻辑驱动器返回后会存在wszBuf里面. 自己做分割即可.
例如 C:\ B:\
*/ //3.根据指定盘符获取它的类型. 可以移除的还是不可以移除的.
DWORD dwDriveType = GetDriveTypeW(TEXT("C:\\")); //具体返回值查询MSDN 注意从0开始 //4.获取卷的详细信息.
DWORD dwVolumneSerial = ; //驱动卷的序列号(不是硬盘序列号)
DWORD dwFileMaxLen = ; //系统允许的最大文件名的长度
DWORD dwFileSystem = ; //文件系统标识.
TCHAR dwFileSystemBuffer[] = { }; //文件操作系统的名称 TCHAR szVolName[] = { }; //返回的卷的别名
GetVolumeInformationW(
TEXT("c:\\"), // IN参数 你要查看那个卷的信息
szVolName, // OUT参数. 查询到的卷的别名会给你.例如你的别名是C
sizeof(TCHAR) * , // IN参数. 上面缓冲区的大小.
&dwVolumneSerial, // OUT 驱动卷的序列号
&dwFileMaxLen, // OUT 写文件读文件等等文件名最大可以是多大.
&dwFileSystem, // OUT 文件操作系统标识.有多中宏组合,具体可以查询MSDN. 标识你这个文件是
dwFileSystemBuffer, //你当前系统是 NTFS 还是FAT32
sizeof(TCHAR) * //上面缓冲区的大小.
); return ;
}
2.文件夹(目录) 操作的相关API
CreateDirectory();//创建目录
RemoveDirectory();//删除目录
MoveFile(); //修改目录名称.
GetCurrentDirectory(); //获取进程当前目录
SetCurrentDirectory(); //设置进程当前目录.
具体代码例子:
main函数调用即可.
void GetDirectoryApi()
{
CreateDirectory(TEXT("D:\\123"),NULL);//创建目录
MoveFile(TEXT("D:\\123"), TEXT("D:\\456")); //修改目录名称.
RemoveDirectory(TEXT("D:\\456"));//删除目录
TCHAR szCurrentDirectoryBuffer[] = { };
DWORD dwBuffsize = sizeof(TCHAR) * ; GetCurrentDirectory(dwBuffsize, szCurrentDirectoryBuffer); //获取当前目录
SetCurrentDirectory(szCurrentDirectoryBuffer); //设置当前目录.
}
3.文件操作相关API
CreateFile( ) 创建文件
DeleteFile(); 删除文件
CloseHandle(); 关闭文件句柄
GetFileSize(); 获取文件大小.
ReadFile(); 读文件
WriteFile(); 写文件
CopyFile(); 拷贝文件
具体看如下代码详解参数意义.
void OptFileApi()
{
//1.创建文件
HANDLE hFile = CreateFile(
TEXT("D:\\123.txt"), //你要创建的文件名
GENERIC_READ | GENERIC_WRITE, // 创建的这个文件只读模式创建时只写模式创建还是读写都可以.如果只读则不可以写.
, // 文件共享模式. 意思就是你这个文件创建完毕之后.当前读写只能有一个人在用,其他人不能操作. 为0就是排他. 或者说你可以设置为其他人可以读.
NULL, // 每个内核对象都有的SD安全属性
OPEN_EXISTING, //创建文件的信息. 你这个文件是文件不存在就创建 还是打开已经存在的. 还是总是创建新的.
FILE_ATTRIBUTE_NORMAL, //创建的文件属性. 意思就是我创建的这个文件是隐藏文件啊 还是别的文件. 反正就是属性.
NULL
);
//2.获取文件大小
DWORD dwLowSize = ;
DWORD dwHighSize = ;
dwLowSize = GetFileSize(hFile, &dwHighSize); //如果是32位系统.返回值存储了大小.如果是64位系统.则高32位也会存储.
//3.写文件
TCHAR szBuffer[] =TEXT("HelloFile");
DWORD dwSize = sizeof(TCHAR) * ;
DWORD OutSize = ;
WriteFile(hFile, //往哪个文件中写
szBuffer, //写入的Buffer数据
dwSize, //写入的大小
&OutSize, //实际写入的大小.操作系统返回给你
NULL); //异步操作不需要
//4.读文件
//4.1设置File读取位置
SetFilePointer(hFile, //读取那个文件
, //低32位的偏移.具体偏移 就是从文件开始位置 + 偏移位置读取. 如果是64位那么第三个参数也要使用.
,
FILE_BEGIN //偏移起始位置. 文件开始 文件结束. 还是文件中间
);
ReadFile(hFile, //读哪个
szBuffer, //读入的数据放到Buffer
dwSize, //Buffer大小
&OutSize, //实际读入的大小.操作系统返回给你
NULL); //异步操作不需要 CloseHandle(hFile); //5.拷贝文件
CopyFile(
TEXT("D:\\123.txt"), //源文件
TEXT("E:\\123.txt"), //目的文件
TRUE //是否覆盖目的文件
);
//.关闭文件句柄 //6.删除文件
DeleteFile(TEXT("d:\\123.txt")); }
三丶内存映射文件
我们上几篇博客讲解了CreateFileMaping 创建物理内存页. 那么我们可以把文件跟物理页绑定.
例如下图:
其实文件映射到物理内存了.那么我们直接操作内存就可以. 想相当于操作文件.
具体步骤.
1.创建文件.如果文件已经存在.则打开文件.获取文件句柄.
2.申请共享内存.使用CreateFileMapping. 将文件句柄传入.
3,将物理内存映射到线性地址(虚拟内存)中.使用 MapViewOfFile.
4.操作的虚拟地址就是文件内容了.
具体看如下代码.
void OptFileApi()
{
//1.创建文件
HANDLE hFile = CreateFile(
TEXT("D:\\calc.exe"), //你要创建的文件名
GENERIC_READ | GENERIC_WRITE, // 创建的这个文件只读模式创建时只写模式创建还是读写都可以.如果只读则不可以写.
0, // 文件共享模式. 意思就是你这个文件创建完毕之后.当前读写只能有一个人在用,其他人不能操作. 为0就是排他. 或者说你可以设置为其他人可以读.
NULL, // 每个内核对象都有的SD安全属性
OPEN_EXISTING, //创建文件的信息. 你这个文件是文件不存在就创建 还是打开已经存在的. 还是总是创建新的.
FILE_ATTRIBUTE_NORMAL, //创建的文件属性. 意思就是我创建的这个文件是隐藏文件啊 还是别的文件. 反正就是属性.
NULL
); //2.创建内存物理页.跟文件挂靠
HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE,0, 0X1000, NULL); //读写的方式映射.不需要其它进程使用. //3.映射到虚拟内存位置.
LPVOID szBuffer = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); //直接操作内存即可.
printf("%x",*(PDWORD)szBuffer); //打印前四个字节.
*(PDWORD)szBuffer = 0xFFFF0000; //修改文件前四个字节.
/*
此API可以 强制更新缓存.
BOOL FlushViewOfFile( LPCVOID lpBaseAddress, // starting address
SIZE_T dwNumberOfBytesToFlush // number of bytes in range);
*/
//4.取消映射
UnmapViewOfFile(szBuffer);
CloseHandle(hFileMap);
CloseHandle(hFile); }
如果映射到虚拟内存中.也就是调用完毕 MapViewOfFile的时候.其缓冲区就是文件的起始位置. 可以直接指针修改了.
例如我们的Calc计算器.头四个字节已经被我们修改成了 0xFFFF了.
很简单. 多动手做即可.
四丶内存映射文件之多进程共享.
如下图所示:
A进程映射物理内存.并且映射文件. B进程使用这块物理内存其实也是操作文件.
只不过附带了一个文件.修改修改物理内存的时候变成修改文件了.具体代码不在贴了.
主需要申请共享内存的时候给定一个名字. 那么双进程就可以使用了.
完整代码
BOOL Characteristic(LPCTSTR lpFileName)
{
//打开文件,读取头八个字节.
HANDLE hFile = CreateFile(
lpFileName,
GENERIC_READ | GENERIC_WRITE,
,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
); if (INVALID_HANDLE_VALUE == hFile)
return FALSE; //读取文件特征.这里判断是否是PE.读取节个数.以及节开头的标志. HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, , 0X1000, NULL);
if (NULL == hFileMap)
return FALSE; LPVOID szBuffer = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, , , );
if (NULL == szBuffer)
return FALSE; UnmapViewOfFile(szBuffer);
CloseHandle(hFileMap);
CloseHandle(hFile);
return ;
}