在 Windows 程式開發中,確認系統上特定動態連結庫(DLL)的版本號是常見需求。例如判斷 comctl32.dll 版本以決定是否支援某些 Common Controls 功能。許多開發者嘗試透過 GetProcAddress 取得特定函數位址來間接推斷版本,但這種方式既繁瑣又不夠可靠。本文將系統性介紹兩種標準獲取 DLL 版本的方法,並提供一個封裝良好的 CModuleVersion 類別,大幅簡化開發流程。
現代 Windows DLL(特別是系統元件如 comctl32.dll、shell32.dll 等)通常匯出 DllGetVersion 函數,專門用於回報版本資訊。其原型定義於 shlwapi.h(舊版 SDK 在 showapi.h)中:
typedef struct _DLLVERSIONINFO {
DWORD cbSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformID; // DLLVER_PLATFORM_WINDOWS (1) 或 DLLVER_PLATFORM_NT (2)
} DLLVERSIONINFO;
HRESULT DllGetVersion(DLLVERSIONINFO *pdvi);
由於並非所有 DLL 都實作此函數,呼叫時必須採用標準動態連結流程:
LoadLibrary 載入目標 DLLGetProcAddress 取得 DllGetVersion 位址DLLVERSIONINFO 結構FreeLibrary 釋放模組CModuleVersion 類別已將上述流程封裝為靜態方法,使用極為直觀:
DLLVERSIONINFO dvi;
if (CModuleVersion::DllGetVersion(_T("comctl32.dll"), dvi)) {
// dvi.dwMajorVersion, dvi.dwMinorVersion 等即為版本號
}
當 DLL 未提供 DllGetVersion(如舊版 shell32.dll)時,必須使用版本資源 API。這組 API 包含 GetFileVersionInfoSize、GetFileVersionInfo、VerQueryValue 等,定義於 winver.h,需連結 version.lib。
大多數 EXE/DLL 內建 VS_VERSION_INFO 資源,包含固定資訊(VS_FIXEDFILEINFO:檔案版本、產品版本等)與可變字串資訊(公司名稱、版權、內部名稱等),這些正是エクスプローラ「屬性 → 版本」頁籤顯示的內容。
CModuleVersion 繼承自 VS_FIXEDFILEINFO,可直接存取固定欄位:
CModuleVersion ver;
if (ver.GetFileVersionInfo(_T("comctl32.dll"))) {
WORD major = HIWORD(ver.dwFileVersionMS);
WORD minor = LOWORD(ver.dwFileVersionMS);
// ...
}
可變字串則透過 GetValue 取得:
CString sCompany = ver.GetValue(_T("CompanyName"));
CString sCopyright = ver.GetValue(_T("LegalCopyright"));
GetFileVersionInfo 先呼叫 GetFileVersionInfoSize 取得緩衝區大小,配置記憶體後再呼叫 GetFileVersionInfo 填充資料。原始緩衝區包含固定與可變兩區塊。固定資訊對應根節點 "\\":
LPVOID lpvi; UINT len;
VerQueryValue(buf, _T("\\"), &lpvi, &len); // lpvi 指向 VS_FIXEDFILEINFO
可變資訊依語言與代碼頁組織,需先查詢 "\\VarFileInfo\\Translation" 取得 langID 與 charset(如繁體中文 0x0404、ANSI 代碼頁 0x04E4),再構造鍵值路徑:
\\StringFileInfo\\040404E4\\CompanyName
CModuleVersion::GetValue 內部自動處理語言查詢、字串格式化與 VerQueryValue 呼叫,開發者完全無需關心細節。
隨文附贈的 VersionDlg 為 MFC 對話框程式,包含編輯框輸入模組名稱,即時以兩種方法顯示版本資訊。核心邏輯於 ON_EN_CHANGE 處理函式 OnChangedModule 中:
void CVersionDialog::OnChangedModule() {
CString moduleName;
GetDlgItemText(IDC_MODULE, moduleName);
// 方法一:DllGetVersion
DLLVERSIONINFO dvi;
if (CModuleVersion::DllGetVersion(moduleName, dvi)) {
FormatAndDisplay(dvi, m_staticDllVer);
}
// 方法二:FileVersion API
CModuleVersion ver;
if (ver.GetFileVersionInfo(moduleName)) {
FormatAndDisplay(ver, m_staticFileVer);
}
}
版本 API 屬於 version.lib,若專案設定未加入此庫會產生連結錯誤。ModuleVer.h 採用 #pragma comment 指令自動導入:
// 告訴連結器自動連結 version.lib
#pragma comment(linker, "/defaultlib:version.lib")
此技巧極為實用,免去手動設定專案屬性頁的麻煩。
if (ver <= 470) ... else if (ver == 471) ... 這類脆弱的硬編碼邏輯。早期 comctl32.dll 版本混亂(4.70、4.71、4.72…6.00 隨 IE 發布),導致應用程式相容性極差。微軟後續允許應用程式隨安裝包重新分發 comctl32.dll(詳見 MSDN 文件 40comupd.htm),並引入 Side-by-Side 組件機制徹底解決 DLL Hell 問題。現代開發建議使用 Manifest 指定相依版本,而非直接檢查檔案版本。
獲取 DLL 版本資訊,優先嘗試 DllGetVersion(快速、結構化);若不支援則回退至 FileVersion API(通用、資訊豐富)。CModuleVersion 類別以乾淨的介面封裝雙重機制,並自動處理記憶體管理、語言查詢、資源釋放等繁瑣細節,是 Windows 系統程式開發者的利器。完整原始碼請參考本文附件。