前言

windows常见的文件操作有日志、操作配置文件、ini、注册表、音视频的文件存储。
而linux系统具有一切皆文件的概念。


正文

c/c++那会都是打开一个文件然后以什么模式,用完还要关闭。


c/c++ win32 mfc文件操作

c文件操作

fopen函数

1
2
3
4
_ACRTIMP FILE* __cdecl fopen(
_In_z_ char const* _FileName,
_In_z_ char const* _Mode
);

fopen_s函数

1
2
3
4
5
_ACRTIMP errno_t __cdecl fopen_s(
_Outptr_result_nullonfailure_ FILE** _Stream,
_In_z_ char const* _FileName,
_In_z_ char const* _Mode
);

fwrite函数

1
2
3
4
5
6
_ACRTIMP size_t __cdecl fwrite(
_In_reads_bytes_(_ElementSize * _ElementCount) void const* _Buffer,
_In_ size_t _ElementSize,
_In_ size_t _ElementCount,
_Inout_ FILE* _Stream
);

带s一般都是所谓的安全函数

几种模式_Mode

  1. a add
  2. r read
  3. w weite
    • 要求文件存在,权限估计也比较高

然后随便写个按钮绑定一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void CMyCFileDlg::OnBnClickedWriteFile(){
//C 写文件
FILE *pFile = fopen("1.txt", "w"); //以写入模式打开文件
if (pFile == NULL){
MessageBox(_T("文件打开失败"));
return;
}

char szBuf[1024] = "c language file";

fwrite(szBuf, 1, strlen(szBuf)+1, pFile);

//用完关闭
fclose(pFile);
}

点击按钮之后文件夹目录就会多了这个1.txt,内容也是我们写的szBuf。
具体可以看文件创建时间是否符合我们刚才按下按钮的时候。

然后使用读文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CMyCFileDlg::OnBnClickedReadFile(){
//c 读文件
FILE *pFile = fopen("1.txt", "r");
if (pFile == NULL){
MessageBox(_T("文件打开失败"));
return;
}

char szbuf[1024] = { 0 };
int len = fread(szbuf, 1, 1024, pFile);

fclose(pFile);
MessageBox(szbuf);
}

实现起来也比较简单。

不过这里有个点要注意,因为读文件的时候文件不一定就写满了1024.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void CMyCFileDlg::OnBnClickedReadFile(){
//c 读文件
FILE *pFile = fopen("1.txt", "r");
if (pFile == NULL){
MessageBox(_T("文件打开失败"));
return;
}

char szbuf[1024] = { 0 };
//fseek 求文件的偏移量
fseek(pFile, 0, SEEK_END);
int fLen = ftell(pFile); //等到文件指针的当前位置
fseek(pFile, 0, SEEK_SET); //前面的end会使指针跑到最后后面导致读数据都是空的
int len = fread(szbuf, 1, fLen, pFile);

fclose(pFile);
MessageBox(szbuf);
}

要注意偏移之后要让指针回到起始位置。


c++文件操作

c++是以类作为核心的语言,所以文件也通过一个类读写。std::ofstream

使用前记得

#include
using namespace std;

不然没法用这个类。解锁命名空间也是为了写着方便

1
2
3
4
5
6
7
void CMyCFileDlg::OnBnClickedWriteFile(){
//c++ 写
ofstream ofs("2.txt");
char szBuf[1024] = "c++ file edit";
ofs.write(szBuf, strlen(szBuf) + 1);
ofs.close();
}

写起来也非常简单

顺便把读文件也写了,到时候再看。

1
2
3
4
5
6
7
8
void CMyCFileDlg::OnBnClickedReadFile(){ 
//c++ 读
ifstream ifs("2.txt");
char szBuf[1024] = { 0 };
ifs.read(szBuf, 1024);
ifs.close();
MessageBox(szBuf);
}

效果自然是没啥问题的。


win32 api

其实跟之前进程用到过

