MFC文件读取

参考文章

http://www.360doc.com/content/10/0604/09/799_31186780.shtml#

http://pursuitweal.ycool.com/post.2655843.html

计算机是如何管理自身所存放着大量的信息的呢? windows 的磁盘管理程序为我们提供了一套严密而又高效的信息组织开工 --- 硬盘上的信息是以文件的形式被管理的。

面向存储的文件技术 
什么是文件? 计算机中,一篇文章、一幅图画、一个程序、一首歌曲等都是以文件的形式存储在磁盘上的,每个文件都有一个文件名。计算机就是对文件按名存取的。文件名的格式如下:主文件名 . 扩展名

文件名由主文件名和扩展名两部分组成,中间用小圆点隔开,其中扩展名可以省略。而扩展名是用来区分文件类型的。 Windows 为了区分文件的类型,一些软件系统会自动给文件加上“ .wps ”扩展名;画图程序画的图像文件一般为“ .bmp ”等。

在 windows 中,主文件名可以由英文字符、汉字、数字以及一些符号等组成,但不能使用 +<>*?/ 等符号。

什么是文件夹? 
在计算机中存放着数以万计的文件,为了便于管理这些文件,就像我们把文件分类放到不同的抽屉中便于查阅一样,在计算机中也有像抽屉的东西,它就是文件夹。文件夹也要有一个名字,取名的原则与文件的取名类似,只是不用再区分文件夹的类型,当文件夹多了以后,还可以把某些文件夹归到一个大文件夹中去。久而久之,就构成了计算机中庞大的磁盘文件结构。

为什么要在程序中使用文件? 
通常,程序中的数据在程序运行结束后,就会从内存中清除,再次运行程序时不会自动出现。在编制程序的过程中不可避免地会遇到将某些数据永久保存的问题,当关闭程序后,依然可以使用这些数据,这时就需要进行文件操作。

文件类型 
Visual c++ 处理的文件通常分为两种:

文本文件: 只可被任意文本编辑器读取 ASCII 文本。

二进制文件: 指对包含任意格式或无格式数据的文件的统称。

这里只介绍文本文件的读写, INI 文件也属于文本文件的范畴,且 INI 文件的结构和用途与普通的文本文件不同,所以会单独介绍。

第一部分:文本文件 
文本文件的读写 
认识 CFile 类;认识文本文件;能够正确灵活应用文本文件存取信息;避免文本文件读写的常见误区。

CFile 是 MFC 的文件操作基本类,它直接支持无缓冲的二进制磁盘 I/O 操作,并通过其派生类支持文本文件、内存文件和 socket 文件。

 

客户操作记录实例功能预览及关键知识点 
许多系统,出于安全或其他原因,常常要求随时对键盘进行监控,利用 Hook (钩子)技术编写的应用程序能够很好地达到这个目的。本软件就制作了一个客户操作记录软件,即在软件运行过程中,用户在键盘上的按键操作会被记录下来,这样对维护软件的正常运行非常有利。

只要启动客户操作记录软件后,不管输入焦点是否在本软件上,按键都会被记录下来。我们需要的是键盘的系统监控,只要本软件在运行,无论当前计算机在做什么,都能监测到用户按键的行为并做出反应,这就要用到 Hook 技术。

Hook 技术在很多特殊软件中广泛应用,如,金山词霸的“取词”功能,就用到了 Hook 计技术。

钩子的本质是一段用以处理系统消息的程序,通过系统调用,将其挂入系统。钩子的种类很多,每种钩子可以截获并处理相应的消息,每当特定的消息发出,在到达目的窗口之前,钩子程序先行截获该消息、得到对此消息的控制权。此时在钩子函数中就可以对截获的消息进行加工处理,甚至可以强制结束消息的传递。

从钩子的本质来看,可以优先截获操作系统的各种消息进行处理,所以它几乎无所不能,因为 windows 的应用程序都是基于消息驱动的,应用程序的操作都依赖于它所得到的消息的类型及内容。

