记录最近工作中遇到的一些坑
最近公司给我分派了一个写游戏资源清理器的任务(清理游戏中不再使用的图片资源,主要是一些活动地图用到的资源),由于历史原因必须要使用MFC编写界面,因此遇到了很多坑,在此记录一下,防止以后再踩。
本文为大便一箩筐的原创内容,转载请注明出处,谢谢:http://www.cnblogs.com/dbylk/p/4904106.html
一、关于C/C++的文件输入输出
由于公司C++库里提供的Log输出到文件的功能比较简单,想要显示时间和一条语句打印多个变量比较麻烦,所以我就自己写了一个简单的Log类,但实现的过程中遇到了很多Bug,大部分都与C/C++提供的文件输入输出函数有关。
1. C++的fstream::open不支持打开含中文命名的文件
最开始我使用的Log文件都是以英文命名直接放在根目录下面,一直没出现什么问题。后来因为想实现一个通过Log还原备份文件的功能,我把Log文件放在了一个名字包含中文的文件夹里,结果发现Log文件打不开,测了半天都没找到原因,后来才抱着试试看的心态把中文文件夹换成了英文,没想到就跑通了。然后网上一搜才知道原来C++的fstream默认不支持中文路径,想要使用中文路径的方法网上有很多这里我就不贴了。这里需要提一下的是C提供的FILE(fopen函数)是默认支持中文路径的。
2. C的fopen与C++的fstream::open都只能在已经存在的文件夹中创建文件,不支持根据输入的路径创建文件夹
这个问题也是因为想要把Log文件集中放在程序根目录下的文件夹里才发现的。想要解决这个问题可以通过使用MFC提供的CreateDirectory函数或C提供mkdir函数先创建文件夹,值得一提的是这俩个函数也都不支持创建多级目录 —A—)。。。为此我还得单独用递归实现一个CreateMultiLevelDirectory的函数。
3. 使用C的fwrite函数或C++的fstream::write函数向文本输出大量数据时发生写入中断与数据丢失的情况
这个问题是因为这两个函数在输出时都会在内存中创建一个缓冲区。如果不做任何处理的话,数据会被先写入到内存缓冲区,再由系统决定何时写入磁盘。这样一来,当频繁调用这俩个函数输出数据时(本人[大便 一箩筐]测试时,每次输出1KB左右的数据,总共大约在5MB ~ 10MB之间就会出错),就会产生缓冲区被填满而无法继续写入导致数据丢失的情况。解决的方法是每次输出少量数据时,就调用一次C的fflush函数或C++的fstream::flush函数,刷新缓冲区,确保缓冲区中的数据被写入磁盘。当使用C++的"fstream << endl"(实际上是对endl(fstream)函数的重载)时,在endl中也使用了同样的方法刷新缓冲区。(据说C的read与write函数因为不会创建缓冲区,所以不会导致这个问题,具体有待验证。)
4. 关于Windows平台下的文件换行
这个问题本人[大 便一箩筐]当初还在学校的时候就被坑过,在此一起做一个整理。在Windows下输出Log的时候,仅使用'\n'输出文本,再用记事本(notepad.exe)打开是看不到文件换行的,这是因为在Windows系统中,标准的换行符是“回车换行符”,即"\r\n",只有当这两个符号同时出现时,才会换行。而在Linux系统中,只需要输入'\n'就可以起到换行的效果(Linux系统中并没有对'\r'进行定义)。不过当我们使用一些第三方文本编辑程序时(比如notepad++),一般都是支持'\n'换行显示的。
不过我在这里提到这个问题,主要是因为使用C提供的FILE读取文件时,并没有一个方便的方法可以逐行读取文本。虽然C标准库中的fgets函数可以读取文本中的一行数据,但获取后的数据末尾带有换行符(C++的getline函数获取的字符串末尾不带换行符),因此我们在把数据读入缓存后,需要判断字符串的最后一个字符是否为'\n',若是则将它置为'\0'(正常情况下末尾都有'\n'),同时还要注意清除'\r'符号。
此外,使用fgets逐行获取文本中的字符串时,建议直接使用fgets函数的返回值作为是否读到文件末尾的标志,而不要使用feof函数判断。这是因为fgets读到文本末尾,且没有读到任何字符时,就不会改变缓冲区的内容,而是直接返回NULL值。当使用feof作为终止读取的判断条件时,若文本最后一行为空,则fgets会获取一个单独的EOF标志,但由于没有读到任何数据,所以会它不会修改缓冲区中的内容(不会写入'\0'符号),由此导致的结果是文本的最后一行字符会被处理2次。
// 不推荐的写法 Author: 大便一箩筐
char cBuffer[1024]; while(feof(pFile)) { // 若这一步读到一个单独的文件终止符,由于没有读到数据,上一次读取的内容还在cBuffer中,不会被修改 fgets(cBuffer, 1024, pFile); /* 其他操作 */ }
// 推荐的写法 Author: 大便一箩筐 char cBuffer[1024]; while(fgets(cBuffer, 1024, pFile)) { /* 其他操作 */ }
不过需要注意的是,若fgets获取文本内容出错时,也会返回NULL值,若有需要可以在while结束后通过调用ferror函数判断,并做进一步的处理。
二、使用消息在线程之间传递数据
1. PostMessage与SendMessage
由于本人[大便一 箩筐]想要实现进度条功能,需要在前台的界面线程和后台的处理线程之间同步任务进度,所以采用了消息作为传递数据的方法。Windows API提供了两个函数用于发送消息:PostMessage与SendMessage。它们之间的区别是PostMessage只负责将消息传递到目标窗口的消息队列,而不关心消息是否成功被目标线程接收;而SendMessage在消息发送之后会阻塞当前线程,确认消息被目标窗口处理之后才会继续执行下一步操作。本人[大便一箩 筐]最初使用了PostMessage传递消息,然后发现进度条经常抽风,随后改用SendMessage,问题解决。
2. 关于PostMessage的参数问题
使用PostMessage函数在线程之间传递数据时,数据必须放在一个自定义的消息结构体中,并将结构体对象的指针作为参数传递给消息接收对象,这样一来会导致一个问题:若参数使用的是局部变量,由于PostMessage将消息放到目标窗口的消息队列后会立即返回,有可能窗口还未对参数进行处理,参数(指针)所指向的局部变量就已经因为发送函数执行完毕而被销毁了。虽然此时不会提示内存访问错误,但往往会使目标窗口取得一个不正确的参数对象,继而导致不可预知的结果。解决的办法是定义一个全局的消息队列,使用PostMessage传递消息时,将参数对象加入到队列中,再将队列中的消息指针传递给目标窗口,目标窗口处理完毕后再将参数对象从队列中弹出;另一个方法就是使用SendMessage函数发送消息,由于SendMessage在消息被处理完毕前会被阻塞,所以消息参数的局部对象并不会被销毁。
三、Window的文件目录
1. 关于Windows系统中路径的比较
- Windows系统可以同时兼容包含斜杠'/'与反斜杠'\\'的路径,因此当对比两个路径是否相同时,需要将路径的目录间隔符进行统一
- 对比文件夹路径时,需要注意末尾是否包含目录间隔符(斜杠'/'或反斜杠'\\')
- 对比路径时,需要注意路径中是否包含'.'(当前目录)和'..'(上级目录),如果有的话需要将这两种符号消除
- 注意路径是相对路径还是绝对路径
2. 关于遍历Windows系统中的文件目录
Windows系统提供了WIN32_FIND_DATA数据结构和FindFirstFile、FindNextFile、FindClose函数用于遍历文件目录。但使用时需要注意它们会访问到文件夹中隐藏的'.'(当前目录)和'..'(上级目录),因此使用递归遍历文件目录和它的子目录时,一定要记得剔除这两种情况,否则程序会陷入死循环。