1
2
3
4
5
6
7
8
9
CreateFileW(
_In_ LPCWSTR lpFileName, //创建或打开的对象名称
_In_ DWORD dwDesiredAccess, //访问方式 读 写 查
_In_ DWORD dwShareMode, //共享方式 0
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, //NULL 不能被进程继承
_In_ DWORD dwCreationDisposition, //如何创建文件NEW ALWAYS
_In_ DWORD dwFlagsAndAttributes, //设置文件属性
_In_opt_ HANDLE hTemplateFile //NULL
);

至于a和w就是多字节和Unicode的区别。

写文件的方式

1
2
3
4
5
6
7
WriteFile(
_In_ HANDLE hFile,
_In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer,
_In_ DWORD nNumberOfBytesToWrite,
_Out_opt_ LPDWORD lpNumberOfBytesWritten,
_Inout_opt_ LPOVERLAPPED lpOverlapped
);

随便写写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void CMyCFileDlg::OnBnClickedWriteFile(){
//win32 write
HANDLE hFile;
hFile = CreateFile("3.txt", GENERIC_WRITE, NULL, NULL,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL
);
if (hFile == INVALID_HANDLE_VALUE){
MessageBox("创建文件对象失败");
return;
}

DWORD dwWrite;
char szBuf[1024] = "win32 api edit file";
WriteFile(hFile, szBuf, strlen(szBuf) + 1, &dwWrite, NULL);

CloseHandle(hFile);
}


void CMyCFileDlg::OnBnClickedReadFile(){
//win32 read
HANDLE hFile;
hFile = CreateFile("3.txt", GENERIC_READ, NULL, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
);
if (hFile == INVALID_HANDLE_VALUE){
MessageBox("创建文件对象失败");
return;
}

DWORD dwRead;
char szBuf[1024] = { 0 };
ReadFile(hFile, szBuf, 1024, &dwRead, NULL);

CloseHandle(hFile);
MessageBox(szBuf);
}

这里有点比较麻烦,就是write的时候,好像只能创建一次
hFile = CreateFile("3.txt", GENERIC_WRITE, NULL, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);

其中CREATE_NEW就是新建的意思,但是如果存在了他就会报错,但改成其它的感觉又不太合适。先凑合用。


MFC

写法跟c++比较类似吧

1
2
3
4
5
6
7
void CMyCFileDlg::OnBnClickedWriteFile(){
//mfc write
CFile cf("4.txt", CFile::modeCreate | CFile::modeWrite);
char szBuf[1024] = "mfc edit files";
cf.Write(szBuf, strlen(szBuf) + 1);
cf.Close();
}
1
2
3
4
5
6
7
8
void CMyCFileDlg::OnBnClickedReadFile(){
//mfc read
CFile cf("4.txt", CFile::modeRead);
char szBuf[1024] = { 0 };
cf.Read(szBuf, 1024);
cf.Close();
MessageBox(szBuf);
}

这样最基础的操作肯定是没问题的。

不过读文件好像还有别的骚操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void CMyCFileDlg::OnBnClickedReadFile(){
//mfc read

CFileDialog fileDlg(TRUE);
fileDlg.m_ofn.lpstrTitle = "Test";
//过滤器
fileDlg.m_ofn.lpstrFilter = "Text Files(*.txt)\0*.txt\0All Files(*.*)\0*.*\0\0";

if (IDOK == fileDlg.DoModal()){
CFile cf(fileDlg.GetFileName(), CFile::modeRead);
DWORD dwFileLen = cf.GetLength();
char szBuf[1024] = { 0 };
cf.Read(szBuf, dwFileLen);
cf.Close();
MessageBox(szBuf);
}
}

点击读文件他会弹出一个文件夹让你选。
然后根据我们的过滤器,一种是text一种是all

随便打开其中一个都能读出来


配置文件的操作

配置文件的格式比较特殊,.ini
里面一般都是配置选项。
WritePrivateProfileString

1
2
3
4
5
6
WritePrivateProfileStringW(
_In_opt_ LPCWSTR lpAppName,
_In_opt_ LPCWSTR lpKeyName,
_In_opt_ LPCWSTR lpString,
_In_opt_ LPCWSTR lpFileName
);

w和a就是对这些字符的要求不一样。

