最近还是没有什么好的题目,这篇文章也是炒炒冷饭。
这是大约两年前的事情了,当时我在做一个文件分析系统,要对很多文件类型进行判定,文件类型的判定千差万别,为了保证其灵活性,我打算使用DLL,增加一种新的文件,如果不能简单地根据字节位判断,那就新增一种DLL。
那么是由exe打开文件呢?还是由DLL打开文件呢?我想了一下,从效率和防止代码冗余上考虑,还是由exe打开文件比较好,接下去就简单了,给每个DLL定义统一的接口,将打开的文件的句柄传入,让DLL分析,返回结果,就OK了,但一个同事马上提醒我:“这种方法不可行。”我问为什么?他说以前尝试过,不信试试看。
结果对他不利,因为我的程序一点问题都没有,他想了想说以前DLL传递的不是文件句柄,是文件指针,FILE类型的文件指针,好吧,我也不妨试试看,果然!出现内存错误了。我跟他都很感兴趣,于是研究为什么?你想知道么?哈哈,我看还是让你自己试试看吧,这样印象深刻,我一开始就公布答案太没意思了。
创建一个DLL,随便叫什么,导出个函数,比如:
__declspec(dllexport) JudgeFile(FILE *pFile);
函数里边随便对pFile执行一些操作,比如:
__declspec(dllexport) int JudgeFile (FILE *pFile) { char data[10]; fread(data, 10, 10, pFile); return 0; }
调用也很简单:
FILE *pf = fopen("123.txt", "r"); if (pf==NULL) return -1; JudgeFile (pf); fclose(pf);
这样就行了,马上编译运行一下看看,出错了!一个红色的“X”,很亲切吧。
调试发现是DLL里出的错误,也就是那个fread函数,为什么?
因为:DLL有自己独立的代码段和数据段,它使用的库甚至可以跟exe的不一样而不会引起编译时的冲突,如果FILE*是由DLL打开并使用的,那一点问题都没有,那由exe打开就有问题了?是的,因为C库中的fopen其实最后都是对Win API中的CreateFile的调用,但除了这个调用之外,C库还在一个全局数据结构中记录了文件打开的情况及一些相关信息,所以用fopen打开一个文件,光是它返回的FILE*指针还不足以反映文件的全部状况,所有使用到FILE*指针的C库函数,都需要访问那个全局数据结构,问题来了,DLL拥有自己独立的代码段和数据段,那个全局数据结构,DLL访问的是自己的,而不是exe的,这样就出现了偏差,如果全局结构中根本没有要访问的文件的相关信息,硬是就要用这些不匹配的信息尝试操作文件,当然会导致内存出错啦。
那为什么传递一个文件句柄就没有问题?因为CreateFile打开文件返回的是一个内核对象句柄,这个句柄对整个进程来说,理解都是一致的,不存在额外的全局数据结构问题,所以请放心使用。
最后:细节决定成败。任何idea都需要坚固的技术支撑!