深入解析VisualC++文件系统编程

深入解析VisualC++文件系统编程

 

 

 

文件系统的查找、读写、删除等操作在程序设计中出现非常频繁。如何在较短的时间内找到实际项目中最佳解决方案,对前人工作积累的高效的代码进行分析和重用就显得十分重要。以目前Windows桌面开发中流行平台Visual C++为例。下文是笔者在多年实际项目开发中,积累的文件系统编程方面的一些经验,以此抛砖引玉,并对某些特殊问题进行详细分析。

  1.文件的查找

  MFC类库中的CFileFind类是专门的文件查找类。下面代码演示了该类的使用方法:

  CStringstrFileName;

  CFileFind fileFinder;

  BOOL bFinded = fileFinder.FindFile("C:\winnt\system32\*.dll");

  while(bFinded)

  {

      bFinded = fileFinder.FindNextFile();

      strFileName = fileFinder.GetFileTitle();

}

2.文件的读写

文件的读写编程非常普遍,其最常见方法是直接使用MFC类库的CFile声明一个对象,而后用这个对象的指针做参数声明一个CArchive对象,就可对复杂数据类型操作了。如下例示:

   //对文件进行写操作

   CString strTemp;

   CFile mFile;

   mFile.Open("c:\\try.TRY",CFile::modeCreate|CFile::modeWrite);

   CArchive ar(&mFile,CArchive::store);

   ar<<ar.Close();

   mFile.Close();

   //对文件进行读操作

   CFile mFile;

   if(mFile.Open("d:\dd\try.TRY",CFile::modeRead)==0)

   return;

   CArchive ar(&mFile,CArchive::load);

    ar>>strTemp;

     ar.Close();

   mFile.Close();

实际编程中,VC++中是以追加方式向文本文件写入数据情况较多。在c语言中,追加数据相当简单,设定writefile函数的写参数为“a+”就可以了。在用MFC中的CStdioFile类进行文件操作,读写时,参数modeNoTruncate的意思是不截取。测试中发现,WriteString写字符串之前要首先将指针定位到文件末尾:

CString str = "software week\r\n";

CStdioFilefile(strFile,CFile::modeCreate|CFile::modeNoTruncate|CFile::modeWrite);

file.SeekToEnd();//先定位到文件尾部

file.WriteString(strTmp);

file.Close;

3.文件复制、移动和删除操作

MFC中不提供这些操作具体类,要采用API中文件操作的相关函数如CopyFile()、CreateDirectory()、DeleteFile()、MoveFile()等。其调用形式简单,用法可详细参考MSDN。下面以具体应用中的临时目录和文件删除为例进行说明:

VC++只提供了删除一个空目录的函数,但实际中往往要求删除其下很多文件,需要开发用户自定义函数以实现这一功能。代码如下所示:

//函数功能: 删除指定路径下的指定文件,支持通配符

//参数: lpstrName:被删除的文件;strCurrentPath:找到的文件路径

void DelDirectoryPointFiles(LPSTRlpstrName, CString strCurrentPath)

{

    //删除指定路径下的指定文件,支持通配符

    //lpstrName:被删除的文件;strCurrentPath:找到的文件路径

    WIN32_FIND_DATAwinFileData;

    HANDLEhSearch;

    charszHome[MAX_PATH];

    DWORDdwRightWrong;

    DWORDdwNameLength;

    //当前的程序路径

    dwRightWrong= GetCurrentDirectory(MAX_PATH, szHome);

    dwRightWrong= SetCurrentDirectory(strCurrentPath);

    //保存程序执行路径,然后把当前路径设定为需要查找的路径

    hSearch= ::FindFirstFile(lpstrName, &winFileData);

    if(hSearch!= INVALID_HANDLE_VALUE)

    {

        dwNameLength= lstrlen(winFileData.cFileName);

        DeleteFile(winFileData.cFileName);

        while(::FindNextFile(hSearch,&winFileData))

        {

            //寻找下一个符合条件的文件,找到一个删除一个

            dwNameLength= lstrlen(winFileData.cFileName);

            DeleteFile(winFileData.cFileName);

        }

        FindClose(hSearch);// 关闭查找句柄    

    }

    dwRightWrong= SetCurrentDirectory(szHome);

}

至于删除子目录,则需要使用递归循环。读者可考虑用文件查找类CFileFind,利用IsDots(),IsDirectory()函数作为判断条件,进行编程实现。

4.临时文件的使用

  在软件安装过程中,你常会发现C:\winnt\Temp目录下有大量的扩展名为tmp的文件,这就是大部分商业化软件在安装运行时所建立的临时文件。临时文件应用也相当普遍,与普通文件操作不同的只是其文件名获取要调用函数GetTempFileName()。其第一个参数是建立此临时文件的路径,第二个参数是建立临时文件名的前缀,第四个参数用于得到建立的临时文件名。代码示例如下:

   charszTempPath[_MAX_PATH], szTempFile[_MAX_PATH];

   GetTempPath(_MAX_PATH, szTempPath);

   GetTempFileName(szTempPath, _T("app_"), 0, szTempfile);

   CFile tempFile(szTempfile,CFile:: modeCreate|CFile:: modeWrite);

   static const TCHAR sz[] = _T("software week");

