Windows API教程(三) 文件系统

索引

  • 概念简介
    • 文件对象
    • 文件流
    • 文件句柄
    • 文件指针
  • 文件系统操作 常见 API
    • 高级文件操作
  • 本讲程序功能列表
  • CreateFile
    • 具体参数
    • 返回值
  • DeleteFile
    • 参数
    • 返回值
  • CopyFile、MoveFile、FindFirstFile
  • ReadFile
  • GetCurrentDirectory、FindNextFile
  • 更多改进
  • 学习方法小结

本讲程序功能列表

程序名 功能
touch 创建文件
rm 删除文件
cp 复制文件
mv 移动文件
cat 查看文件内容
ls 列举当前目录下文件

那么说到文件系统,实际上本节讨论的反而不是文件系统本身的问题。因为文件系统本身是个很复杂的东西,从Windows诞生到现在已经经历过好几种文件系统 如FAT16、FAT32、到现在的NTFS等等。撇开windows不谈,linux平台下的文件系统更是多的去了,什么ext系列reiserfs、xfs、jfx等等,真心很多。

那么博主这里讨论的文件系统主要是讨论windows下围绕文件系统的的一系列操作。自然还包括一系列的文件系统基本概念。

文件对象

学过C++的可能比较好理解,学过C的可以把文件对象当成是标准库中操作文件的FILE结构体。

文件流

当我们开始操作一个文件的时候,这个文件的打开关闭都由程序控制,而其中的内容是可以动态增减的,这样的情况类似水流一般,我们把这样的文件状态称作文件流。

文件句柄

当我们打开文件的时候,系统分配的一个句柄标识。

文件指针

博主看来,这个通常是文件读写操作时,存储相对于文件起始位置的一个指针。

文件系统操作 常见 API

CreateFile 创建、打开文件
ReadFile 读取文件内容
WriteFile 写入文件内容
SetFilePointer 移动文件指针
SetEndOfFile 设置文件结尾标志
CopyFile 文件拷贝
DeleteFile 文件删除
MoveFile 文件移动
CreateDirectory 创建一个目录
RemoveDirectory 删除一个目录
GetCurrentDirectory 获取当前程序所在目录
SetCurrentDirectory 设置当前程序所在目录
FindFirstFile 查找指定目录下的第一个文件
FindNextFile 查找下一个文件
LockFile 文件锁定
UnlockFile 文件解锁
GetFileType 获取文件类型
GetFileSize 获取文件的大小
GetFileAttributes 获取文件属性
SetFileAttributes 设置文件属性
GetFileTime 获取文件时间
GetFileInformationByHandle 获取文件信息
GetFullPathName 获取文件的完整路径
GetModuleFileName 获取当前模块全路径

文档详见:MSDN 文件操作 详细文档

高级文件操作

CreateFileMapping 创建文件的映射对象
MapViewOfFile 创建视图,将创建的文件映射对象到当前进程的地址空间中
FlushViewOfFile 将视图中数据都写入磁盘,对视图的操作都会反映到磁盘上的文件中
OpenFileMapping 打开已存在的文件映射
UnmapViewOfFile 取消文件映射
QueryDosDevice 查询 MS-DOS 设备的名称

详细参见:MSDN 内存操作 详细文档

CreateFile

各位可以跟着博主的步骤慢慢的开始学着使用windows API。那么我们先来做些小应用,准备实现一个功能,即创建一个空的文件。

那么我们先去翻查 API 的列表,发现了 CreateFile 这个函数,如果读者以后再编程的时候没有找到合适的 API 可以直接上 微软的官方网站 MSDN 上面找完整的 API 目录。

CreateFile 的 MSDN 官方文档可以找到:CreateFile function

最后看到函数原型:

HANDLE WINAPI CreateFile(
  _In_      LPCTSTR lpFileName,			// 文件名
  _In_      DWORD dwDesiredAccess,		// 访问方式
  _In_      DWORD dwShareMode,			// 共享模式
  _In_opt_  LPSECURITY_ATTRIBUTES lpSecurityAttributes,	// 安全属性
  _In_      DWORD dwCreationDisposition,// 创建凡是
  _In_      DWORD dwFlagsAndAttributes,	// 文件属性
  _In_opt_  HANDLE hTemplateFile		// 模板文件句柄 
);