如果 Hook 过程在应用程序中实现,若应用程序不是当前窗口时,该 Hook 就补齐作用;如果 Hook 在 DLL 中实现,程序在运行中动态调用它,它能实时对系统进行监控。根据需要,我们采用的是在 DLL 中实现 Hook 的方式。

(应用程序 exe? 和 DLL 的区别所在)

 

文本文件存储原理 
字符被计算机处理时都是以二进制代码的形式出现的,即一个字符对应一个 8 位二进制数,这种二进制码的集合就是所谓的 ASCII 码。

基本的 ASCII 码有 128 个,最高位都是 0 ,对应的十进制数是 0-127 。键盘上的字符,如英文字母、数字和一些常用符号,使用基本 ASCII 部分。如数字“ 0 ”的 ASCII 码用二进制数表示就是 00110000 (即十进制数 48 )。

扩展的 ASCII 码有 128 个,最高位是 1 ,对应的十进制数是 128-255 。一些制表符和其他符号使用扩展的 ASCII 码部分。

为解决汉字的存储和显示问题,我国制定了国际 GB2312 。据此规定,一个汉字由 2 个扩展的 ASCII 码组成,这种高位为 1 的双字节汉字编码就是汉字的机内码,简称为内码。例如,汉字“学”的机内码用二进制数表示就是 11010001 10100111 (即十进制数 206 和 167 ),用十进制表示就是 53671 ( 206*256+167 )。对于字符,文本文件存储的是它的 ASCII 码,对于汉字,文本文件存储的是它的内码,即两位 ASCII 码,如字符串“ 0 学 0 ”,在文本文件中存储的内容是 00110000 11010001 10100111 00110000

正确的文本文件读写过程 
1. 定义文件变量; 2. 打开指定的文件; 3. 向从文本文件中写入信息; 4. 从文本文件中读取信息; 5. 关闭文件

下面具体介绍如何实现这些过程。

1. 定义文件变量 
定义文件变量格式: CStdioFile 文件变量;

例如,定义一个名称为 f1 的文件变量,语句如下: CStdioFile f1;

2. 打开指定文件 
可以直接通过 CStdioFile 的构造函数来打开磁盘文件,同时可以用标志位指定打开方式(只读、只写、读写等):

CStdioFile(LPCTSTR lpszFileName,UINT nOpenFlags);

其中, lpszFileName 表示要打开的文件名,可以是相对路径或绝对路径

nOpenFlags 设置文件打开方式标志位,可以指定用“ | ”连接多个标志位。下面是常用的打开标志:

CFile::typeText :以文本文件的形式打开文件

CFile::typeBinary :以二进制文件的形式打开文件

CFile::modeCreate :如果指定文件名的文件不存在,则新建文件;如果文件存在并且没有设置 CFile::modeNoTruncate 标志,则清空文件。

CFile::modeNoTruncate :如果文件存在,不把它的长度删除为 0 (即不清空文件中的数据)。

CFile::modeRead :以只读方式打开文件

CFile::modeReadWrite :以可读可写方式打开文件

CFile::modeWrite :以只写方式打开文件

CFile::shareDenyNone :文件打开后,不禁止其他进程对文件的读写操作

CFile::shareExclusive :文件打开后,禁止其他进程对文件的读写操作

CFile::shareDenyRead :文件打开后,禁止其他进程对文件的读操作

CFile::shareDenyWrite :文件打开后,禁止其他进程对文件的写操作

此外,可以不在构造函数中打开文件,而仅仅调用空的构造函数 CStidoFile (),然后用 CStdioFile::Open() 打开文件。 Open 函数的前两个参数和非空构造函数的参数相同,其声明如下:

BOOL Open(LPCTSTR lpszFileName,UINT nOpenFlags,CFileException* pError=NULL);

第 3 个参数与打开失败时的异常处理有关。

实例 1 :以只读方式打开一个文件 
步骤:

使用 AppWizard 创建一个对话框应用程序,删除其自动产生的所有控件,添加一个 Button 控件。双击控件,在相应的函数里添加代码:

       char * pszFileName="C://myfile.txt";

       CStdioFile myFile;

       CFileException fileException;

 

       if(!myFile.Open(pszFileName,CFile::modeCreate|CFile::typeText|CFile::modeRead),&fileException)

       {

              TRACE("Can't open file %s, error = %u/n",pszFileName,fileException.m_cause);

       }