tempFile.Write(sz, lstrlen(sz));

 tempFile.Close();

5.文件的打开/保存对话框

  当用户对所选择的文件打开或存储操作时,要用到文件打开/保存对话框。MFC的类CFileDialog用于实现这种功能。构造CFileDialog对象时,如果在参数中指定了OFN_ALLOWMULTISELECT风格,则在此对话框中可以进行多选操作。此时要重点注意为此CFileDialog对象的m_ofn.lpstrFile分配一块内存,用于存储多选操作所返回的所有文件路径名,如果不进行分配或分配的内存过小就会导致操作失败。演示代码如下:

  CFileDialogmFileDlg(TRUE,NULL,NULL,

OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_ALLOWMULTISELECT,

"All Files (*.*)|*.*||", AfxGetMainWnd());

CString str(" ",5000);

mFileDlg.m_ofn.lpstrFile = str.GetBuffer(5000);

str.ReleaseBuffer();

POSITION mPos = mFileDlg.GetStartPosition();

CString pathName(" ",MAX_PATH);

CFileStatus status;

while(mPos!=NULL)

{

       pathName=mFileDlg.GetNextPathName(mPos);

CFile::GetStatus(pathName, status );

}

VC经常需要选择一个文件夹,但并没有现成的函数。需要使用SHBrowseForFolder,代码如下:

#include<Shlobj.h> 

intSelFolder(HWND hParent, CString &strFolder)

{

    strFolder.Empty();

    LPMALLOC lpMalloc;

    if (::SHGetMalloc(&lpMalloc) !=NOERROR) return 0;

    char szDisplayName[_MAX_PATH];

    char szBuffer[_MAX_PATH];

    BROWSEINFO browseInfo;

    browseInfo.hwndOwner = hParent;

    browseInfo.pidlRoot = NULL; // 设桌面目录为根目录

    browseInfo.pszDisplayName = szDisplayName;

    browseInfo.lpszTitle = "选择目录";

    browseInfo.ulFlags =BIF_RETURNFSANCESTORS|BIF_RETURNONLYFSDIRS;

    browseInfo.lpfn = NULL;

    browseInfo.lParam = 0;

 

    LPITEMIDLIST  lpItemIDList;

    if ((lpItemIDList =::SHBrowseForFolder(&browseInfo)) != NULL)

    {  // 得到列表框中所选择的子项的目录路径

        if (::SHGetPathFromIDList(lpItemIDList,szBuffer))

        {

            if (szBuffer[0] == '\0') return 0;

            // 返回所得到的目录路径.szBuffer

            strFolder = szBuffer;

            return TRUE;

        }

        else  return TRUE; // strResult 为空

        // 释放资源

        lpMalloc->Free(lpItemIDList);

        lpMalloc->Release();

    }   

 return TREU;

}

6.配置信息INI文件操作

在我们编程中,总有一些配置信息需要保存下来。最常用的简单办法就是将这些信息写入INI文件中。在程序初始化时读入,在程序结束时保存到INI文件中。具体应用如下:

将信息写入到INI文件中所用的WINAPI函数原型为:

BOOLWritePrivateProfileString(

LPCTSTRlpAppName, // INI文件中字段名

LPCTSTRlpKeyName, // lpAppName下的子键名,通俗讲就是变量名

LPCTSTRlpString, // 键值,也就是变量的值,不过必须为LPCTSTR型或CString

LPCTSTRlpFileName // 完整的INI文件名

);.

将信息从INI文件中读入到程序中的变量所用的WINAPI函数原型为:

DWORDGetPrivateProfileString(

LPCTSTRlpAppName, // INI文件中字段名

LPCTSTRlpKeyName, // lpAppName下的子键名,通俗讲就是变量名

LPCTSTRlpDefault, //如果INI文件中没有前两个参数指定的字段名或键名,则将此值赋给变量

LPTSTRlpReturnedString, //目的缓存器的CString对象

DWORDnSize, //目的缓存器大小

LPCTSTRlpFileName //完整INI文件名

);

读入整型值要用另一个WINAPI函数:UINTGetPrivateProfileInt( LPCTSTR lpAppName, LPCTSTR lpKeyName, INT nDefault,LPCTSTR lpFileName ); 其参数意义与上相同。

以实现最近使用的多个文件名保存为例,运用以上函数进行循环写入多个值,示例代码如下:

CStringstrTemp, strTempA;

int i,nCount =10;

//需要对10个文件名进行保存

for(i=0; i<nCount; i++)