那么首先稍微了解一下,_In__In_opt_,如同第一节中提到过的 far 和 near 这两个也是用来做标识的宏,其中_In_表示该参数是一个输入参数,即数值直接传入;_Out_则是指输出参数,一般传入指针,函数执行中会通过指针对该实参赋值;而_opt_选项是表示该参数是可选的,即,可以设置为空。

具体参数

lpFileName 输入参数

操作对象文件的相对路径绝对路径。需要注意的就是文件名中的特殊字符,比如空格需要转义等等,还有就是文件名的长度不要超过系统限制(ANSI版 请使用 MAX_PATH 宏)。

dwDesiredAccess 输入参数

指明对文件对象的操作存取方式,最常用的是

常量 通用意义 (Generic meaning)
GENERIC_READ 读取权限
GENERIC_WRITE 写入权限
GENERIC_EXECUTE 执行权限
GENERIC_ALL 所有权限

你可以设置文件打开是读(GENERIC_READ)还是写(GENERIC_WRITE)亦或是读写 (GENERIC_READ | GENERIC_WRITE)。

dwShareMode 输入参数

共享模式。指明与其他进程是否共享该文件,可以是共享读(FILE_SHARE DELETE)、共享写(FILE_SHARE_WRITE)、共享删除(FILE_SHARE.READ),如果要指明多个属性,使用“位与~‘I”运算。如果指明上述参数,其他进程就可以对文件进行相关操作。如果本进程需要独占本文件,则将本参数设置为 0。

意义
0 阻止其他进程对当前文件或设备进行删除或读写
FILE_SHARE_DELETE 允许其他进程通过 “删除” 权限打开。
因此其他进程无法通过 “删除” 权限打开当前文件或设备。
如果没有设置该项,但是该文件或设备已经以 “删除” 的权限打开,那么 这个方法会调用失败。
注: “删除” 权限 允许 “删除” 和重命名操作。
FILE_SHARE_READ 允许其他进程通过 “读取” 权限打开。
因此其他进程无法通过 “读取” 权限打开当前文件或设备。
如果没有设置该项,但是该文件或设备已经以 “读取” 的权限打开,那么 这个方法会调用失败。
FILE_SHARE_WRITE 允许其他进程通过 “写入” 权限打开。
因此其他进程无法通过“写入”权限打开当前文件或设备。
如果没有设置该项,但是该文件或设备已经以 “写入” 的权限打开,或者已经有一个文件以执行的权限打开,那么这个方法会调用失败。

lpSecurityAttributes 输出参数(可选)

指向 SECURITY_ATTRIBUTES结构的指针,表示本文件句柄的安全属性,能影响其是否可被子进程继承等操作。如果设定为NULL,则子进程不可继承本句柄。SECURITY ATTRIBUTES 结构不常用,对此数据结构的设置,涉及Windows系统中对权限管理的原理,在本节中不作详细说明。

dwCreationDisposition 输入参数

Value Meaning
CREATE_ALWAYS 总是创建一个新文件。
如果指定的文件存在并且是可写的,这个方法会清除内容,重写一遍。这样的情况下,即使成功了 last-error(类似于C中的错误流)也会被设置成 ERROR_ALREADY_EXISTS (183)。
如果指定的文件不存在并且文件名是一个有效路径,一个新的文件会被创建, 函数调用成功, 并且 last-error 会被设置成 0。
更多的信息, see the Remarks section of this topic。
CREATE_NEW 仅当该文件不存在时创建一个新文件。
如果指定的文件存在,这个方法会失败 并且 last-error 会被设置成 ERROR_FILE_EXISTS (80)。
如果指定的文件不存在并且文件名是一个可写目录下的有效路径,一个新的文件会被创建。
OPEN_ALWAYS 总是打开这个文件。
如果指定的文件 存在, 函数调用成功 并且 last-error 会被设置成 ERROR_ALREADY_EXISTS (183)。
如果指定的文件不存在,并且文件名是一个可写目录下的有效路径,该函数会创建一个文件并且 last-error 会被设置成 0。
OPEN_EXISTING 仅当该文件或设备存在时,打开它。
如果指定的文件或设备不存在,这个方法会失败 并且 last-error 会被设置成 ERROR_FILE_NOT_FOUND (2)。
TRUNCATE_EXISTING 打开一个文件并且清空(truncates)它(仅当该文件存在)。
如果指定的文件 不存在,这个方法会失败 并且 last-error 会被设置成 ERROR_FILE_NOT_FOUND (2)。
这个过程的调用必须设置 dwDesiredAccess 参数为 GENERIC_WRITE 。

