在 Windows 開發中,經常需要從可執行文件、動態鏈接庫、圖標庫中提取圖標資源並保存為通用格式。本文基於 MFC 實現一個完整的圖標提取工具,封裝 CIcons 與 CDib 兩大核心類,支持從 .exe、.dll、.icl、.ico、.bmp 多種源格式提取圖標,並導出為 .ico 或 .bmp 文件。

圖 1 程序運行界面
Icons.h / Icons.cpp:CIcons 類,負責圖標資源讀取、格式轉換、ICO 文件寫入Dib.h / Dib.cpp:CDib 類,負責設備無關位圖(DIB)操作、BMP 文件讀寫將四個文件加入項目即可直接調用,極易集成。
class CIcons {
public:
// 從各類源讀取圖標資源
LPICONRESOURCE ReadIconFromICOFile(LPCTSTR szFileName);
LPICONRESOURCE ReadIconFromEXEFile(LPCTSTR szFileName, LPCTSTR szIconID);
BOOL IconImageFromBMPFile(LPCTSTR szFileName, LPICONIMAGE pIconImage, BOOL bStretch);
// 圖標創建與轉換
HICON MakeIconFromResource(LPICONIMAGE pIconImage);
HICON GetIconFromInstance(HINSTANCE hInst, LPCTSTR szIconID);
// 導出文件
BOOL WriteIconToICOFile(LPICONRESOURCE pIconRes, LPCTSTR szFileName);
BOOL IconImageToBMPFile(LPCTSTR szFileName, LPICONIMAGE pIconImage);
// 緩衝區管理
void IconBuffer(LPICONIMAGE pIconImage, DWORD dwSize);
};
void CIconSnapDlg::OnOK() {
// 1. 文件對話框選擇源文件
CFileDialog fileDialog(TRUE, _T("*.ICO"), NULL, 0,
_T("資源文件(*.ICO,*.BMP,*.EXE,*.DLL,*.ICL)|*.ICO;*.BMP;*.EXE;*.DLL;*.ICL||"));
if (fileDialog.DoModal() != IDOK) return;
CString szOpenFileName = fileDialog.GetPathName();
CString szExt = fileDialog.GetFileExt(); szExt.MakeLower();
// 2. 清空列表
m_List.ResetContent();
// 3. 根據擴展名分發處理
if (szExt == _T("ico")) {
LoadICOFile(szOpenFileName);
} else if (szExt == _T("bmp")) {
LoadBMPFile(szOpenFileName);
} else { // exe, dll, icl
LoadModuleResources(szOpenFileName);
}
// 4. 更新 UI 狀態
m_List.SetCurSel(0);
UpdateButtonsState();
InvalidateRect(NULL, TRUE); // 觸發重繪
}
void CIconSnapDlg::LoadICOFile(LPCTSTR szFileName) {
LPICONRESOURCE lpIR = pIcons->ReadIconFromICOFile(szFileName);
HICON hIcon = ExtractIcon(AfxGetInstanceHandle(), szFileName, 0);
if (hIcon) {
m_List.AddString(szFileName);
m_List.SetItemData(0, (DWORD_PTR)szFileName); // 存儲標識
CStatic* pStatic = (CStatic*)GetDlgItem(IDC_ICONS);
pStatic->SetIcon(hIcon);
}
}
void CIconSnapDlg::LoadBMPFile(LPCTSTR szFileName) {
ICONRESOURCE ir;
pIcons->IconImageFromBMPFile(szFileName, &ir.IconImages[0], TRUE);
HICON hIcon = pIcons->MakeIconFromResource(&ir.IconImages[0]);
if (hIcon) {
m_List.AddString(szFileName);
m_List.SetItemData(0, (DWORD_PTR)szFileName);
CStatic* pStatic = (CStatic*)GetDlgItem(IDC_ICONS);
pStatic->SetIcon(hIcon);
}
}
void CIconSnapDlg::LoadModuleResources(LPCTSTR szFileName) {
// 1. 作為數據文件加載模塊(不執行代碼,安全)
HINSTANCE hLib = LoadLibraryEx(szFileName, NULL, LOAD_LIBRARY_AS_DATAFILE);
if (!hLib) {
AfxMessageBox(_T("文件載入錯誤,必須是 Win32 文件!"));
return;
}
EDII.szFileName = szOpenFileName;
EDII.hInstance = hLib;
// 2. 枚舉 RT_GROUP_ICON 資源
if (!EnumResourceNames(hLib, RT_GROUP_ICON,
(ENUMRESNAMEPROC)MyEnumProc, (LPARAM)GetSafeHwnd())) {
AfxMessageBox(_T("枚舉圖標資源失敗!"));
FreeLibrary(hLib);
return;
}
// 枚舉回調中將資源名稱添加到列表並關聯 ItemData
}
枚舉回調函數:
BOOL CALLBACK MyEnumProc(HANDLE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG_PTR lParam) {
HWND hWnd = (HWND)lParam;
CIconSnapDlg* pDlg = (CIconSnapDlg*)CWnd::FromHandle(hWnd);
if (pDlg) {
int nIndex = pDlg->m_List.AddString(lpszName);
pDlg->m_List.SetItemData(nIndex, (DWORD_PTR)lpszName); // 關鍵:存儲資源名指針
}
return TRUE; // 繼續枚舉
}
void CIconSnapDlg::OnPaint() {
CPaintDC dc(this);
LPTSTR lpIconID = (LPTSTR)m_List.GetItemData(m_List.GetCurSel());
if (lpIconID == (LPTSTR)LB_ERR) return;
CString szExt = szOpenFileExtName; // 之前保存的擴展名
if (szExt == _T("exe") || szExt == _T("dll") || szExt == _T("icl")) {
HICON hIcon = pIcons->GetIconFromInstance(EDII.hInstance, lpIconID);
CStatic* pStatic = (CStatic*)GetDlgItem(IDC_ICONS);
pStatic->SetIcon(hIcon); // 靜態圖片控件顯示
}
// ICO/BMP 已在加載時顯示
}
void CIconSnapDlg::OnButtonSaveas() {
CFileDialog dlg(FALSE, _T("*.ICO"), NULL, 0,
_T("圖標文件(*.ICO)|*.ICO|位圖文件(*.BMP)|*.BMP||"));
if (dlg.DoModal() != IDOK) return;
CString szSavePath = dlg.GetPathName();
CString szSaveExt = dlg.GetFileExt(); szSaveExt.MakeLower();
LPTSTR lpIconID = (LPTSTR)m_List.GetItemData(m_List.GetCurSel());
if (lpIconID == (LPTSTR)LB_ERR) return;
LPICONRESOURCE lpIR = NULL;
// 如果源是模塊文件,需重新讀取選中資源
if (szOpenFileExtName == _T("exe") || szOpenFileExtName == _T("dll") || szOpenFileExtName == _T("icl")) {
lpIR = pIcons->ReadIconFromEXEFile(szOpenFileName, lpIconID);
}
// ICO/BMP 源已在加載時獲取 lpIR
if (!lpIR || m_List.GetCount() == 0) {
AfxMessageBox(_T("無可保存的圖標資源!"));
return;
}
BeginWaitCursor();
BOOL success = FALSE;
if (szSaveExt == _T("bmp")) {
success = pIcons->IconImageToBMPFile(szSavePath, &lpIR->IconImages[0]);
} else if (szSaveExt == _T("ico")) {
success = pIcons->WriteIconToICOFile(lpIR, szSavePath);
}
EndWaitCursor();
if (!success) AfxMessageBox(_T("保存失敗!"));
}
WriteIconToICOFile 內部實現:
ICONDIR 頭部(保留字、類型=1、圖標數量)ICONDIRENTRY(寬、高、顏色數、保留、平面數、位深、數據大小、文件偏移)IconImageToBMPFile 將 ICONIMAGE 轉換為標準 DIB 格式寫入,處理顏色表、行字節對齊(4 字節倍數)、底-up 順序等細節。
| 難點 | 解決方案 |
|---|---|
| EXE/DLL 安全加載 | LoadLibraryEx(..., LOAD_LIBRARY_AS_DATAFILE) 僅映射資源節,不執行入口點代碼 |
| 資源枚舉回調上下文傳遞 | EnumResourceNames 的 lParam 傳遞 HWND,回調中 CWnd::FromHandle 獲取對話框指針 |
| 列表項關聯資源標識 | SetItemData 存儲資源名指針(LPTSTR),GetItemData 取回 |
| 多格式統一內部表示 | ICONRESOURCE 結構封裝多個 ICONIMAGE,屏蔽源格式差異 |
| 大圖標縮放顯示 | IconImageFromBMPFile 參數 bStretch=TRUE 啟用拉伸 |
| 內存管理 | GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE) + GlobalLock/Unlock/Free 管理緩衝區 |
CListCtrl + CImageList)通過封裝 CIcons/CDib 核心類,實現了從 EXE/DLL/ICL/ICO/BMP 多源格式提取圖標資源,並導出為 ICO/BMP 通用格式的完整工具。關鍵技術點包括:安全模塊加載、資源枚舉回調、列表項數據關聯、多格式統一內部表示、ICO/BMP 文件結構精確讀寫。該設計模塊化程度高,四個文件即可集成到任意 MFC 專案,極大降低了圖標資源處理的開發門檻。