运行结果:如果 C:/ 下没有 myfile.txt 文件,则新生成该文件。

 

3. 向从文本文件中写入信息 
CStdioFile 提供了函数 WriteString 来向文本文件中写入文本, WriteString 函数的格式如下:

void WriteString(LPCTSTR lpsz);

WriteString 的参数 lpsz 是一个以 ”/0” 字符结束的字符串,要把这个字符串的内容写入文件。

提示 :使用 WriteString 函数时,如果希望每执行一次 WriteString ,文本文件中的内容就会自动换行一次,那么就需要在需要换行的地方输出“ /n ”:

myFile.WriteString(“ 第 1 行 /n”) ;

实例 2 :向文件中写入文本 
建立 MFC 基于对话框的程序,删除自动添加的所有控件,添加一个“确定”按钮,双击按钮,按默认添加事件函数,双击按钮,在相应的函数处添加如下代码:

       char* pszFileName="C://myfile.txt";

       CStdioFile myFile;

       CFileException fileException;

 

       if(myFile.Open(pszFileName,CFile::typeText|CFile::modeCreate|CFile::modeReadWrite),&fileException)

       {

              myFile.WriteString(" 第 1 行 /n");

              CString strOrder;

              strOrder.Format("%d,%.3f",66,88.88);

              myFile.WriteString(strOrder);

       }

       else

       {

              TRACE("Can't open file %s,error=%u/n",pszFileName,fileException.m_cause);

       }

程序运行结果: C:/myfile.txt 文件中内容如下:

第 1 行

66,88.880

 

4. 从文本文件中读取信息 
CStidoFile 提供了函数 ReadString 来读取文本, ReadString 有两种形式,一种为:

virtual LPTSTR ReadString(LPTSTR lpsz, UINIT nMax);

ReadString 函数的参数如下:

lpsz :是用户提供的一个指向字符串的指针,它用来接受从文件读出的文本,以 ”/0” 结束。

nMax 是本次所允许读入的文本字符个数,不计“ /0” 字符,也就是说最多能读入 nMax-1 个文本字符。

ReadString 的返回值是一个 LPTSTR 类型的指针,它指向从文件读出的文本字符串,如果到达文件尾,则返回 NULL 。

ReadString 的另一种形式为:

BOOL ReadString(CString& rString);

参数 rString 用来容纳从文件读出的文本。

CString 版本忽略回车换行符,返回值是一个布尔值。如果返回值为 FALSE ,表示因到达文件尾而没有读到任何字符。

提示: 每执行一次 ReadString ,就会自动从文本文件中读取一行数据,同时文件操作指针会自动跳转到下一行。

实例 3 :从文件中读取文本信息 
步骤:创建基于对话框的 MFC 程序,删除所有自动添加的控件,添加按钮控件,为按钮添加事件,并在相应的函数处,添加如下代码:

       char* pszFileName="C://myfile.txt";

       CStdioFile myFile;

       CFileException fileException;

       if(myFile.Open(pszFileName,CFile::typeText|CFile::modeReadWrite),&fileException)

       {

              myFile.SeekToBegin();

              CString str1;

              myFile.ReadString(str1);

 

              CString str2;

              myFile.ReadString(str2);

              AfxMessageBox(str1+str2);

       }

       else

       {

              TRACE("Can't open file %s,error=%u/n",pszFileName,fileException.m_cause);

       }

       myFile.Close();

程序运行结果:为程序 F9 设置断点,然后 F5 单步执行,结果如下:

 

 

 

5. 关闭文件 
对文件的操作完成后,使用 CloseFile 关闭文件。

函数 CStdioFile::Close 关闭一个文件,一般一个文件使用完毕就应该关闭它:

myFile.Close();

 

错误的文本文件读写过程 
在读写文本文件的时候,最常见的错误是 --- 操作文件不存在。这种错误产生的典型原因有:

