WEB开发网
开发学院软件教学网络软件 用于 Foxmail 的辅助工具 SmartFox 阅读

用于 Foxmail 的辅助工具 SmartFox

 2006-07-22 10:07:06 来源:WEB开发网   
核心提示: 环境:Windows 98SE/2000,VC++ 6.0,用于 Foxmail 的辅助工具 SmartFox,Foxmail 4.1 简体中文版摘要本文旨在介绍 Foxmail 的账号存储机制,并基于此编写一个辅助工具 SmartFox ,afxpriv.h ,afxtempl.h 在 CSmartFoxA

环境:Windows 98SE/2000,VC++ 6.0,Foxmail 4.1 简体中文版

摘要

本文旨在介绍 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 了。 重要约定:

  1. 所有账号的目录均在闪存上!
  2. 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() 类似,在此不再赘述。

(二)实现步骤:

  1. 在 StdAfx.h 中加入以下的头文件:objidl.h ,afxole.h ,afxpriv.h ,afxtempl.h
  2. 在 CSmartFoxApp::InitInstance() 中加入: ::CoInitialize(NULL);
  3. 为 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; // 保存流中账号目录所在偏移地址的数组  
  4. 利用 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 就可以实现“随身邮”的功能了!

Tags:用于 Foxmail 辅助工具

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接