然后瞎写一个

1
2
3
4
5
6
7
8
9
10
11
void CMyCFileDlg::OnBnClickedWriteFile(){
//ini write
char szPath[MAX_PATH] = { 0 };
GetCurrentDirectory(MAX_PATH, szPath);
CString szPathFile;
szPathFile.Format("%s\\Test.ini", szPath);
//瞎写的。
WritePrivateProfileString("man", "friend", "张三", szPathFile);
WritePrivateProfileString("man", "student", "李四", szPathFile);
WritePrivateProfileString("school", "teacher", "王五", szPathFile);
}

启动之后点击写文件

可以在文件夹目录下看到我们写的配置。

至于读文件,也有点相似吧
要用到这个玩意

1
2
3
4
5
6
7
8
GetPrivateProfileStringW(
_In_opt_ LPCWSTR lpAppName,
_In_opt_ LPCWSTR lpKeyName,
_In_opt_ LPCWSTR lpDefault,
_Out_writes_to_opt_(nSize, return + 1) LPWSTR lpReturnedString,
_In_ DWORD nSize,
_In_opt_ LPCWSTR lpFileName
);

额,这里ini格式瞎写的,所以读出来的时候可能看着太怪了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void CMyCFileDlg::OnBnClickedReadFile(){

//ini read
char szPath[MAX_PATH] = { 0 };
GetCurrentDirectory(MAX_PATH, szPath);
CString szPathFile;
szPathFile.Format("%s\\Test.ini", szPath);

char dwKey[1024] = { 0 };
char dwKeyName[1024] = { 0 };
char dwValue[1024] = { 0 };
GetPrivateProfileString("man", "friend", NULL, dwKey, 1024, szPathFile);
GetPrivateProfileString("man", "student", NULL, dwKeyName, 1024, szPathFile);
GetPrivateProfileString("school", "teacher", NULL, dwValue, 1024, szPathFile);

//cstring拼接
CString strShow;
strShow.Format("friend:%s student:%s teacher:%s", dwKey, dwKeyName, dwValue);
MessageBox(strShow);
}

但反正最后还是读出了值:


注册表

注册表是存储在二进制文件里面的,win32api 提供了大量的函数操作注册表

额,默认打开的话,win+r是打开运行,然后输入regedit即可打开注册表,如果之前有用过就会很熟悉。

动注册表之前,vs需要用管理员启动,不然肯定是无法写入的。

项目还是之前那个没关系。

RegCreateKey创建指定的注册表项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
WINADVAPI
LSTATUS
APIENTRY
RegCreateKeyA (
_In_ HKEY hKey,
_In_opt_ LPCSTR lpSubKey,
_Out_ PHKEY phkResult
);
WINADVAPI
LSTATUS
APIENTRY
RegCreateKeyW (
_In_ HKEY hKey, //句柄,实际应该为分支
_In_opt_ LPCWSTR lpSubKey, //打开或创建的表项名称
_Out_ PHKEY phkResult //用来接收创建或打开的表项句柄
);
#ifdef UNICODE
#define RegCreateKey RegCreateKeyW
#else
#define RegCreateKey RegCreateKeyA
#endif // !UNICODE

这个微软就喜欢多字节和Unicode,从他头文件中各自宏定义去兼容这两种编码。

RegOpenKey 打开注册表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
WINADVAPI
LSTATUS
APIENTRY
RegOpenKeyA (
_In_ HKEY hKey,
_In_opt_ LPCSTR lpSubKey,
_Out_ PHKEY phkResult
);
WINADVAPI
LSTATUS
APIENTRY
RegOpenKeyW (
_In_ HKEY hKey,
_In_opt_ LPCWSTR lpSubKey,
_Out_ PHKEY phkResult
);
#ifdef UNICODE
#define RegOpenKey RegOpenKeyW
#else
#define RegOpenKey RegOpenKeyA
#endif // !UNICODE

