win32核心编程00:文件操作

    xiaoxiao2021-04-15  337

    目录

    0x00.文件基本操作

    0x01写入:

    0x02获取文件大小:

    0x03设置文件内容指针:

    0x04拷贝文件:

    0x05移动文件:

    0x06关闭文件句柄:

    0x07.文件夹操作

    windows操作系统的文件系统:每个文件夹下都有两个默认隐藏的文件夹:

    0x08实现文件夹遍历:

    0x09.文件映射



    0x00.文件基本操作

      C文件操作:FILE* fopen fwrite fread fseek ftell fclose

       C++文件操作:fstream文件流对象 file.open file.wirte file.read file.seekg file.close

      windows文件操作:不太常用,因为用C和C++的文件操作就可以了,前两者是跨平台的,在Linux上也可以使用。

    用HANDLE句柄来标识文件

    创建打开文件用 CreateFile

    写文件 WriteFile

    读文件 ReadFile

    设置文件内容指针 SetFilePointer

    拷贝文件 CopyFile()

    获取文件大小 GetFileSize

    移动文件 MoveFile

    删除文件 DeleteFile

    关闭文件 CloseHandle

    当我们在写操作系统内核的代码 需要进行文件操作时,没得C和C++编译器供我们使用,只能用这些函数,所以必须掌握。

    当一个函数你不知道怎么用的时候,vs编译器按F1或者F12查阅相关文档即可。

    HANDLE CreateFileA(//返回创建或者打开后的文件句柄,如果失败,返回-1 LPCSTR lpFileName,//文件名 DWORD dwDesiredAccess,//访问方式 GENERIC_ALL 可读可写,GENERIC。。 只读 ,GENERIC。。只写 DWORD dwShareMode,//共享方式,指文件对于操作系统的很多用户的共享方式 LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全属性 SECURITY_开头 DWORD dwCreationDisposition,//创建(CREATE_AlWAYS) 还是 打开(OPEN_ALWAYS) DWORD dwFlagsAndAttributes,//方式和属性 HANDLE hTemplateFile//模板文件句柄 );

    CreateFile不仅仅可以用来打开或者创建一个文件,它可以用来打开或者创建一个设备(凡是可以和windows通信的都叫设备,包括文件(读写)、文件夹、逻辑磁盘驱动器(读写),物理磁盘驱动器(读写)、控制台(输入输出)等等)

    windows为CreateFile打开的文件 创建一个文件内核对象,用这个内核对象来管理这个文件。多次调用CreateFile打开同一个文件,虽然是同一个文件,但是windows每次都会创建一个新的内核对象来管理它。每个内核对象中都有一个文件内容指针。纸箱应该读写的地址。

    而所谓的文件句柄,不过是文件内核对象在进程句柄表的中的索引。

    windows文件操作的好处:

    1.可以创建隐藏文件:dwFlagsAndAttributes中如果选择FILE_ATTRIBUTE_HIDDEN可创建一个隐藏文件,这是C语言的文件操作所不能办到的。

    2.windows文件操作是“线程安全的”,C或C++的文件操作则不是。所谓线程安全,就是当一个文件被当前进程所打开或者创建时,该文件就被该进程的该线程所占有,其他进程的线程无法读写该文件,直到该线程closehandle(即将对应文件的内核对象从该进程的句柄表中删除)或者该线程结束或者该进程结束。

    3.很多其他操作:

    获取文件大小拷贝文件移动文件

     

    // win32wenjian.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include "pch.h" #include <iostream> #include <windows.h> int main() { //1.使用该函数需要添加头文件 windows.h //2.该函数的作用: (1)如果同文件中没有该文件,创建该文件。如果有该文件,打开该文件(文件名处为路径名)。 (2)返回文件句柄给一个变量,方便操作文件。 HANDLE hFile=CreateFile("file.txt",GENERIC_ALL, FILE_SHARE_READ|FILE_SHARE_WRITE,/*共享方式 有 共享读,共享写,共享删除,全部以FILE_SHARE_开头*/ NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,//普通文件 NULL); if (INVALID_HANDLE_VALUE == hFile)//如果hFile == -1 即创建文件失败 { printf("创建文件失败!\n"); return -1; } printf("创建文件成功!\n"); while (1); return 0; }

    0x01写入:

    //写入 char buff[256] = "像我这么优秀的人,本该灿烂过一生\n";//16*2=32 DWORD writeOff = 0; bool is = WriteFile(hFile, buff, strlen(buff),//总共写入多少个字符 &writeOff//成功写入了多少字节,一般我们会创建一个DWORD类型的变量,将变量的地址写进去 , NULL);//是否有其他操作,NULL表示没有 //之所以要创建一个wirteOff变量,是因为WriteFile函数只有一个返回值,返回写入成功与否,具体成功写入了多少字符,需要用我们创建的变量writeOff来返回。 if (is) printf("写入%d字节\n", writeOff);//如果写入的文字变成了乱码,需要将当前项目属性改为多字节字符集 else printf("写入失败!\n");

    0x02获取文件大小:

    DWORD 就是unsigned long 4个字节,2^32-1B=4 294 967 295B=4GB,即如果仅仅用返回值表示size的话,最大只能表示4GB,但是一个文件可能比4GB大。所以专门设置了一个参数:文件大小的高字节fileSizeHigh,将文件大小分成两个变量来存。低字节存在size中,高字节部分存在另一个变量中。

    注意:打开文件和创建文件的 参数不同,CREATE_ALWAYS OPEN_ALWAYS

    //得到文件 HANDLE hMkv = CreateFile("123.mkv", GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hMkv == INVALID_HANDLE_VALUE) { cout << "打开失败!" << endl; return 0; } DWORD fileSizeHigh; DWORD size = GetFileSize(hMkv,&fileSizeHigh); long long totalSize = size | (long long)fileSizeHigh << 32; printf("文件大小为%ld字节\n", totalSize);

    0x03设置文件内容指针:

    //设置文件的内容指针 LONG high = 0; SetFilePointer(hFile, 33,//移动多少个字节 &high, FILE_BEGIN//从什么地方开始移动 ); DWORD num; WriteFile(hFile, "奈何20多年到头来,还在人海里浮沉", strlen("奈何20多年到头来,还在人海里浮沉"), &num, NULL);

    0x04拷贝文件:

    //**拷贝文件** bool ret = CopyFile("file.txt",//已经存在的文件名 "Song.txt",//新的文件名 true); if (ret) printf("拷贝成功!\n"); else printf("拷贝失败!\n"); //用这个函数可以写一个U盘偷猎者

    0x05移动文件:

    //**移动文件** MoveFile("file.txt",//已经存在的文件名 "..//dustbin.txt");//移动到上层文件夹中的。。 DeleteFile("song.txt");

    0x06关闭文件句柄:

    //关闭文件句柄 CloseHandle(hFile); //只要不关闭文件句柄,现在的进程就一直占据这个文件 //即拥有该文件的操作权限,禁止其他程序来去操作这个文件 //直到关闭文件句柄。实现原理:线程锁

    0x07.文件夹操作

    windows操作系统的文件系统:每个文件夹下都有两个默认隐藏的文件夹:

    名称为.的文件夹:表示当前文件夹

    名称为..的文件夹:表示上层文件夹

    0x08实现文件夹遍历:

     思路:写一个函数 TravelDirectory(传入文件夹名),函数功能就是遍历一个文件夹。怎么遍历呢?当然是用递归来实现,最简单的情形的就是文件夹中没有文件,直接返回。那么如何缩小问题规模呢?当然是采用分治的思想来分布解决这个问题了。遍历一个文件夹可以分为两步,第一步、找到第一个文件,判断是文件还是文件夹,如果找到的是一个文件夹就调用TravelDirectory遍历。如果是文件,就输出文件名。第二步、不断找下一个文件。直到没有下一个文件就返回。

    是不是感觉这个思路 和 深度寻路算法很相似呢?其实问题的本质就是深度寻路算法的问题。只不过,遍历文件夹,问题更简单,因为windows的所有文件其实就是一个森林,每一个盘符都是一个树。所以我们不需要像深度遍历图一样,准备一个辅助数组来记录每个点是不是已经走过。

    下面我们来详细讨论每一步具体怎么做:

    1.通配符号:

    %dint\n换行\\\%%%*对应任意个任意字符?对应一个任意字符

    2.找指定文件(从头开始找):

    注意:并不是像函数名说的找第一个文件。这个函数就是找指定的文件。

    如果能找到指定文件,创建对应的文件对象,返回该文件对象在当前进程的句柄。如果找不到指定名字的文件,返回值为INVALID_HANDLE_VALUE.

    HANDLE FindFirstFileA( LPCSTR lpFileName, //你要找的文件的名字 LPWIN32_FIND_DATAA lpFindFileData //你找到的文件的相关信息,根据属性可以判断是文件还是文件夹 );

    使用实例:

    WIN32_FIND_DATA fileInfo; HANDLE hFile = FindFirstFile("123.mkv", &fileInfo); if (INVALID_HANDLE_VALUE == hFile) { cout << "找不到" << endl; return 0; } cout << "找到了" << endl;

    我们用一个结构体来存该文件的相关信息,WIN32_FIND_DATA结构体定义如下:

    typedef struct _WIN32_FIND_DATAW { DWORD dwFileAttributes;//文件属性 FILETIME ftCreationTime;//创建时间 FILETIME ftLastAccessTime;//最后一次访问时间 FILETIME ftLastWriteTime;//最后一次写入时间 DWORD nFileSizeHigh;//文件大小 DWORD nFileSizeLow;//文件大小 DWORD dwReserved0;//保留位,更新迭代之用 DWORD dwReserved1;//保留位,更新迭代之用 _Field_z_ WCHAR cFileName[ MAX_PATH ];//文件名 _Field_z_ WCHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;

    其中文件属性包括:

    #define FILE_ATTRIBUTE_READONLY 0x00000001 #define FILE_ATTRIBUTE_HIDDEN 0x00000002 #define FILE_ATTRIBUTE_SYSTEM 0x00000004 #define FILE_ATTRIBUTE_DIRECTORY 0x00000010 #define FILE_ATTRIBUTE_ARCHIVE 0x00000020 #define FILE_ATTRIBUTE_DEVICE 0x00000040 #define FILE_ATTRIBUTE_NORMAL 0x00000080 #define FILE_ATTRIBUTE_TEMPORARY 0x00000100 #define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200 #define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400 #define FILE_ATTRIBUTE_COMPRESSED 0x00000800 #define FILE_ATTRIBUTE_OFFLINE 0x00001000 #define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 #define FILE_ATTRIBUTE_ENCRYPTED 0x00004000 #define FILE_ATTRIBUTE_INTEGRITY_STREAM 0x00008000 #define FILE_ATTRIBUTE_VIRTUAL 0x00010000 #define FILE_ATTRIBUTE_NO_SCRUB_DATA 0x00020000 #define FILE_ATTRIBUTE_EA 0x00040000 #define FILE_ATTRIBUTE_PINNED 0x00080000 #define FILE_ATTRIBUTE_UNPINNED 0x00100000 #define FILE_ATTRIBUTE_RECALL_ON_OPEN 0x00040000 #define FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS 0x00400000

    通过这个函数,我们只要将文件名设置为“传入的文件夹名\\*”,就可以找到文件夹中的第一个任意文件。

    3.找下一个文件:给定一个文件对象句柄,找到文件夹中该文件的下一个文件。如果能找到下一个文件,返回值为真,如果找不到下一个文件,返回值为假。我们遍历文件夹就是一个循环,我们可以用这个函数来作为循环结束的条件。

    BOOL WINAPI FindNextFileA( _In_ HANDLE hFindFile, _Out_ LPWIN32_FIND_DATAA lpFindFileData );

    源代码:无注释版。这个代码经过修改,可以查找磁盘中特定后缀的文件。并进行特定操作。

    // 20-windows文件操作.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include "pch.h" #include <iostream> #include <Windows.h> using namespace std; void travelDirectory(char* pathName) { TCHAR fileName[MAX_PATH] = { 0 }; sprintf(fileName, "%s\\*", pathName); WIN32_FIND_DATA fileInfo; HANDLE hFile = FindFirstFile(fileName, &fileInfo); if (INVALID_HANDLE_VALUE == hFile) { //cout << "空文件夹" << endl; return; } int ret = 1; TCHAR tempName[MAX_PATH] = { 0 }; while (ret) { if (fileInfo.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY) { if (fileInfo.cFileName[0] != '.') {//跳过".文件夹和..文件夹" memset(tempName, 0, sizeof(TCHAR)*MAX_PATH); sprintf(tempName, "%s\\%s", pathName, fileInfo.cFileName); //printf("文件夹:%s\n", tempName); travelDirectory(tempName); } } #if 0 else if (fileInfo.dwFileAttributes == FILE_ATTRIBUTE_NORMAL) { printf("文件:%s\n", fileInfo.cFileName); } #endif else if(fileInfo.dwFileAttributes == FILE_ATTRIBUTE_HIDDEN){ printf("隐藏文件:%s\n", fileInfo.cFileName); } #if 1 else { printf("其他文件:%s\n", fileInfo.cFileName); } #endif ret = FindNextFile(hFile, &fileInfo); } return; } int main() { travelDirectory((char*)"F:"); cout << "遍历完成" << endl; return 0; }

    注释版:

    // win32文件夹操作.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include "pch.h" #include <iostream> #include <windows.h>//用windows的宏都要加这个头文件 int count = 0; void findFile(char* fileName) { //1.设置文件名 fileName//*.* char findFileName[MAX_PATH] = { 0 }; //define MAX_PATH 260 在windows操作系统上,一个文件的名字,最大260个字节 sprintf(findFileName, "%s\\*.*", fileName);// 反斜杠需要转义 *.*也可以修改为* /* Windows中常用的通配符是 * 可以代替所有的字母/中文 ? 可以代替一个字母/中文 */ //2.循环找文件 WIN32_FIND_DATA findData;//结构体,用来专门装文件的属性 HANDLE hFile = FindFirstFile(findFileName, &findData);// if (INVALID_HANDLE_VALUE == hFile) {//如果没有找到第一个文件 return; } int ret = 1; char temp[MAX_PATH]; while (ret) {//找不到为止 //2.1判断是文件夹还是文件 if (FILE_ATTRIBUTE_DIRECTORY == findData.dwFileAttributes) { //2.1.1文件夹 if (findData.cFileName[0] != '.')//防止陷入 .文件夹 和 ..文件夹的死循环里面 { //2.1.1.1设置文件名 memset(temp, 0, MAX_PATH); sprintf(temp, "%s\\%s", fileName, findData.cFileName); printf("%d:%s\n", count++, temp); Sleep(1000); //2.1.1.2遍历文件夹 findFile(temp); } } else { //2.1.2文件 //打印文件名 或者do someting else memset(temp, 0, MAX_PATH); sprintf(temp, "%s\\%s", fileName, findData.cFileName); printf("%d:%s\n", count++, temp); Sleep(1000); } //2.2找下一个文件 ret = FindNextFile(hFile, &findData);//如果找到了下一个文件,返回1,没找到返回0 } } int main() { //获取当前文件夹名 char currentDirName[MAX_PATH] = { 0 }; GetCurrentDirectory(MAX_PATH,currentDirName);//获取当前文件夹名称,储存在一个字符数组中 findFile(currentDirName); } //作业: //代码行数查看器。 //找到一个文件夹下所有的.c .cpp文件统计一共写了多少行代码

    0x09.文件映射

    3.1虚拟内存 和 物理内存

    物理内存:内存条。

    虚拟内存:物理内存映射而来。

    3.2 文件映射虚拟内存:

    io操作:常规的文件操作方式,需要从磁盘中读取或者写入磁盘

    内存操作:CPU直接读写内存,效率更高

    可以像操作内存来操作内存,因为文件io(读写)很慢。

    3.3.文件映射编程模型:

    打开文件         CreateFile创建文件映射 CreateFileMapping加载文件映射  MapViewOfFile:把文件映射为内存使用映射内存卸载文件映射  UnmapViewOfFile关闭文件映射  CloseHandle关闭文件  CloseHandle // 文件映射.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include "pch.h" #include <iostream> #include <windows.h> int main() { //step1:创建文件 HANDLE hFile = CreateFile("American.txt", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS/*只读*/, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) { printf("创建文件失败!\n"); return -1; } printf("创建文件成功!\n"); //step2:创建文件映射 HANDLE hFileMapping = CreateFileMapping(hFile, NULL,//文件映射的属性:NULL表示默认的属性 PAGE_READWRITE,//保护方式:PAGE_开头表示文件映射以 内存页(4K)为单位 0,//高位文件大小 40,//低位文件大小 文件多大 映射多大 40字节 "fileMapping1"//映射的名字 ); //step3:加载文件映射:即我们可以像使用内存一样去使用这个文件了 void* p = MapViewOfFile(hFileMapping,//文件映射的句柄 FILE_MAP_ALL_ACCESS,//访问方式:任何方式,可读可写可操作 0, //开始的地方 0, //开始的地方 40 //总共的长度 );//返回一个指针,这个指针就是映射出来的内存段首地址。 //step4:使用文件映射出来的内存,给内存中赋值 0-9 int* pp = (int*)p; for (int i = 0; i < 10; i++) { printf("%d", *pp); pp++; } //step5:卸载文件映射 UnmapViewOfFile(p); //step6:删除文件映射 CloseHandle(hFileMapping); //step7:关闭文件 CloseHandle(hFile); }

    选做:创建一个保存有4G个电话号码的文件,查找某个电话号码。

    方式1.直接读文件

    方式2.映射文件为虚拟内存,内存操作。

    作业:代码行数统计器

    // 代码行数统计者.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include "pch.h" #include <iostream> #include <Windows.h> #include<string.h> int judge(char* str); int counter = 0;//统计代码行数 int num=0;//统计C文件数量 int dirs = 0;//统计文件夹数量 void findFile(char* filename){ char findFileName[MAX_PATH] = { 0 }; sprintf(findFileName, "%s\\*.*",filename); WIN32_FIND_DATA findFileData; HANDLE hFile = FindFirstFile(findFileName, &findFileData); if (hFile == INVALID_HANDLE_VALUE) { printf("该文件夹中没有.C文件\n"); return; } int ret = 1; char temp[MAX_PATH]; while (ret) { if (FILE_ATTRIBUTE_DIRECTORY ==findFileData.dwFileAttributes) {//如果是文件夹 if (findFileData.cFileName[0] != '.') { dirs++; printf("第%d个文件夹:%s\n",dirs,findFileData.cFileName); memset(temp, 0, MAX_PATH); sprintf(temp, "%s\\%s", filename, findFileData.cFileName); findFile(temp); } } else { if (judge(findFileData.cFileName))//如果是一个.c文件 { HANDLE hCpp = CreateFile(findFileData.cFileName, GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hCpp) { printf("打开文件失败\n"); } else { int lines = 0; num++; printf("第%d个C文件:%s\n", num, findFileData.cFileName); printf("打开第%d个文件成功!开始分析代码行数...\n", num); //得到文件大小 DWORD FileSizeHigh = 0; DWORD FileSizeLow = GetFileSize(hCpp, &FileSizeHigh); printf("该文件低位大小为:%d B,高位大小为%d B\n ", (int)FileSizeLow, (int)FileSizeHigh); Sleep(1000); //创建文件映射 HANDLE hFileMapping = CreateFileMapping(hCpp, NULL,//文件映射的属性:NULL表示默认的属性 PAGE_READWRITE,//保护方式:PAGE_开头表示文件映射以 内存页(4K)为单位 FileSizeHigh,//高位文件大小 FileSizeLow,//低位文件大小 文件多大 映射多大 "fileMapping1"//映射的名字 ); //step3:加载文件映射:即我们可以像使用内存一样去使用这个文件了 void* p = MapViewOfFile(hFileMapping,//文件映射的句柄 FILE_MAP_ALL_ACCESS,//访问方式 0, //开始的地方 0, //开始的地方 FileSizeLow //总共的长度 );//返回一个指针,这个指针就是映射出来的内存段首地址 /* MapViewOfFile()函数允许全部或部分映射文件,在映射时, 需要指定数据文件的偏移地址以及待映射的长度。其中,文 件的偏移地址由DWORD型的参数dwFileOffsetHigh和dwFileOffsetLow 组成的64位值来指定,而且必须是操作系统的分配粒度的整数倍,对于Windows操作系统,分配粒度固定为64KB。 。 */ char * ppBuffer = (char*)p; if (ppBuffer == NULL) { printf("文件指针为空,退出\n"); return; } for (int i = 0; i < (int)FileSizeLow; i++) { if (*ppBuffer++ == '\n') { counter++; lines++; } } printf("该文件代码行数大约为:%d\n", lines); //step5:卸载文件映射 UnmapViewOfFile(p); //step6:删除文件映射 CloseHandle(hFileMapping); } CloseHandle(hCpp); } } ret = FindNextFile(hFile, &findFileData); } } int judge(char* str) { int len = strlen(str); if (( *(str + len - 1) == 'p'&& *(str + len - 2) == 'p'&& *(str + len - 3) == 'c'&& *(str + len - 4) == '.') || ((*(str + len - 1) == 'c') && (*(str + len - 2) == '.'))) return 1; else return 0; } int main() { char currentDirectoryName[MAX_PATH] = { 0 }; GetCurrentDirectory(MAX_PATH, currentDirectoryName); findFile(currentDirectoryName); printf("小盆友,到目前为止你一共写了%d行代码!\n", counter); while (1); }

     

     


    最新回复(0)