读写文件每一个软件开发显目必定涉及的工作。CreateFile函数用于创建对应的文件句柄,WriteFile函数是用来写数据到文件,ReadFile函数是从文件里读取数据出来。
CreateFile
该函数用于生成设备(文件)的对应句柄(HANDLE)。
//CreateFile函数声明
HANDLE CreateFile(
LPCTSTP lpFileName, //文件名
DWORD dwDesiredAccess, //访问方式
DWORD dwShareMode, //共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
//一个指向安全结构的指针,一般设置为NULL即可
DWORD dwCreationDisposition, //创建方式
DWORD dwFlagAndAttributes, //属性
HANDLE hTemplateFile //复制文件句柄
)
下面是具体参数的相关说明
●dwDesiredAccess 访问方式
GENERIC_READ 表示允许对设备(文件)进行读访问
GENERIC_WRITE 表示允许对设备(文件)进行写操作
NULL 表示仅允许获取一个与设备(文件)有关的信息
●dwShareMode 共享模式
NULL 表示不共享
FILE_SHARE_READ 表示允许设备(文件)进行共享读访问
FILE_SHARE_WRITE 表示允许设备(文件)进行共享写访问
●lpSecurityAttributes 安全指针
指向一个 LPSECURITY_ATTRIBUTES 结构的指针,该结构定义了设备(文件)的安全特性,一般情况取NULL。
●dwCreationDisposition 创建方式
CREATE_NEW 创建文件,若文件存在则会出错
CREATE_ALWAYS 创建文件,会改写前一个文件(常用)
OPEN_EXISTING 文件必须已经存在,由设备提出要求
OPEN_ALWAYS 如果文件不存在则创建它
TRUNCATE_EXISTING 将现有文件缩短为零长度
●dwFlagAndAttributes 属性
FILE_ATTRIBUTE_ARCHIVE 将文件标记为归档属性
FLIE_ATTRIBUTE_COMPRESSED 将文件标记为已压缩,或者标记为文件在目录中的默认方式
FILE_ATTRIBUTE_NORMAL 默认属性
FIEL_ATTRIBUTE_HIDDEN 隐藏文件或目录
FIEL_ATTRIBUTE_READONLY 文件为只读文件
FIEL_ATTRIBUTE_SYSTEM 文件为只读文件
FILE_FLAG_WAITE_THROUGH 操作系统不得推迟对文件的写操作
FILE_FLAG_OVERLAPPED 允许对文件进行重叠操作
FILE_FLAG_NO_BUFFERING 禁止对文件进行缓存处理,只能写入磁盘的扇区块
FILE_FLAG_RANDOM_ACCESS 针对随机访问对文件进行优化
FILE_FLAG_SEQUENTIAL_SCAN 针对连续访问对文件进行缓冲优化
FILE_FLAG_DELETE_ON_CLOSE 关闭句柄后将文件删除,适用于临时文件
ReadFile
ReadFile函数将文件数据读取到一个缓冲区中。
//ReadFile 函数声明
ReadFile(
HANDLE hFile, //句柄
LPVOID lpBuffer, //缓冲区指针
DWORD nNumberOfBytesToRead, //读出的字节数
LPDWORD lpNumberOfBytesRead, //用于保存实际读出的字节数的存储区域,用于判断是否读取成功
LPOVERLAPPED lpOverlapped //OVERAPPED结构体指针,一般取NULL
);
WriteFile
WriteFile函数将数据写入到一个文件中,该函数比fwrite更加灵活、方便、
//WriteFile 函数声明
WriteFile(
HANDLE hFile, //句柄
LPVOID lpBuffer, //数据缓冲区指针
DWORD nNumberOfBytesToRead, //写入的字节数
LPDWORD lpNumberOfBytesRead, //用于保存实际写入的字节数的存储区域,用于判断是否读取成功
LPOVERLAPPED lpOverlapped //OVERAPPED结构体指针,一般取NULL
);
下面一起看一个借助这些WINAPI接口完成的有趣的示例,(该示例来自网易云课堂择善教育)
首先先简单介绍一下BMP文件格式,是Windows操作系统中的标准图像文件格式
我们使用UItraEdit打开一个BMP文件可以看到如下结果:
我们先来看一下BMP文件首部的结构
typedef struct tagBITMAPFILEHEADER
{
WORD bfType;//位图文件的类型,必须为BM(1-2字节)
DWORD bfSize;//位图文件的大小,以字节为单位(3-6字节,低位在前)
WORD bfReserved1;//位图文件保留字,必须为0(7-8字节)
WORD bfReserved2;//位图文件保留字,必须为0(9-10字节)
DWORD bfOffBits;//位图数据的起始位置,以相对于位图(11-14字节,低位在前)
//文件头的偏移量表示,以字节为单位
}BITMAPFILEHEADER;
这里最重要的就是需要获得位图的起始位置的偏移量,这样才能在不破坏原文件格式的情况下,将文件悄悄的写入。
对于每一个位图文件,第一个像素的偏移地址都是固定的,在上图最后一个红色方框处,00000036对应的就是首像素偏移字节量。
我们通过偏移量轻松的定位了第一个像素的位置,此时每一个数据由6个字节组成,反正就是后三个改了影响不大,很难看出来,所以可以藏数据(我想最强大脑那些人一定可以看出来吧)
不管了 ,下面一起看看代码,主要是文件读写操作。
// HideInBMP.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h> //winapi的头文件
#include <iostream>
#include <cstring>
using namespace std;
char * GetFileContent(char *filename, DWORD *filesize)
{
//用于创建句柄,并开辟缓冲区,将文件内容读入
HANDLE hfile = CreateFileA(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
//文件句柄
//GENERIC_READ | GENERIC_WRITE 表示允许对文件进行读写操作
// FILE_SHARE_WRITE | FILE_SHARE_READ 表示允许对文件进行共享读写操作
//OPEN_EXISTING 表示文件必须已经存在
if (hfile==INVALID_HANDLE_VALUE)
{
cout << "Can't open " << filename << endl;
return NULL;
}
DWORD dwRead;
DWORD dwSize = GetFileSize(hfile, &dwRead); //读取文件大小
*filesize = dwSize;
char * cBuf = new char[dwSize]; //在堆上开辟缓冲区,等待读入文件数据
RtlZeroMemory(cBuf, sizeof(cBuf)); //用0填充缓冲区
ReadFile(hfile, cBuf, dwSize, &dwRead, 0);
//读入数据到缓冲区
if (dwRead != dwSize) //判断是否读入正常
{
cout<<"Read"<<filename<<" failed\n"<<endl;
return NULL;
}
CloseHandle(hfile);
return cBuf; //返回指向缓冲区的指针
}
bool SaveFile(char * buf, int len, char * filename)
{
//用于向BMP文件中写数据
HANDLE hfile = CreateFileA(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
//创建句柄
//允许读写操作
//允许共享读写操作
//若文件不存在则创建它,当我们要从BMP中解析出txt文件时是不存在的 ,(不过我感觉这里用CREATE_ALWAYS会不会也可以)
//默认属性
if (hfile == INVALID_HANDLE_VALUE)
{
cout << "Can't open" << filename << endl;
return false;
}
SetFilePointer(hfile, 0, 0, FILE_BEGIN); //将句柄指到文件首
DWORD dwWritten; //保存写了多少字节到文件中
WriteFile(hfile, buf, len, &dwWritten, 0);
//将数据写入文件
CloseHandle(hfile);
return true;
}
bool Hide(char *secretFileName,char *bmpFileName)
{
DWORD dwBMPSize, dwSecretSize;
char * lpBMP = GetFileContent(bmpFileName, &dwBMPSize); //存储原始的文件名
char * lpSecret = GetFileContent(secretFileName, &dwSecretSize);
DWORD * lpFirstPoint = (DWORD *)(lpBMP + 10);
//读取第一个像素的偏移字节
char * lpCurrentBMP = lpBMP + *lpFirstPoint + 3;
将指针指向作用较小的后三个字节
char * lpCurrentSecret = lpSecret;
//第一个像素点保存Secret文件大小
*((DWORD*)lpCurrentBMP) = dwSecretSize;
//保存写入的TXT文件大小,解析时使用
lpCurrentBMP += 6; //每个像素占6个字节
for (; lpCurrentBMP<(lpBMP + dwBMPSize) && lpCurrentSecret <(lpSecret + dwSecretSize); lpCurrentBMP += 6)
//不能大于bmp的大小
//循环将整个TXT文件依次写入缓冲区
{
if (dwSecretSize>2)
{
*lpCurrentBMP = *lpCurrentSecret;
*(lpCurrentBMP + 1) = *(lpCurrentSecret + 1);
*(lpCurrentBMP + 2) = *(lpCurrentSecret + 2);
lpCurrentSecret += 3;
dwSecretSize -= 3;
}
else if(dwSecretSize==2)
{
*lpCurrentBMP = *lpCurrentSecret;
*(lpCurrentBMP + 1) = *(lpCurrentSecret + 1);
break;
}
else if (dwSecretSize==1)
{
*lpCurrentBMP = *lpCurrentSecret;
break;
}
else
{
break;
}
}
SaveFile(lpBMP, dwBMPSize, bmpFileName);
//将写入了txt数据的缓冲区,写入bmp图片
delete[] lpBMP;
delete[] lpSecret;
return true;
}
bool Recovery(char *bmpFileName,char *secretFileName)
{
//将保存了的TXT信息读出到缓冲区,操作与写入类似
DWORD dwBMPSize;
char * lpBMP = GetFileContent(bmpFileName, &dwBMPSize);
DWORD * lpFirstPoint = (DWORD *)(lpBMP + 10);
cout << "First point offset : " << *lpFirstPoint << endl;
DWORD dwSecretSize = *(DWORD *)(lpBMP + *lpFirstPoint + 3);
//读取存储的TXT的文件大小
cout << dwSecretSize << endl;
cout << "Secret file size : " << dwSecretSize << endl;
char * SecretBuf = new char[dwSecretSize];
char * lpCurrentBMP = lpBMP + *lpFirstPoint + 3 + 6;
for (int i = 0; lpCurrentBMP<(lpBMP + dwBMPSize) && i<dwSecretSize; lpCurrentBMP += 6)
{
SecretBuf[i] = *lpCurrentBMP;
SecretBuf[i + 1] = *(lpCurrentBMP + 1);
SecretBuf[i + 2] = *(lpCurrentBMP + 2);
i += 3;
}
SaveFile(SecretBuf, dwSecretSize, secretFileName);
delete[] SecretBuf;
delete[] lpBMP;
return true;
}
int main(int argc, char *argv[])
{
if (argc<3)
{
cout << "Usage " << argv[0] << " Encrypt secret_file_name BMP_file_name"<<endl;
cout << "Usage " << argv[0] << " Decrypt secret_file_name BMP_file_name" << endl;
return 0;
}
if (strcmp(argv[1], "Encrypt") == 0)
{
Hide(argv[2], argv[3]);
}
else if (strcmp(argv[1],"Decrypt")==0)
{
Recovery(argv[3], argv[2]);
}
else
{
cout << argc << endl;
cout << argv[1] << endl;
cout << "Invalid para" << endl;
}
cout << "Done!" << endl;
return 0;
}
整个程序简单有趣,但将WINAPI文件读写的强大功能体现的淋漓尽致,相应的Win32控制台程序戳这里
http://download.csdn.net/detail/avalon_y/9532526