dwFlagsAndAttributes 输入参数

文件属性和文件标志。一般情况下文件属性较常用,而操作标志不常用,可以使用“1”运算符指定多个属性和标志。

属性 意义
FILE_ATTRIBUTE_ARCHIVE 存档文件 应用程序 使用这个属性来 标记文件的 备份或删除
FILE_ATTRIBUTE_ENCRYPTED 加密文件, 这意味着文件中的所有数据都是加密的. 对于一个目录而言, 这意味着其包含的文件以及子目录都默认是加密的。 更多的信息,请搜索 File Encryption。
如果 FILE_ATTRIBUTE_SYSTEM 也同时被设置,那么该属性不会起任何作用。
PS: 该属性在 windows 的家庭版、家庭高级版、Starter或者ARM版本上不支持
FILE_ATTRIBUTE_HIDDEN 隐藏文件。 请不要将其放在普通的目录下.
FILE_ATTRIBUTE_NORMAL 未设置其他属性。只能单独使用(不能 “|” )
FILE_ATTRIBUTE_OFFLINE 离线存储文件该属性表明文件数据被物理的移动到离线存储器(offline storage)。
该属性通常用于操作远程文件。应用程序请不要随意地设置该属性.
FILE_ATTRIBUTE_READONLY 文件只读
FILE_ATTRIBUTE_SYSTEM 系统文件。
FILE_ATTRIBUTE_TEMPORARY 临时文件
更多的信息, see the Caching Behavior section of this topic.

hTemplateFile 输入参数(可选)

当存取权限包括 GENERIC_WRITE 时,可以设置为一个模板
文件的句柄。一般情况下,可设置为 NULL,表示不使用模板文件。

返回值

如果成功则返回HANDLE数据类型的文件的句柄,如果失败,则返回 NVALID_HANDLE_VALUE,想要查看具体的错误情况请使用 GetLastError 函数。

touch 创建文件

是否被上述详细的列表给看花眼了?好吧,各位淡定,这个只是给大家举个例子,表示每个函数的详细信息都是可以查得到的(如果搜不到中文的,就老老实实的上MSDN吧)

那么我们继续准备实现一个功能,即创建一个空的文件。只需要简单的调用该函数即可,那么大家打开各自的IDE准备开始编写,注意我们要建的项目类型是空项目,就是刚出来什么都没有的那种,项目名称最好是叫 touch (因为最后生成的exe名称就是你的项目名称)并且注意你的项目所保存的路径,因为等会程序写好了是要切换到那个目录的。

空项目建好之后,在源代码文件夹处右键新建一个 .c 文件(不行先用.cpp文件也是可以的):

main.c


#include <windows.h>
#include <stdio.h>	// for printf
void help();

int main(int argc, char const *argv[])
{
	int i;

	// 参数数目为1 即用户只输入程序名,程序仅收到一个参数
	if (argc == 1)
	{
		help();
		return 0;
	}

	// 遍历指针数组所指向的参数
	for (i = 1; i < argc; i++)
	{
		CreateFile(
			argv[i],				// 文件名
			GENERIC_WRITE,			// 写入权限
			0,						// 阻止其他进程访问
			NULL,					// 子进程不可继承本句柄
			CREATE_NEW,				// 仅不存在时创建新文件
			FILE_ATTRIBUTE_NORMAL,	// 普通文件
			NULL);					// 不适用模板文件
	}
	
	
	return 0;
}