RegSetValue写入注册表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
WINADVAPI
LSTATUS
APIENTRY
RegSetValueA (
_In_ HKEY hKey,
_In_opt_ LPCSTR lpSubKey,
_In_ DWORD dwType,
_In_reads_bytes_opt_(cbData) LPCSTR lpData, //存放的数据
_In_ DWORD cbData //要存放的值的大小长度
);
WINADVAPI
LSTATUS
APIENTRY
RegSetValueW (
_In_ HKEY hKey,
_In_opt_ LPCWSTR lpSubKey,
_In_ DWORD dwType,
_In_reads_bytes_opt_(cbData) LPCWSTR lpData,
_In_ DWORD cbData
);
#ifdef UNICODE
#define RegSetValue RegSetValueW
#else
#define RegSetValue RegSetValueA
#endif // !UNICODE

RegQueryValue检索与指定注册表项的默认值或未命名值关联的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
WINADVAPI
LSTATUS
APIENTRY
RegQueryValueA (
_In_ HKEY hKey,
_In_opt_ LPCSTR lpSubKey,
_Out_writes_bytes_to_opt_(*lpcbData, *lpcbData) __out_data_source(REGISTRY) LPSTR lpData,
_Inout_opt_ PLONG lpcbData
);
WINADVAPI
LSTATUS
APIENTRY
RegQueryValueW (
_In_ HKEY hKey,
_In_opt_ LPCWSTR lpSubKey,
_Out_writes_bytes_to_opt_(*lpcbData, *lpcbData) __out_data_source(REGISTRY) LPWSTR lpData,
_Inout_opt_ PLONG lpcbData
);
#ifdef UNICODE
#define RegQueryValue RegQueryValueW
#else
#define RegQueryValue RegQueryValueA
#endif // !UNICODE

那么写入的部分其实还是有点水

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void CMyCFileDlg::OnBnClickedWriteFile(){
//注册表 写
HKEY hKey;
DWORD dwWeight = 70;

//创建注册表
//DWORD dwRet = ::RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MYWEIGHT\\admin", &hKey);
DWORD dwRet = ::RegCreateKey(HKEY_CURRENT_USER, "Software\\HHH\\admin", &hKey);
if (dwRet != ERROR_SUCCESS){
MessageBox("创建注册表失败");
return;
}

//写注册表
dwRet = ::RegSetValueEx(hKey, "weight", NULL, REG_DWORD, (CONST BYTE *)dwWeight, 4);
if (dwRet != ERROR_SUCCESS){
MessageBox("写入注册表失败");
return;
}

//关闭注册表
::RegCloseKey(hKey);
}

然后跑起来看看注册表里面有没有我们写入的。

md,::RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MYWEIGHT\\admin", &hKey);这个注册表位置管理员打开的软件居然写不进去,我找了半天没找到,换了个地方写就马上见效,离谱,浪费我一堆时间啊。

然后读的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void CMyCFileDlg::OnBnClickedReadFile(){
//注册表 读
HKEY hKey;
DWORD dwRet = ::RegOpenKey(HKEY_CURRENT_USER, "Software\\HHH\\admin", &hKey);
if (dwRet != ERROR_SUCCESS){
MessageBox("打开注册表失败");
return;
}

//读或者查注册表
DWORD dwWight;
DWORD dwType;
DWORD dwSize;
CString strShow;
dwRet = ::RegQueryValueExA(hKey, "weight", 0, &dwType, (LPBYTE) &dwWight, &dwSize);
if (dwRet != ERROR_SUCCESS){
MessageBox("读取注册表失败");
return;
}
strShow.Format("Weight = %d", dwWight);

//关闭注册表
::RegCloseKey(hKey);
MessageBox(strShow);
}

其实写起来也不难,就是要填充这个参数问题。

效果就是这样了。


常规的文件等级

  1. 调试日志 debugview 文件日志、警告日志、错误日志 /五星
  2. 视频存储 /四星
  3. 文件传输CFile和Socket结合 /四星
  4. C语言和mfc的文件操作,win32api /三星
  5. windows的配置文件 /五星
  6. 注册表 病毒 逆向 /五星

结语

反正比较常用的文件操作还是以c语言和mfc为主吧,毕竟c用的很久了。mfc嘛自然在这个框架里面最好用。