科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航



ZDNet>网络频道>ZD评测>用于 Foxmail 的辅助工具 SmartFox

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文旨在介绍 Foxmail 的账号存储机制,并基于此编写一个辅助工具 SmartFox ,实现闪存“随身邮”功能。

来源:巧巧读书 2008年07月25日

关键字:Foxmail SmartFox 电子邮件

  本文旨在介绍 Foxmail 的账号存储机制,并基于此编写一个辅助工具 SmartFox ,实现闪存“随身邮”功能。

  最近我买了一支爱国者的经典型“迷你王”闪存,自己安装了 Foxmail ,并在闪存上创建了两个账号以收发邮件。但当我在另一台 PC 上使用 Foxmail 时,它却提示没有可用的账号。这是怎么回事呢?原来, Foxmail 在创建账号时,会将这个账号所在目录完整的路径名记录下来。由于闪存在不同的 PC 上获得的盘符不一定相同,这样在闪存的盘符改变后, Foxmail 便因无法定位该目录而发生错误。

  开始时,我编辑了一个批处理文件Foxmail.BAT 用以启动 Foxmail 。其思路是将闪存的根

  目录重定向为 Z: 盘,每次将闪存的当前盘符作为参数传递给这个批处理文件即可。为此我不得

  不将之前创建的账号删除,在命令行提示符下启动批处理命令,然后在 Z: 盘下创建账号。这样

  不是太麻烦了吗?是的!我也曾尝试过利用中间文件自动进行参数传递,但是由于 DOS的不可重入性而不能保证每次都成功运行。因此我开始剖析 Foxmail 的账号存储机制。

  Foxmail 的账号存储机制

  Foxmail 将每个账号的最基本信息(账号名称、存储目录)存放在安装目录下的文件

  Accounts.CFG 中。在每个账号的目录中则利用文件 Account.STG 存储该账号的其他信息。

  Accounts.CFG 是一个复合文档,它包括一个 Ver30 存储( Storage )。在 Ver30 存储下有一个accounts.cfg 流( Stream )。你可以使用 Visual Studio 6.0 提供的 DocFile 阅读器打开它。关于存储和流的更多知识,请参阅 MSDN: [Platform SDK] Structured Storage 。

  经过分析,我得到了 Foxmail 的账号存储机制。如下即为 accounts.cfg 流的 C 语言描述结构(表中均为十六进制):

  

偏移地址

变量名

描述

00000000

BYTE Reserved [40];

文件头。在 00000007 处存储了曾创建账号的个数。

00000040

DWORD cAccount;

现有的账号数。

第一个账号信息块( AIB , Account Information Block )的开始。

00000044

DWORD idxAccount;

该账号的顺序编号。

DWORD lenACTName;

账号名字符串的长度。

String strACTName [lenACTName];

账号名字符串。

DWORD lenACTPath;

账号所在目录的字符串长度。

String strACTPath [lenACTPath];

账号所在目录的全路径名。

BYTE Reserved [18];

0x18 个 00 (可能用以存储密码)。

下一个 AIB

注意:所有的字符串长度均不包括结尾符在内。

  编程实现

  现在我们就可以开始编写 Foxmail 的辅助工具 SmartFox 了。 重要约定:

  所有账号的目录均在闪存上!

  SmartFox 在 Foxmail 安装目录下工作! 程序的流程:获得闪存的当前盘符,打开 accounts.cfg 流,修改所有账号的目录盘符为闪存当前盘符,启动 Foxmail 。我认为没有必要在 Foxmail 退出后恢复 accounts.cfg 的内容,你认为呢?

  SmartFox 是一个利用 MFC实现的基于对话框的应用程序,静态链接 MFC 的动态链接库。

  运行界面如下,单击左键启动 Foxmail ,单击右键退出。