1. 路径错误 
       char * pszFileName="C://Windows//MyFile.txt";

       CStdioFile myFile;

       CFileException fileException;

 

       if(!myFile.Open(pszFileName,CFile::modeCreate|CFile::typeText|CFile::modeReadWrite),&fileException)

       {

              // 文件操作代码

       }

       else

       {

              TRACE("Can't open file %s, error = %u/n",pszFileName,fileException.m_cause);

 

       }

       myFile.Close();

       由于将文件变量与一个绝对路径的文件名关联,而程序的数据通常存储在相对路径下,所以一旦相对路径和相对路径不一致时,就会出错。

       举例而言,上一段程序本意是想从 windows 的安装目录下面的 MyTextFile.txt 文件中读取一行数据,但是如果操作系统安装的路径不是 C:/Windwos ,而是 C:/Winnt, 那么这段程序就会出错。

              解决方法是在程序中使用相对路径,改正后的程序如下:

              // 获取 windows 路径

       LPTSTR lpBuffer=new char[MAX_PATH];

       ::GetWindowsDirectory(lpBuffer,MAX_PATH);

       strcat(lpBuffer,"//MyFile.txt");

 

       CStdioFile myFile;

       CFileException fileException;

       if(myFile.Open(lpBuffer,CFile::typeText|CFile::modeCreate|CFile::modeReadWrite),&fileException)

       {

              // 文件操作代码

       }

       else

       {

              TRACE("Can't open file %s, error = %u/n",pszFileName,fileException.m_cause);

 

       }

 

       CString strFileTitle="MyFile.txt";

       CStdioFile myFile;

       CFileException fileException;

 

       if(myFile.Open(strFileTitle,CFile::typeText|CFile::modeReadWrite),&fileException)

       {

              // 文件操作代码

              myFile.WriteString(" 测试! ");

       }

       else

       {

              TRACE("Can't open file %s, error = %u/n",pszFileName,fileException.m_cause);

       }

       myFile.Close();

2. 操作文件不存在 
如果应用程序所有路径下面不存在 MyFile.txt 文件,那么在 WriteString 写入信息时就会出错。

       解决办法就是在程序中打开文件前要检查是否存在此文件。如下程序:

       CString strFileTitle="MyFile.txt";

       CFileFind finder;

       if(finder.FindFile(strFileTitle))

       {

              CStdioFile myFile;

              CFileException fileException;

 

              if(myFile.Open(lpBuffer,CFile::typeText|CFile::modeCreate|CFile::modeReadWrite),&fileException)

              {

                     // 文件操作代码

              }

              else

              {

              TRACE("Can't open file %s, error = %u/n",pszFileName,fileException.m_cause);

              }

       }

       else

       {

              TRACE("Can't find file %s/n",strFileTitle);

       }

       myFile.Close();

 

3. 超越文件权限进行读写操作 
       在打开文件的过程中,通过参数指定了文件的读写权限,如果读写的操作没有和相应的权限对应,就会出现错误。

       下面的程序就是典型的忽略了文件操作权限的例子:

       CString strFileTitle="MyFile.txt";

       CStdioFile myFile;

       CFileException fileException;

       if(myFile.Open(strFileTitle,CFile::typeText|CFile::modeCreate|CFile::NoTruncate|CFile::modeRead),&fileException)

       {

              // 文件操作代码

              myFile.WriteString(" 测试 !");

       }

       else

       {

              TRACE("Can't open file %s,error=%u/n",strFileTitle,fileException.m_cause);

       }

       myFile.Close();

支招儿 : 
1. 准确定位文件的路径 
操作文件的过程中,经常需要将文本文件放在程序自身的目录中,但是如果仅仅在程序中使用不指定任何路径信息的相对路径,如:

    myFile.Open("MyFile.txt",CFile::modeCreate|CFile::typeText|CFile::modeReadWrite);

