環(huán)境:Windows 98SE/2000,VC++ 6.0,F(xiàn)oxmail 4.1 簡體中文版 摘要 本文旨在介紹 Foxmail 的賬號存儲機制,并基于此編寫一個輔助工具 SmartFox ,實現(xiàn)閃存“隨身郵”功能。 問題的出現(xiàn)與分析 最近我買了一支愛國者的經(jīng)典型“迷你王”閃存,自己安裝了 Foxmail ,并在閃存上創(chuàng)建了兩個賬號以收發(fā)郵件。但當(dāng)我在另一臺 PC 上使用 Foxmail 時,它卻提示沒有可用的賬號。這是怎么回事呢?原來, Foxmail 在創(chuàng)建賬號時,會將這個賬號所在目錄完整的路徑名記錄下來。由于閃存在不同的 PC 上獲得的盤符不一定相同,這樣在閃存的盤符改變后, Foxmail 便因無法定位該目錄而發(fā)生錯誤。 開始時,我編輯了一個批處理文件 Foxmail.BAT 用以啟動 Foxmail 。其思路是將閃存的根目錄重定向為 Z: 盤,每次將閃存的當(dāng)前盤符作為參數(shù)傳遞給這個批處理文件即可。為此我不得不將之前創(chuàng)建的賬號刪除,在命令行提示符下啟動批處理命令,然后在 Z: 盤下創(chuàng)建賬號。這樣不是太麻煩了嗎?是的!我也曾嘗試過利用中間文件自動進(jìn)行參數(shù)傳遞,但是由于 DOS 的不可重入性而不能保證每次都成功運行。因此我開始剖析 Foxmail 的賬號存儲機制。 Foxmail 的賬號存儲機制 Foxmail 將每個賬號的最基本信息(賬號名稱、存儲目錄)存放在安裝目錄下的文件Accounts.CFG 中。在每個賬號的目錄中則利用文件 Account.STG 存儲該賬號的其他信息。 Accounts.CFG 是一個復(fù)合文檔,它包括一個 Ver30 存儲( Storage )。在 Ver30 存儲下有一個accounts.cfg 流( Stream )。你可以使用 Visual Studio 6.0 提供的 DocFile 閱讀器打開它。關(guān)于存儲和流的更多知識,請參閱 MSDN: [Platform SDK] Structured Storage 。 經(jīng)過分析,我得到了 Foxmail 的賬號存儲機制。如下即為 accounts.cfg 流的 C 語言描述結(jié)構(gòu)(表中均為十六進(jìn)制):
編程實現(xiàn) 現(xiàn)在我們就可以開始編寫 Foxmail 的輔助工具 SmartFox 了。 重要約定: 1.所有賬號的目錄均在閃存上! 2.SmartFox 在 Foxmail 安裝目錄下工作! 程序的流程:獲得閃存的當(dāng)前盤符,打開 accounts.cfg 流,修改所有賬號的目錄盤符為閃存當(dāng)前盤符,啟動 Foxmail 。我認(rèn)為沒有必要在 Foxmail 退出后恢復(fù) accounts.cfg 的內(nèi)容,你認(rèn)為呢? SmartFox 是一個利用 MFC 實現(xiàn)的基于對話框的應(yīng)用程序,靜態(tài)鏈接 MFC 的動態(tài)鏈接庫。 運行界面如下,單擊左鍵啟動 Foxmail ,單擊右鍵退出。
(一)對程序中使用的 API 的介紹,更詳細(xì)的內(nèi)容請參見 MSDN : 1. 獲得當(dāng)前目錄的全路徑名 (Win API) 1. DWORD GetCurrentDirectory( 2. DWORD nBufferLength, // 保存目錄名的緩沖區(qū)大小 3. LPTSTR lpBuffer // 指向緩沖區(qū)的指針 4. ); 2. 打開一個復(fù)合文檔 (Win API) 1. HRESULT StgOpenStorage( 2. const WCHAR *pwcsName, //復(fù)合文檔的文件名 3. IStorage *pstgPriority, //先前已打開的根存儲的指針 4. DWORD grfMode, //訪問模式 5. SNB snbExclude, //指向一個SNB結(jié)構(gòu)的指針,以確定哪些元素將被排除訪問 6. DWORD reserved, //保留 7. IStorage **ppstgOpen //接受返回的IStorage接口指針 8. ); 3. 打開一個子存儲或流 (IStorage API) 1. HRESULT OpenStorage( 2. const WCHAR *pwcsName, //要打開的存儲的名字 3. IStorage *pstgPriority, //該參數(shù)一定為NULL值 4. DWORD grfMode, //訪問模式 5. SNB snbExclude, //該參數(shù)一定為NULL值 6. DWORD reserved, //保留 7. IStorage **ppstg //接受返回的IStorage接口指針 8. ); OpenStream 的參數(shù)與 OpenStorage 的差不多,只是返回的是一個 IStream 指針。 4. 取得流的大小,移動流的讀寫指針,從流讀寫數(shù)據(jù) (IStream API) 01. HRESULT Stat( 02. STATSTG *pstatstg, //指向一個STATSTG結(jié)構(gòu)的指針,STATSTG的cb域即為流的大小。 03. DWORD grfStatFlag //決定是否要在STATSTG中返回某些值的標(biāo)志 04. ); 05. 06. 07. HRESULT Seek( 08. LARGE_INTEGER dlibMove, //相對于dwOrigin的偏址 09. DWORD dwOrigin, //起始位置 10. ULARGE_INTEGER *plibNewPosition //接受指向新位置的指針 11. ); 12. 13. 14. HRESULT Read( 15. void *pv, //指向數(shù)據(jù)緩沖區(qū)的指針 16. ULONG cb, //要讀的字節(jié)數(shù) 17. ULONG *pcbRead //實際讀出的字節(jié)數(shù) 18. );Write 的參數(shù)與 Read 的一樣。 5. 加載外部程序 (Win API) 01. HINSTANCE ShellExecute( 02. HWND hwnd, //父窗口句柄 03. LPCTSTR lpVerb, //要執(zhí)行的動作:edit,explore,find,open,print,properties 04. LPCTSTR lpFile, //文件名 05. LPCTSTR lpParameters, //傳遞的命令行參數(shù) 06. LPCTSTR lpDirectory, //缺省工作目錄 07. INT nShowCmd //窗口的顯示模式 08. ); 09. 10. UINT WinExec( 11. LPCSTR lpCmdLine, // 命令的字符串 12. UINT uCmdShow //窗口的顯示模式 13. ); 6. 此外,我在程序中使用了 COM 庫的缺省 IMalloc 接口管理緩沖區(qū) 1. HRESULT CoGetMalloc( //這是一個Win API 2. DWORD dwMemContext, //決定該內(nèi)存塊是否被共享的標(biāo)志 3. LPMALLOC * ppMalloc //接受返回的內(nèi)存分配器的IMalloc接口指針 4. ); IMalloc::Alloc() ,IMalloc::Free() 的使用與 C 語言中的 alloc() 和 free() 類似,在此不再贅述。 (二)實現(xiàn)步驟: 在 StdAfx.h 中加入以下的頭文件:objidl.h ,afxole.h ,afxpriv.h ,afxtempl.h 1.在 CSmartFoxApp::InitInstance() 中加入: ::CoInitialize(NULL); 2.為 CSmartFoxDlg 添加如下數(shù)據(jù)成員: 1. IStorage * m_pRootStg; // 根存儲的接口指針 2. IStorage * m_pVer30Stg; //Ver30 存儲的接口指針 3. IStream * m_pStream; //accounts.cfg 流的接口指針 4. char * m_pBuffer; // 用以讀寫 accounts.cfg 流的緩沖區(qū)指針 5. char m_Driver; // 閃存的當(dāng)前盤符 6. CArray m_aryPosition; // 保存流中賬號目錄所在偏移地址的數(shù)組 3.利用 Class Wizard 為 CSmartFoxDlg 添加或修改下列函數(shù): 001. BOOL CSmartFoxDlg::OnInitDialog() 002. { 003. CDialog::OnInitDialog(); 004. SetIcon(m_hIcon, TRUE); // Set big icon 005. SetIcon(m_hIcon, FALSE); // Set small icon 006. try 007. { 008. m_Driver = GetDriver(); 009. m_pStream = GetIStream(); 010. m_pBuffer = GetBuffer(m_pStream); 011. GetAccountInfo(m_pStream, m_pBuffer); 012. } 013. catch ( char * sMsg) 014. { 015. AfxMessageBox(sMsg,MB_OK,NULL); 016. ClearUp(); 017. } 018. return TRUE; // return TRUE unless you set the focus to a control 019. } 020. 021. void CSmartFoxDlg::OnClickEmail() 022. { 023. ShellExecute( this ->m_hWnd, 024. "open" , 025. "mailto: korby@sohu.com?subject=Re: 關(guān)于SmartFox的意見" , 026. NULL, 027. NULL, 028. SW_SHOWNORMAL); 029. ClearUp(); 030. } 031. 032. void CSmartFoxDlg::OnLButtonDown( UINT nFlags, CPoint point) 033. { 034. // I will modify the driver letter of accounts here, and startup FoxMail. 035. try 036. { 037. ModifyAccountDriver(m_pStream, m_pBuffer); 038. ClearUp(); 039. if (WinExec( "FoxMail.EXE" , SW_SHOWNORMAL) < 31) throw "加載FoxMail.EXE失敗" ; 040. } 041. catch ( char * sMsg) 042. { 043. AfxMessageBox(sMsg,MB_OK,NULL); 044. } 045. } 046. 047. void CSmartFoxDlg::OnRButtonDown( UINT nFlags, CPoint point) 048. { 049. // If I don''''t do this, the message will be transferred to window behind. 050. SetCapture(); 051. } 052. 053. void CSmartFoxDlg::OnRButtonUp( UINT nFlags, CPoint point) 054. { 055. ::ReleaseCapture(); 056. ClearUp(); 057. } 058. 059. void CSmartFoxDlg::ClearUp() 060. { 061. if (m_pRootStg != NULL) m_pRootStg->Release(); 062. if (m_pVer30Stg != NULL) m_pVer30Stg->Release(); 063. if (m_pStream != NULL) m_pStream->Release(); 064. if (m_pBuffer != NULL) 065. { 066. IMalloc * pMalloc; 067. ::CoGetMalloc(MEMCTX_TASK, &pMalloc); 068. pMalloc->Free(m_pBuffer); 069. pMalloc->Release(); 070. } 071. ::CoUninitialize(); 072. OnCancel(); 073. } 074. 075. // All codes below find out driver letter and directories of each account. 076. char CSmartFoxDlg::GetDriver() 077. { 078. char sCurDir[256]; 079. int ret = ::GetCurrentDirectory(256, sCurDir); 080. if (ret == NULL) throw "取當(dāng)前驅(qū)動器盤符時失敗" ; 081. else return sCurDir[0]; 082. } 083. 084. IStream * CSmartFoxDlg::GetIStream() 085. { 086. USES_CONVERSION; 087. // Get interface Storage pointer of Accounts.CFG 088. IStream * pStream; 089. HRESULT hr; 090. hr = ::StgOpenStorage(T2COLE( "Accounts.CFG" ), 091. NULL, 092. STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 093. NULL, 094. 0, 095. &m_pRootStg); 096. if (hr != S_OK) throw "打開Accounts.CFG文件時失敗" ; 097. 098. hr = m_pRootStg->OpenStorage(T2COLE( "Ver30" ), 099. NULL, 100. STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 101. NULL, 102. 0, 103. &m_pVer30Stg); 104. if (hr != S_OK) throw "打開Ver30存儲時失敗" ; 105. 106. hr = m_pVer30Stg->OpenStream(T2COLE( "accounts.cfg" ), 107. NULL, 108. STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 109. NULL, 110. &pStream); 111. if (hr != S_OK) throw "打開accounts.cfg流時失敗" ; 112. 113. return pStream; 114. } 115. 116. char * CSmartFoxDlg::GetBuffer(IStream * pStream) 117. { 118. STATSTG StatStg; 119. IMalloc * pMalloc; 120. char * pBuffer; 121. HRESULT hr; 122. 123. hr = pStream->Stat(&StatStg, NULL); 124. if (hr != S_OK) throw "讀取accounts.cfg流的大小時失敗" ; 125. 126. hr = ::CoGetMalloc(MEMCTX_TASK, &pMalloc); 127. if (hr != S_OK) throw "獲取COM庫的IMalloc接口指針時失敗" ; 128. 129. pBuffer = ( char *)pMalloc->Alloc( ULONG (StatStg.cbSize.QuadPart)); 130. if (pBuffer == NULL) throw "申請緩沖區(qū)時失敗" ; 131. 132. pMalloc->Release(); 133. return pBuffer; 134. } 135. 136. void CSmartFoxDlg::GetAccountInfo(IStream * pStream, char * pBuffer) 137. { 138. // I will find out names and directories of each account. 139. STATSTG StatStg; 140. ULONG cbReaded; 141. HRESULT hr; 142. 143. char * p = pBuffer; 144. DWORD cAccount; 145. DWORD len; 146. CString name; // Gets the name of account. 147. CString path; // Gets the path of account. 148. 149. CString strEdit; //Displays text in edit control. 150. CEdit * pEdit = (CEdit *) GetDlgItem(IDC_EDIT); 151. strEdit.Format( "閃存當(dāng)前為%c:盤, 現(xiàn)存賬號及其目錄: \r\n" , m_Driver); 152. 153. hr = pStream->Stat(&StatStg, NULL); 154. if (hr != S_OK) throw "讀取accounts.cfg流的大小時失敗" ; 155. 156. hr = pStream->Read(pBuffer, ULONG (StatStg.cbSize.QuadPart), &cbReaded); 157. if (hr != S_OK) throw "讀取accounts.cfg流的內(nèi)容時失敗" ; 158. 159. p += 0x40; 160. cAccount = (*p); //Count of accounts. 161. p += 0x4; 162. 163. for ( DWORD i = 1; i <= cAccount; i++) 164. { 165. // Value of (*p) is index of account. 166. if ( DWORD (*p) != i) throw "accounts.cfg流損壞" ; 167. p += 0x4; //Skips the index number. 168. len = DWORD (*p); //Gets length of name. 169. p += 0x4; //Skips the length number. The string does not include NULL. 170. 171. name.Empty(); 172. path.Empty(); 173. 174. for ( DWORD n = 0; n < len; n++, p++) name += char (*p); // Gets account name. 175. 176. len = DWORD (*p); // Gets length of directory. 177. p += 4; // Skips the length number. 178. 179. m_aryPosition.Add(p-pBuffer); //Stores offset into array. 180. 181. for (n = 0; n< len; n++, p++) path += char (*p); //Gets account path. 182. 183. strEdit += name+ "\t" +path+ "\r\n" ; 184. 185. p += 0x18; //Skips 0x18 Nulls. 186. } 187. pEdit->SetWindowText(strEdit); 188. } 189. 190. void CSmartFoxDlg::ModifyAccountDriver(IStream *pStream, char * pBuffer) 191. { 192. STATSTG StatStg; 193. ULONG cbWrited; 194. HRESULT hr; 195. 196. LARGE_INTEGER MovOffset; 197. ULARGE_INTEGER NewPosition; 198. 199. char * p; 200. 201. for ( int i = 0; i< m_aryPosition.GetSize(); i++) 202. { 203. p = pBuffer + m_aryPosition.GetAt(i); 204. (*p) = m_Driver; 205. } 206. 207. hr = pStream->Stat(&StatStg, NULL); 208. if (hr != S_OK) throw "讀取accounts.cfg流的大小時失敗" ; 209. 210. MovOffset.QuadPart = 0; 211. hr = pStream->Seek(MovOffset, 0, &NewPosition); 212. if (hr != S_OK) throw "移動accounts.cfg流的讀寫指針時失敗" ; 213. 214. pStream->Write(pBuffer, ULONG (StatStg.cbSize.QuadPart), &cbWrited); 215. if (hr != S_OK) throw "向accounts.cfg流寫入數(shù)據(jù)時失敗" ; 216. } 編譯鏈接生成 SmartFox.EXE 后,將其拷貝到 Foxmail 在閃存上的安裝目錄。利用 SmartFox.EXE 運行 Foxmail 就可以實現(xiàn)“隨身郵”的功能了! |
|