void help()
{
	printf("创建新文件:");
	printf("touch <文件名1> <文件名2> ...");
}

编写好了之后,注意只需要生成即可(快捷键F7,不用F5打开运行),生成之后就可以在debug目录下找到exe文件。这个时候我们打开cmd,切换到项目的Debug目录下(cmd基础操作见《CMD扫盲教程》)调用我们刚刚写的程序:

可以看到调用程序很简单的就建立了三个txt文件,注意参数也是可以加路径的哦,例如 touch F:1.txt也是可以的。
那么,接下来我们就可以在某个地方建个专用文件夹来存放自己写的多个函数,例如 F:/mytools 目录,然后把刚刚的touch.exe复制到这个目录下,接着将 F:/mytools 添加到环境变量(cmd 设置PATH环境变量)。 以后 cmd 中就可以随时随地的用自己写的这些程序了。(博主是准备带大家写一系列的工具集)

DeleteFile

大家可以使用各种搜索引擎去搜索,百度不到就用谷歌。当然官方文档还是要去MSDN的,这边直接上MSDN就可以搜到了,注意MSDN的搜索关键字最好是 方法名+function,例如 DeleteFile Function : http://msdn.microsoft.com/en-us/library/windows/desktop/aa363915(v=vs.85).aspx

函数原型:

BOOL WINAPI DeleteFile(
  _In_  LPCTSTR lpFileName	// 删除文件名
);

DeleteFile的功能是删除一个已存在的文件文件。

参数

lpFileName 输入参数

所要删除的文件的相对路径(如”1.txt”相对于当前的路径) 或绝对路径 (如”F:1.txt”)。

返回值

返回BOOL值,那么第一节的时候我们也看到过了 BOOL 其实就是int类型的同义字。如果该函数执行成功的话,会返回一个非零的数,如果失败的话,会返回零。想知道错误信息的话,就请使用 GetLastError 函数。

rm 删除文件

这个函数的参数很少,但是大家也不要小觑。那么我们现在再来调用这个API实现个小功能。那就是删除文件。

还是跟上面一样,空项目,项目名称 “rm” (最后生成exe的名称)。


#include <windows.h>
#include <stdio.h>	// for printf

#define FILE_DEL_MAX 10

void help();

struct _del_attr {
	int Force_del;	// 强制删除选项
} ;

struct _del_list {
	const char *File_to_Del[FILE_DEL_MAX]; // 待删文件列表
	int count;				// 待删文件数量
	struct _del_attr Attr;	// 删除选项
} Delete_list = {0};

int main(int argc, const char *argv[])
{
	int i;
	char cmd;

	if(argc == 1)
	{
		help();
	}

	// 遍历参数
	for (i = 1; i < argc; i++)
	{
		// 获取参数选项
		if( *argv[i] == '-') // 如果当前指向字符串的第一个为 '-'
		{
			if( strcmp( argv[i], "-f" ) == 0 )
			{
				Delete_list.Attr.Force_del = 1;
			}
		} else // 保存文件路径
		{			
			Delete_list.File_to_Del[Delete_list.count] = argv[i];
			// printf("待删文件路径 [%s] 已保存n", Delete_list.File_to_Del[Delete_list.count]);
			Delete_list.count++;
			// printf("目前待删文件数目 %dn", Delete_list.count);
		}
	}

	// 遍历待删文件列表
	for (i = 0; i < Delete_list.count; i++)
	{
		if( Delete_list.Attr.Force_del == 1 ) // 是否强制删除
		{			
			if( !DeleteFile( Delete_list.File_to_Del[i] ) ) // 如果非零则删除失败
			{
				printf("删除文件错误:%xn",GetLastError());
			}
		}else
		{	// 询问是否删除
			printf("是否要删除文件 [%s] ?(y/n)", Delete_list.File_to_Del[i] );
			scanf("%c", &cmd);
			getchar(); // 回收回车
			if ( cmd == 'y' )
			{
				if( !DeleteFile( Delete_list.File_to_Del[i] ) ) // 如果非零则删除失败
				{
					printf("删除文件错误:%xn",GetLastError());
				}else
				{
					printf("文件 [%s] 已删除n", Delete_list.File_to_Del[i]);
				}
			}
		}
	}
}