那么就有可能出现不能正确定位的情况,准确定位文件位置的方法是获得可执行程序自身的绝对路径,如:

    TCHAR FilePath[MAX_PATH];

    GetModuleFileName(NULL,FilePath,MAX_PATH);

       (_tcstchr(FilePath,'//'))[1]=0;

       lstrcat(FilePath,_T("MyFile.txt"));

 

       CStdioFile myFile;

       CFileException fileException;

       if(myFile.Open(FilePath,CFile::modeCreate|CFile::typeText|CFile::modeReadWrite),&fileException)

       {

              // 文件操作代码

       }

       else

       {

              TRACE("Can't open file %s,error=%u/n",FilePath,fileException.m_cause);

       }

       myFile.Close();

2. 读文本文件指定的一行,并得到文本文件的总行数。 
读文本文件指定的一行,并得到文本文件的总行数

要统计文本文件的总行数,可以从头逐行读,直到文件尾,程序:

 

       CStdioFile myFile;

       CFileException fileException;

       if(myFile.Open("MyFile.txt",CFile::modeCreate|CFile::modeNoTruncate|CFile::typeText|CFile::modeReadWrite),&fileException)

       {

              CString strContent;

              int order=1;

              while(myFile.ReadString(strContent))

              {

                     if(2==order)

                     {

                            AfxMessageBox(strContent);

                     }

                     order=order+1;

              }

       }

       else

       {

              TRACE("Can't open file");

       }

       myFile.Close();

 

实例演示文件操作过程 
客户操作记录实例 
本软件分为两个部分,一部分是 DLL 模块,里面利用 Hook 技术完成键盘监控和写入文件的功能;另一部分是界面部分,调用 DLL 启动和停止客户操作记录功能。

第 1 步:创建 MFC DLL 项目

第 2 步:创建 TestHook.h 文件

第 3 步:加入全局共享数据变量

第 4 步:保存 DLL 实例句柄

第 5 步:类 CKeyboradHook 的成员函数

第 6 步:创建钩子可执行程序

第 1 步:创建 MFC DLL 项目 
创建一个名为 HookTest 的 project , project 的类型为选择 MFC AppWizard(DLL),DLL 类型为 MFC Extension DLL(using shared MFC DLL)

注意: 选择 File->New 菜单项,在弹出对话框的左边的列表框中选择 MFC AppWizard(DLL).

在 project name 文本框中输入项目名称, HookTest ; location 中输入项目的存盘路径;选中 Create new workspace ;在 platForms 列表中选择 Win32 选项。

单击 OK 按钮继续下一步,在弹出的对话框中设置 DLL 类型为 MFC Extension DLL ( using shared MFC DLL ) .

在 IDE 中,选择 FileView 选项卡,在其中就会发现其中有 HookTest.cpp 文件,却没有 HookTest.h 文件,这是因为 visual C++6.0 中没有现成的钩子类,所以要自己动手创建 TestHook.h 文件,在其中建立钩子类。

第 2 步:创建 TestHook.h 文件 
选择 File 菜单,再选择 New 菜单项,将弹出 New 对话框。选择 files 选项卡,并且选择其中的 C/C++ Header File.

选中 add to project ,并且在对应的下拉列表中选择项目名称 HookTest ;在 location 文本框中输入项目的存盘路径,或单击右边的按钮选择相应的路径;在 file 对应的文本框中输入文件名 HookTest.h ;单击 OK 按钮,在 IDE 中自动打开 Hooktest.h 文件供编辑代码用;

TestHook.h 文件:

#if _MSC_VER>1000

#pragma once

#endif //_MSC_VER>1000

 

class AFX_EXT_CLASS CHookTest:public CObject

{

public:

       CHookTest();

       ~CHookTest();

       BOOL StartHook(); //StartHook() 函数实现安装钩子

       BOOL StopHook(); //StopHook() 函数实现卸载钩子

};

第 3 步:加入全局共享数据变量 
HookTest.cpp 文件中添加:

// 存储各个键赌赢的字符

CString cskey[TOTAL_KEYS]=

{

       "BACKSPACE",

       "TAB",

       ……

       "F12",

};

// 存储各个键对应的键值

int nkey[TOTAL_KEYS]=

{

       0X08, //"BACKSPACE",

       0X09, //"TAB",

    …….

       0x7b,//"F12",

};