{

strTemp.Format("%d",i);

strTempA= 文件名;  //文件名可以从数组,列表框等处取得

::WritePrivateProfileString("UseFileName","FileName"+strTemp,strTempA,

"c:\\usefile\\usefile.ini");

}

//将文件总数写入,以便读出

strTemp.Format("%d",nCount);

::WritePrivateProfileString("FileCount","Count",strTemp,"c:\\usefile\\usefile.ini");

读出代码和上述类似。另外要补充以下注意事项:INI文件的路径必须完整,文件名的前各级子目录必须存在,否则写入不成功,函数返回 FALSE 值。文件名路径中必须为” \\”,VC++中 “\\ “才表示一个”\”。如果INI文件要放在程序所在目录中,此时参数lpFileName 为 ".\\student.ini"。

7.目录的日期和时间修改

在Windows环境下开发某些具有数据备份和恢复等功能的程序,在拷贝文件及其目录时把文件和目录的所有属性、包括日期和时间完全备份并还原。但Win32只提供修改文件时间的API函数,没有关于修改目录时间的任何描述。但仔细分析Windows提供的备份功能时发现,它实现的这一功能是从备份有关的Win32 API入手的。步骤如下:以FILE_FLAG_BACKUP_SEMANTICS属性调用Win32 API函数CreateFile()打开目录,再调用修改文件时间功能的SetFileTime() 函数即可。由此,在数据备份和恢复软件中,所有目录(包括根目录)都可以完全恢复原来的日期和时间了。

BOOLSetDirTime(char* pDirName,SYSTEMTIME newstime) //修改指定目录的时间

{

 HANDLE hDir;

 //打开目录的Win32API调用

 hDir = CreateFile(DirName,GENERIC_READ |GENERIC_WRITE,     // 必须写方式打开

      FILE_SHARE_READ|FILE_SHARE_DELETE,NULL,OPEN_EXISTING,// 打开现存的目录

       FILE_FLAG_BACKUP_SEMANTICS,// 只有这样才能打开目录

       NULL);

 if(hDir == INVALID_HANDLE_VALUE)

   return FALSE; // 打开失败时返回

 FILETIME lpCreationTime;    // 目录的创建时间

 FILETIME lpLastAccessTime; //最近一次访问目录的时间

 FILETIME lpLastWriteTime;  //最近一次修改目录的时间

 SystemTimeToFileTime(&newstime,&lpCreationTime);

 SystemTimeToFileTime(&newstime,&lpLastAccessTime);

 SystemTimeToFileTime(&newstime,&lpLastWriteTime);

 // 修改目录时间的Win32API函数调用

 BOOL bVal = SetFileTime(hDir,&lpCreationTime, &lpLastAccessTime, &lpLastWriteTime);

 CloseHandle(hDir); //关闭目录

 return bVal;

}

8.运行时程序文件自删除

在共享软件的开发过程中,要求程序在识出恶意破解时或注册未成功时,在运行时就将其自身可执行文件删除。其原理为:如果文件的HANDLE打开,文件删除就会失败。由于HANDLE4是Win操作系统的硬编码,对应于EXE的文件映像(IMAGE)。缺省情况下,操作系统假定没有任何调用就会关闭文件映像区的句柄(HANDLE)。而现在,此HANDLE被关闭,删除文件就解除了文件所对应的句柄。所以CloseHandle(HANDLE(4))的运用十分巧妙。

由于UnmapViewOfFile解除了另外一个对应IMAGE的HANDLE,而且解除了IMAGE在内存的映射。由此以后的任何代码都不可以引用IMAGE映射地址内的任何代码。否则操作系统将报错。而现在代码在UnmapViewOfFile后则刚好没有引用到任何IMAGE内的代码。另外,在ExitProcess之前,EXE文件就将被删除。由于Win9x/NT/2K保护这些被映射到内存的Win32 IMAGE不被删除,所以进程虽然存在,但主线程所在的磁盘EXE文件已经没了。

HMODULEhModule = GetModuleHandle(0);

charbuffer[MAX_PATH]; //存放EXE文件名

GetModuleFileName(hModule,buffer, sizeof(buffer)); //得到可执行的EXE文件名

CloseHandle((HANDLE)4);

_asm

{

 lea eax, buffer

 push 0

 push 0

 push eax

 push ExitProcess

 push hModule

 push DeleteFile

 push UnmapViewOfFile

 ret

}

上述代码调用ret返回到了UnmapViewOfFile,也就是堆栈里的偏移0所指的地方,当进入UnmapViewOfFile的流程时,栈里见到的是返回地址DeleteFile和hModule。当返回DeleteFile时,得到了ExitProcess的地址,即返回地址和参数EAX,而EAX为buffer即EXE的文件名。

以上源码在Windows和VC6.0+SP5平台的实际项目工程中经过验证,运行效果良好。读者可在充分消化吸收的基础上,举一反三,更好的应用于自身的软件开发中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值