(一)对程序中使用的 API 的介绍,更详细的内容请参见 MSDN :

  1. 获得当前目录的全路径名 (Win API) DWORD GetCurrentDirectory(

  DWORD nBufferLength, // 保存目录名的缓冲区大小

  LPTSTR lpBuffer    // 指向缓冲区的指针

  );2. 打开一个复合文档 (Win API) HRESULT StgOpenStorage(

  const WCHAR *pwcsName,  //复合文档的文件名

  IStorage *pstgPriority,  //先前已打开的根存储的指针

  DWORD grfMode,      //访问模式

  SNB snbExclude,      //指向一个SNB结构的指针,以确定哪些元素将被排除访问

  DWORD reserved,      //保留

  IStorage **ppstgOpen   //接受返回的IStorage接口指针

  );3. 打开一个子存储或流 (IStorage API) HRESULT OpenStorage(

  const WCHAR *pwcsName,  //要打开的存储的名字

  IStorage *pstgPriority, //该参数一定为NULL值

  DWORD grfMode,      //访问模式

  SNB snbExclude,     //该参数一定为NULL值

  DWORD reserved,     //保留

  IStorage **ppstg     //接受返回的IStorage接口指针

  );OpenStream 的参数与 OpenStorage 的差不多,只是返回的是一个 IStream 指针。

  4. 取得流的大小,移动流的读写指针,从流读写数据 (IStream API) HRESULT Stat(

  STATSTG *pstatstg, //指向一个STATSTG结构的指针,STATSTG的cb域即为流的大小。

  DWORD grfStatFlag  //决定是否要在STATSTG中返回某些值的标志

  );

  HRESULT Seek(

  LARGE_INTEGER dlibMove,     //相对于dwOrigin的偏址

  DWORD dwOrigin,         //起始位置

  ULARGE_INTEGER *plibNewPosition //接受指向新位置的指针

  );

  HRESULT Read(

  void *pv, //指向数据缓冲区的指针

  ULONG cb, //要读的字节数

  ULONG *pcbRead //实际读出的字节数

  );Write 的参数与 Read 的一样。5. 加载外部程序 (Win API) HINSTANCE ShellExecute(

  HWND hwnd,   //父窗口句柄

  LPCTSTR lpVerb,  //要执行的动作:edit,explore,find,open,print,properties

  LPCTSTR lpFile,   //文件名

  LPCTSTR lpParameters,   //传递的命令行参数

  LPCTSTR lpDirectory,    //缺省工作目录

  INT nShowCmd  //窗口的显示模式

  );

  UINT WinExec(

  LPCSTR lpCmdLine, // 命令的字符串

  UINT uCmdShow   //窗口的显示模式

  );6. 此外,我在程序中使用了 COM 库的缺省 IMalloc 接口管理缓冲区 HRESULT CoGetMalloc(   //这是一个Win API

  DWORD dwMemContext, //决定该内存块是否被共享的标志

  LPMALLOC * ppMalloc //接受返回的内存分配器的IMalloc接口指针

  );IMalloc::Alloc() ,IMalloc::Free() 的使用与 C 语言中的 alloc() 和 free() 类似,在此不再赘述。

  (二)实现步骤:

  在 StdAfx.h 中加入以下的头文件:objidl.h ,afxole.h ,afxpriv.h ,afxtempl.h

  在 CSmartFoxApp::InitInstance() 中加入: ::CoInitialize(NULL);

  为 CSmartFoxDlg 添加如下数据成员: IStorage * m_pRootStg; // 根存储的接口指针

  IStorage * m_pVer30Stg; //Ver30 存储的接口指针

  IStream * m_pStream; //accounts.cfg 流的接口指针

  char * m_pBuffer; // 用以读写 accounts.cfg 流的缓冲区指针

  char m_Driver; // 闪存的当前盘符

  CArray <ULONG, ULONG> m_aryPosition; // 保存流中账号目录所在偏移地址的数组  

  利用 Class Wizard 为 CSmartFoxDlg 添加或修改下列函数: BOOL CSmartFoxDlg::OnInitDialog()

  {

  CDialog::OnInitDialog();

  SetIcon(m_hIcon, TRUE);      // Set big icon

  SetIcon(m_hIcon, FALSE);    // Set small icon

  try

  {

  m_Driver = GetDriver();

  m_pStream = GetIStream();

  m_pBuffer = GetBuffer(m_pStream);

  GetAccountInfo(m_pStream, m_pBuffer);

  }

  catch (char * sMsg)

  {

  AfxMessageBox(sMsg,MB_OK,NULL);

  ClearUp();

  }

  return TRUE; // return TRUE unless you set the focus to a control

  }

  void CSmartFoxDlg::OnClickEmail()

  {

  ShellExecute(this->m_hWnd,

  "open",

  "mailto: korby@sohu.com?subject=Re: 关于SmartFox的意见",  

  NULL,

  NULL,

  SW_SHOWNORMAL);

  ClearUp();

  }

  void CSmartFoxDlg::OnLButtonDown(UINT nFlags, CPoint point)

  {

  // I will modify the driver letter of accounts here, and startup FoxMail.

  try

  {

  ModifyAccountDriver(m_pStream, m_pBuffer);

  ClearUp();

  if (WinExec("FoxMail.EXE", SW_SHOWNORMAL) <31) throw "加载FoxMail.EXE失败";

  }

  catch (char * sMsg)

  {

  AfxMessageBox(sMsg,MB_OK,NULL);

  }

  }

  void CSmartFoxDlg::OnRButtonDown(UINT nFlags, CPoint point)

  {

  // If I don''''t do this, the message will be transferred to window behind.

  SetCapture();

  }

  void CSmartFoxDlg::OnRButtonUp(UINT nFlags, CPoint point)

  {

  ::ReleaseCapture();

  ClearUp();

  }

  void CSmartFoxDlg::ClearUp()

  {

  if (m_pRootStg != NULL) m_pRootStg->Release();

  if (m_pVer30Stg != NULL) m_pVer30Stg->Release();

  if (m_pStream  != NULL) m_pStream->Release();

  if (m_pBuffer  != NULL)

  {

  IMalloc * pMalloc;

  ::CoGetMalloc(MEMCTX_TASK, &pMalloc);

  pMalloc->Free(m_pBuffer);

  pMalloc->Release();

  }

  ::CoUninitialize();

  OnCancel();

  }

  // All codes below find out driver letter and directories of each account.

  char CSmartFoxDlg::GetDriver()

  {

  char sCurDir[256];

  int ret = ::GetCurrentDirectory(256, sCurDir);

  if (ret == NULL) throw "取当前驱动器盘符时失败";

  else return sCurDir[0];

  }

  IStream * CSmartFoxDlg::GetIStream()

  {

  USES_CONVERSION;

  // Get interface Storage pointer of Accounts.CFG

  IStream * pStream;

  HRESULT  hr;

  hr = ::StgOpenStorage(T2COLE("Accounts.CFG"),

  NULL,

  STGM_READWRITE|STGM_SHARE_EXCLUSIVE,

  NULL,

  0,

  &m_pRootStg);

  if (hr != S_OK) throw "打开Accounts.CFG文件时失败";

  hr = m_pRootStg->OpenStorage(T2COLE("Ver30"),

  NULL,

  STGM_READWRITE|STGM_SHARE_EXCLUSIVE,

  NULL,

  0,

  &m_pVer30Stg);

  if (hr != S_OK) throw "打开Ver30存储时失败";

  hr = m_pVer30Stg->OpenStream(T2COLE("accounts.cfg"),

  NULL,

  STGM_READWRITE|STGM_SHARE_EXCLUSIVE,

  NULL,

  &pStream);

  if (hr != S_OK) throw "打开accounts.cfg流时失败";

  return pStream;

  }

  char * CSmartFoxDlg::GetBuffer(IStream * pStream)

  {

  STATSTG  StatStg;

  IMalloc * pMalloc;

  char *   pBuffer;

  HRESULT  hr;

  hr = pStream->Stat(&StatStg, NULL);

  if (hr != S_OK) throw "读取accounts.cfg流的大小时失败";

  hr = ::CoGetMalloc(MEMCTX_TASK, &pMalloc);

  if (hr != S_OK) throw "获取COM库的IMalloc接口指针时失败";

  pBuffer = (char *)pMalloc->Alloc(ULONG(StatStg.cbSize.QuadPart));

  if (pBuffer == NULL) throw "申请缓冲区时失败";

  pMalloc->Release();

  return pBuffer;

  }

  void CSmartFoxDlg::GetAccountInfo(IStream * pStream, char * pBuffer)

  {

  // I will find out names and directories of each account.

  STATSTG  StatStg;

  ULONG   cbReaded;

  HRESULT  hr;

  char *   p = pBuffer;

  DWORD    cAccount;

  DWORD   len;

  CString  name;  // Gets the name of account.

  CString  path;  // Gets the path of account.

  CString  strEdit;  //Displays text in edit control.

  CEdit *  pEdit = (CEdit *) GetDlgItem(IDC_EDIT);

  strEdit.Format("闪存当前为%c:盘, 现存账号及其目录: \r\n", m_Driver);

  hr = pStream->Stat(&StatStg, NULL);

  if (hr != S_OK) throw "读取accounts.cfg流的大小时失败";

  hr = pStream->Read(pBuffer, ULONG(StatStg.cbSize.QuadPart), &cbReaded);

  if (hr != S_OK) throw "读取accounts.cfg流的内容时失败";

  p += 0x40;

  cAccount = (*p);  //Count of accounts.

  p += 0x4;

  for (DWORD i = 1; i <= cAccount; i++)

  {

  // Value of (*p) is index of account.

  if (DWORD(*p) != i) throw "accounts.cfg流损坏";

  p += 0x4;  //Skips the index number.

  len = DWORD(*p);  //Gets length of name.

  p += 0x4;  //Skips the length number. The string does not include NULL.

  name.Empty();

  path.Empty();

  for (DWORD n = 0; n <len; n++, p++) name += char (*p); // Gets account name.

  len = DWORD(*p); // Gets length of directory.

  p += 4; // Skips the length number.

  m_aryPosition.Add(p-pBuffer); //Stores offset into array.

  for (n = 0; n< len; n++, p++) path += char (*p); //Gets account path.

  strEdit += name+"\t"+path+"\r\n";

  p += 0x18; //Skips 0x18 Nulls.

  }

  pEdit->SetWindowText(strEdit);

  }

  void CSmartFoxDlg::ModifyAccountDriver(IStream *pStream, char * pBuffer)

  {

  STATSTG  StatStg;

  ULONG   cbWrited;

  HRESULT  hr;

  LARGE_INTEGER MovOffset;

  ULARGE_INTEGER NewPosition;

  char *   p;

  for (int i = 0; i< m_aryPosition.GetSize(); i++)

  {

  p = pBuffer + m_aryPosition.GetAt(i);

  (*p) = m_Driver;

  }

  hr = pStream->Stat(&StatStg, NULL);

  if (hr != S_OK) throw "读取accounts.cfg流的大小时失败";

  MovOffset.QuadPart = 0;

  hr = pStream->Seek(MovOffset, 0, &NewPosition);

  if (hr != S_OK) throw "移动accounts.cfg流的读写指针时失败";

  pStream->Write(pBuffer, ULONG(StatStg.cbSize.QuadPart), &cbWrited);

  if (hr != S_OK) throw "向accounts.cfg流写入数据时失败";

  }  编译链接生成 SmartFox.EXE 后,将其拷贝到 Foxmail 在闪存上的安装目录。利用 SmartFox.EXE 运行 Foxmail 就可以实现“随身邮”的功能了!

推广二维码
邮件订阅

如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

重磅专题