void help()
{
	printf("删除文件:n");	
	printf("rm <文件名1> <文件名2> ... [-选项]n");	
	printf("选项: -f 强制删除");
}

写好之后,生成exe,打开cmd 切换到该目录下测试一下:

Microsoft Windows [版本 6.1.7601]
版权所有 (c) 2009 Microsoft Corporation。保留所有权利。

# 切换目录
C:UsersLellansin>cd F:WorkspaceCrmDebug

# 切换盘符
C:UsersLellansin>F:

# dir 查看文件列表
F:WorkspaceCrmDebug>dir

04/24/2013  09:31 PM    <DIR>          .
04/24/2013  09:31 PM    <DIR>          ..
04/24/2013  09:26 PM            32,256 rm.exe
04/24/2013  09:26 PM           231,948 rm.ilk
04/24/2013  09:26 PM           412,672 rm.pdb

# 调用我们写的help函数
F:WorkspaceCrmDebug>rm
删除文件:
rm <文件名1> <文件名2> ... [-选项]
选项: -f 强制删除

# touch 创建两个文件
F:WorkspaceCrmDebug>touch 1.txt 2.txt

F:WorkspaceCrmDebug>dir

04/24/2013  09:32 PM    <DIR>          .
04/24/2013  09:32 PM    <DIR>          ..
04/24/2013  09:32 PM                 0 1.txt
04/24/2013  09:32 PM                 0 2.txt
04/24/2013  09:26 PM            32,256 rm.exe
04/24/2013  09:26 PM           231,948 rm.ilk
04/24/2013  09:26 PM           412,672 rm.pdb

# 调用我们写的函数来删除刚刚的两个文件
F:WorkspaceCrmDebug>rm 1.txt 2.txt
是否要删除文件 [1.txt] ?(y/n)y
文件 [1.txt] 已删除
是否要删除文件 [2.txt] ?(y/n)y
文件 [2.txt] 已删除

# 查看发现文件已删除
F:WorkspaceCrmDebug>dir

04/24/2013  09:32 PM    <DIR>          .
04/24/2013  09:32 PM    <DIR>          ..
04/24/2013  09:26 PM            32,256 rm.exe
04/24/2013  09:26 PM           231,948 rm.ilk
04/24/2013  09:26 PM           412,672 rm.pdb

那么大家在修改测试的时候只需要使用vs的生成功能(F7)即可,只不过要注意,如果修改代码生成没改变请使用 “重新生成” 选项(Ctrl+Alt+F7)。测试好了之后,确认功能实现无误,就可以把这个rm.exe拷贝到我们的 F:mytools 下了。哟西,又多了一个工具。

CopyFile、MoveFile、FindFirstFile

按照前两个的流程我们可以很简单的过一下,查查资料,然后写个小工具简单的尝试一下。

# 文件复制
BOOL WINAPI CopyFile(
  _In_  LPCTSTR lpExistingFileName,	// 源文件名(路径)
  _In_  LPCTSTR lpNewFileName,		// 新文件名(路径)
  _In_  BOOL bFailIfExists			// 是否避免覆盖
);

# 文件剪切
BOOL WINAPI MoveFile(
  _In_  LPCTSTR lpExistingFileName,	// 原文件名(路径)
  _In_  LPCTSTR lpNewFileName		// 新文件名(路径)
);

# 查找目录下的第一个文件
HANDLE WINAPI FindFirstFile(
  _In_   LPCTSTR lpFileName,				// 待查目录
  _Out_  LPWIN32_FIND_DATA lpFindFileData	// 找到的文件(输出参数)
);

cp 复制文件