#pragma data_seg("mydata")

// 安装的键盘钩子子句柄

HHOOK glhTestHook=NULL;

//DLL 实例句柄

HINSTANCE glhkInstance=NULL;

#pragma data_seg()

 

第 4 步:保存 DLL 实例句柄 
DllMain 函数中添加如下代码:

       if (dwReason == DLL_PROCESS_ATTACH)

       {

              TRACE0("HOOKTEST.DLL Initializing!/n");

              // 扩展 DLL 仅初始化一次

              if (!AfxInitExtensionModule(HookTestDLL, hInstance))

                     return 0;

              //DLL 加入动态 MFC 类库中

              new CDynLinkLibrary(HookTestDLL);

              // 保存 DLL 实例句柄

              glhkInstance=hInstance;

       }

       else if (dwReason == DLL_PROCESS_DETACH)

       {

              TRACE0("HOOKTEST.DLL Terminating!/n");

              // 终止这个链接库前调用它

              AfxTermExtensionModule(HookTestDLL);

       }

       return 1;     // ok

第 5 步:类 CKeyboradHook 的成员函数 
//KeyboradProc 函数

LRESULT WINAPI KeyboradProc(int nCode,WPARAM wParam,LPARAM lParam)

{

       for(int i=0;i<TOTAL_KEYS;i++)

       {

              if(nkey[i]==(int)wParam)

              {

                     int nKeyStatus=lParam &0x80000000;

                     // 根据用户按键播放对应的声音文件

                     switch(nKeyStatus)

                     case 0: //WM_KEYUP

                            //case 0x80000000://WM_KEYUP

                     {

                            char* pszFileName="C://myfile.txt";

                            CStdioFile myFile;

                            CFileException fileException;

 

                            if(myFile.Open(pszFileName,CFile::typeText|CFile::modeCreate|CFile::modeNoTruncate|CFile::modeReadWrite),&fileException)

                            {

                                   myFile.SeekToEnd();

                                   // 将文件指针移动到文件末尾准备进行追加文本的操作

                                   // 此处可以编写追加文本的操作

                                   myFile.WriteString(cskey[i]);

                            }

                            else

                            {

                     TRACE("Can't open file %s,error=%u/n",pszFileName,fileException.m_cause);

                            }

                     }

              }

       }

       // 调用 CallNextHookEx 函数把钩子信息传递给钩子链的下一个钩子函数

       return CallNextHookEx(glhTestHook,nCode,wParam,lParam);

}

第 6 步:创建钩子可执行程序 
//****************************

BOOL CHookTest::StartHook()

{

       glhTestHook=SetWindowsHookEx(WH_KEYBOARD,KeyboradProc,glhkInstance,0);

       if(glhTestHook!=NULL)

              return TRUE;

       return FALSE;

}

//****************************

/*

HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,INSTANCE hMod,DWORD dwThreadId)

idHook: 钩子类型,它是和钩子函数类型一一对应的,例如, WH_KEYBOARD 表示安装的是键盘钩子, WH_MOUSE 表示的是鼠标钩子等。

lpfn :钩子函数的地址

hMod: 钩子函数所在的实例的句柄,对于线程钩子,该参数为 NULL ;对于系统钩子,该参数为钩子函数的 DLL 句柄

dwThreadId: 指定钩子所监视的线程的线程号,对于全局钩子,该参数为 NULL.

SetWindowsHookEx 返回所安装的钩子句柄。

调用 StartHook 函数后,所有键盘的消息都会转移到 KeyboradProc 函数中,通过数组 nkey 的值与 wParam 参数相比较,可以知道用户按下的是哪个键,通过对 IParam 值的判断,可以知道是按下键还是释放键,然后播放键对应的声音文件即可。

 

*/

//****************************

// 卸载钩子

BOOL CHookTest::StopHook()

{

       BOOL bResult=FALSE;

       if(glhTestHook)

       {

              bResult=UnhookWindowsHookEx(glhTestHook);

              if(bResult)

              {

                     glhTestHook=NULL;

              }

       }

       return bResult;

}

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值