#include <windows.h>
#include <stdio.h>	// for printf
#include <string.h>	// for string operation

int isDirectory(char *path);
void help();

int main(int argc, char const *argv[])
{
	char file_src[MAX_PATH]={0};
	char file_dest[MAX_PATH]={0};

	// 只输入程序名 和一个参数则调用help
	if (argc <= 2)
	{
		help();
		return 0;
	}

	memmove(file_src, argv[1], strlen(argv[1]));
	memmove(file_dest, argv[2], strlen(argv[2]));

	if( isDirectory(file_dest) )
	{	// 如果第二个参数是目录, 则拼装新的文件路径
		sprintf(file_dest, "%s\%s", file_dest, file_src);
	}

	if( CopyFile(file_src, file_dest, 0) == 0)
		printf("文件复制失败!");
	
	return 0;
}

// 判断是否为目录
BOOL isDirectory(char *path)
{
	WIN32_FIND_DATA fd;
	BOOL rel = FALSE;
	char *p = path;
    
	// 查找到第一个文件的句柄
    HANDLE hFind = FindFirstFile(path, &fd);

	while(*p != '\0') p++;

    // 如果结尾是这两种符号就肯定是目录
    if( *(--p) == '\' || *(p) == '/' ) {
        *p = '\0';
        return TRUE;
    } 

	// 判断是否获取错误
	if(hFind != INVALID_HANDLE_VALUE)
	{
		// 文件信息按位与上目录属性, 非目录则全部置零
		if( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
		{
			rel = TRUE;  
		}
		// 关闭查找句柄
		FindClose(hFind);
	}
    return rel;  
}

void help()
{
	printf("复制文件:n");
	printf("cp <文件名> <新路径>n");
	printf("cp <文件名> <新文件名>n");
	printf("cp <文件名> <新路径\新文件名>");
}

mv 移动文件

类似的,只要改动一行代码就可以写出移动文件的程序来了。

#include <windows.h>
#include <stdio.h>	// for printf
#include <string.h>	// for string operation

int isDirectory(char *path);
void help();

int main(int argc, char const *argv[])
{
	char file_src[MAX_PATH]={0};
	char file_dest[MAX_PATH]={0};

	// 只输入程序名 和一个参数则调用help
	if (argc <= 2)
	{
		help();
		return 0;
	}

	memmove(file_src, argv[1], strlen(argv[1]));
	memmove(file_dest, argv[2], strlen(argv[2]));

	if( isDirectory(file_dest) )
	{	// 如果第二个参数是目录, 则拼装新的文件路径
		sprintf(file_dest, "%s\%s", file_dest, file_src);
	}

	if( MoveFile(file_src, file_dest) == 0)
		printf("文件剪切失败!");
	
	return 0;
}

// 判断是否为目录
BOOL isDirectory(char *path)
{
	WIN32_FIND_DATA fd;
	BOOL rel = FALSE;
	char *p = path;
    
	// 查找到第一个文件的句柄
    HANDLE hFind = FindFirstFile(path, &fd);

	while(*p != '\0') p++;

    // 如果结尾是这两种符号就肯定是目录
    if( *(--p) == '\' || *(p) == '/' ) {
        *p = '\0';
        return TRUE;
    } 

	// 判断是否获取错误
	if(hFind != INVALID_HANDLE_VALUE)
	{
		// 文件信息按位与上目录属性, 非目录则全部置零
		if( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
		{
			rel = TRUE;  
		}
		// 关闭查找句柄
		FindClose(hFind);
	}
    return rel;  
}

void help()
{
	printf("剪切文件:n");
	printf("mv <文件名> <新路径>n");
	printf("mv <文件名> <新文件名>n");
	printf("mv <文件名> <新路径\新文件名>");
}

如果读者有跟着一起写的话,那么现在环境变量中应该已经有了4个小程序,那么我们现在来对这几个小程序做下测试:

# 新建目录 test
C:>mkdir test

# 切换
C:>cd test
# 创建两个文件

C:test>touch hello.txt list.txt

# 导入一些数据进去
C:test>echo "hello world" > hello.txt && dir > list.txt

# 查看当前文件下的列表
C:test>dir

04/26/2013  12:16 AM    <DIR>          .
04/26/2013  12:16 AM    <DIR>          ..
04/26/2013  12:16 AM                17 hello.txt
04/26/2013  12:16 AM               352 list.txt

# 复制hello.txt的副本hello2.txt
C:test>cp hello.txt hello2.txt

# 剪切hello.txt 到 hi.txt (相当于重命名)
C:test>mv hello.txt hi.txt

C:test>dir

04/26/2013  12:17 AM    <DIR>          .
04/26/2013  12:17 AM    <DIR>          ..
04/26/2013  12:16 AM                17 hello2.txt
04/26/2013  12:16 AM                17 hi.txt
04/26/2013  12:16 AM               352 list.txt

# 强制删除3个文件
C:test>rm hello2.txt hi.txt list.txt -f

C:test>dir

04/26/2013  12:18 AM    <DIR>          .
04/26/2013  12:18 AM    <DIR>          ..

C:test>

那么博主就做了这些测试,实际上 cp 和 mv 的第二个参数可以直接给一个不同的路径名的,比如cp 1.txt ../ 就是将1.txt复制到上级目录。(”.”是当前目录, “..”是上级目录)

ReadFile

#读取文件内容
BOOL WINAPI ReadFile(
  _In_         HANDLE hFile,
  _Out_        LPVOID lpBuffer,
  _In_         DWORD nNumberOfBytesToRead,
  _Out_opt_    LPDWORD lpNumberOfBytesRead,
  _Inout_opt_  LPOVERLAPPED lpOverlapped
);

cat 查看文件内容

那么这里带大家写一个类似 CMD 中 type 命令的程序,功能是读取一个文件并在 CMD 上显示。

#include <windows.h>
#include <stdio.h>	// for printf
void help();

int main(int argc, char const *argv[])
{
	HANDLE hFile;
	DWORD fileSize, readSize;
	char *buffer;

	if (argc == 2)
	{
		hFile = CreateFile(
			argv[1],				// 文件名
			GENERIC_READ,			// 读取权限
			0,						// 阻止其他进程访问
			NULL,					// 子进程不可继承本句柄
			OPEN_EXISTING,			// 仅当该文件或设备存在时,打开它
			FILE_ATTRIBUTE_NORMAL,	// 普通文件
			NULL);					// 不适用模板文件

		if (hFile == INVALID_HANDLE_VALUE) 
		{ 
			printf("无法打开文件 "%s"n", argv[1]);
			return; 
		}

		fileSize = GetFileSize(hFile,NULL); // 获取文件大小
		buffer = (char *)malloc(fileSize + 1); // 获取一块内存
		buffer[fileSize] = '\0'; // 设置结尾

		ReadFile(
			hFile,		// 文件句柄
			buffer,		// 读取到的文件所存放的缓冲区
			fileSize,	// 要读取的字节数
			&readSize,	// 实际读取的字节数
			NULL		// 用 FILE_FLAG_OVERLAPPED 打开时所需的
		);

		printf(buffer);

		CloseHandle(hFile);
		free(buffer);
	} else 
	{
		help();
	}

	return 0;
}

void help()
{
	printf("浏览文件:");
	printf("cat <文件名>");
}

GetCurrentDirectory、FindNextFile

# 获取当前目录
DWORD WINAPI GetCurrentDirectory(
  _In_   DWORD nBufferLength,	// 保存目录的变量大小
  _Out_  LPTSTR lpBuffer		// 保存目录的变量
);

# 获取下一个文件
BOOL WINAPI FindNextFile(
  _In_   HANDLE hFindFile,		// 相对文件句柄
  _Out_  LPWIN32_FIND_DATA lpFindFileData	// 获取文件信息(输出参数)
);

ls 文件列表

那么这里博主带大家写个类似 dir 这样 cmd 命令的程序,功能就是列举出当前目录下的文件。依旧是空项目,项目名为 ls ,新建一个 .c 文件

#include <windows.h>
#include <stdio.h>	// for sprintf
#include <string.h>	// for memmove

int main(int argc, const char *argv[])
{
	CHAR path[MAX_PATH] = {0};	// 待列路径
	CHAR szFilePath[MAX_PATH];	// 具体查找路径
    HANDLE hListFile;			// 获取到的文件句柄
	WIN32_FIND_DATA fileData;	// 查找到的文件数据 


	if(argc == 1)	// 只输入程序名则显示当前目录
	{		
        GetCurrentDirectory(MAX_PATH,path);
	}else if(argc == 2)	// 指定显示目录则显示指定目录
    {
        memmove(path, argv[1], strlen(argv[1]));
    }
    
    // 复制路径到具体查找路径
    lstrcpy(szFilePath, path);
	// 路径拼接通配符
	lstrcat(szFilePath, "\*");

	// printf("列表路径: [%s] n", szFilePath);

    // 查找路径下第一个文件/目录,获得句柄
    hListFile = FindFirstFile(szFilePath,&fileData);

    // 判断句柄是否获取到
    if(hListFile == INVALID_HANDLE_VALUE)
    {
        printf("错误:%d",GetLastError());
        return 1;
    } else {        
        do
        {	// 输出找到的文件名
            printf("%st", fileData.cFileName );
        }	// 查找下一个文件, 并获取其信息
        while(FindNextFile(hListFile, &fileData));
    }
    return 0;
}

cmd 测试一下:

# 列出当前目录下的文件
F:WorkspaceClsDebug>ls
.       ..      ls.exe  ls.ilk  ls.pdb

# 列出其他目录下的文件
F:WorkspaceClsDebug>ls F:WorkspaceC
.       ..      cp      hello   ls      mv      Project	Project2
rm      Snake   strcoll strxfrm Study   Test    touch	ScanPort   

那么一些基本的添加删除功能的工具集就这样做好了,是否稍稍有点成就感?实际上这些程序中还有很多可以改进的地方。

更多改进

1.touch 中如果新建 touch F:test1.txt 如果test文件夹不存在的话,那么文件1.txt就不能被创建,我们可以加个”-p” 参数简单的用CreateDirectory函数来改进这一功能。

2.rm 和 mv 还只能移动文件,不能移动文件夹,那么我们的目录操作也可以解决这个问题了。通过判断参数是不是目录,然后新建一个目标目录,再通过ls中FindNextFile的形式一个个删除或剪切过去。

3.通配符的问题,比如 rm 和 ls 这样的命令可以考虑添加 rm *.txt -f (强制删除所有txt文件) 还有 ls *.txt (列举所有txt文件) 这样方便的通配符(“*”号匹配任意字符)的情况来删除。

4.ls 命令不能区别文件和文件夹(可以给文件夹改个颜色:使用SetConsoleTextAttribute函数),还可以加个 “-l” 参数来将列表竖起来看,可以加个 “-a” 将一些特出的隐藏文件都显示出来。

这些都是可以改进的地方。读者可以自行尝试着来改进这些程序。相信有的读者也应该发现了,博主这里是在windows平台下实现linux下的一些常见命令^_^~ 嘛,算是一个系列吧,以后也会把这些改进之后的程序源码公开到网上。

学习方法小结

应该能改感觉到一点端倪,流程很简单:

想实现功能 –> 查找相应功能的API –> 收集详细资料 –> 使用该API

实际上 windows API 说白了就是这样,是对微软公司在windows系统平台上提供的系列函数的一个应用而已,过程都这样,查手册、收集资料神马的有的时候反而是最花时间的,至于难度,基本上不用担心API,依旧要注意的是C/C++本身的问题。

至于后来即将研究的进程、线程等,重点也是理解其编程的思想,因为最复杂的部分都已经被封装好了,我们只需要去调用API,学会如何使用罢了。

上一讲:windows 编程之路(二) 句柄与内核
下一讲:windows 编程之路(四) 进程编程

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google+ photo

You are commenting using your Google+ account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

Connecting to %s