|
C++ Q&A... 原著:Paul DiLascia 下载源代码:Apr98CQAcode.exe (22KB)
让我们先看容易的方法。DllGetVersion 用 DLL 版本信息填写一个 DLLVERSIONINFO 结构。该结构定义在 Win32 SDK 的 showapi.h 头文件中。许多人可能都没有安装 Platform SDK,那么就得自己定义这个结构了(译者注:实际上,早期的 Developer Studio 不包含这个头文件。后来的 Visual Studio 6.0 安装已经包含该头文件,路经参见:Driver:/Program Files/Microsoft Visual Studio/VC98/Include),就像我在 VersionDlg 所做的那样。 typedef struct _DllVersionInfo {
DWORD cbSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformID;
} DLLVERSIONINFO;
这个结构中的字段基本不用怎么说明就知道是什么意思:dwPlatformID 为 DLLVER_PLATFORM_WINDOWS (value = 1)指 Windows 9x,而 DLLVER_PLATFORM_NT (value = 2)用于 Windows NT。一旦定义了 DLLVERSIONINFO 结构,就可以调用 DllGetVersion 了,该函数的署名如下: HRESULT DllGetVersion(DLLVERSIONINFO*); 因为并不是任何给定的 Dll 都输出 DllGetVersion 函数,你得按照标准套路来调用它,即调用 GetProcAddress 并判断返回值是否为 NULL。我编写的类 CModuleVersion 中含有一个 DllGetVersion 函数,它把所有细节都进行了封装(参见 Figure 2 中的 ModulVer.cpp。)CModuleVersion 类的使用方法如下: DLLVERSIONINFO dvi;
if (CModuleVersion::DllGetVersion("comctl32.dll", dvi))
{
// now info is in dvi
}
DllGetVersion 是一个比较新的函数(译者注:在1998年是这样。)对于 comctl32 很好使,因为它实现并输出 DllGetVersion——但是对于那些不输出 DllGetVersion 的 DLLs 来说怎么办呢?例如:shell32.dll 就没有实现 DllGetVersion,如 Figure 3 所示。这时你就得用可怕以及奇怪的 GetFileVersionInfo 和 VerQueryValue 函数,它们在 winver.h 中定义。
大多数可执行程序和 DLLs 都具备 VS_VERSION_INFO 资源,在模块的 RC 文件中定义。Figure 4 是 VersionDlg 的 RC 文件中的版本信息。你可以用文本编辑器或者 Visual Studio 直接编辑资源文件中的这段信息。你可以指定文件版本,产品版本等等,以及任何你想要编辑的字段,如:CompanyName、InternalName。文件版本信息与 Exe 或 DLL 文件在资源管理器“属性”页“版本”标签中显示的信息相同(参见 Figure 5)。
等一会儿你就会发现,这些版本 APIs 十分暧昧,很容易把人搞晕菜,但 CModuleVersion 使一切都变得简单明了。这个类派生于 VS_FIXEDFILEINFO(参见 Figure 6),此结构包含“固定的”版本信息,其中有主版本号和次版本号,还有一些 DLLVERSIONINFO 里的东西。使用 CModuleVersion 时,只要像下面这样写即可: CModuleVersion ver;
if (ver.GetFileVersionInfo(_T("comctl32.dll"))
{
WORD major = HIWORD(ver.dwFileVersionMS);
WORD minor = LOWORD(ver.dwFileVersionMS);
...
}
为了存取 CompanyName 这样的可变信息以及内涵的模块创建信息,你可以用另外一个函数 CModuleVersion:: GetValue,例如,下面代码段执行之后,sCompanyName 的值将类似“XYZ”或“Acme Corporation”这样的公司名称: CString sCompanyName =
ver.GetValue(_T("CompanyName"));
CModuleVersion 隐藏了获取信息所要做的所有邋遢细节——相信我,都是些邋遢细节!如果你只是想使用 CModuleVersion,那么看到这里就可以打住了;如果你想要了解 CModuleVersion 的工作原理,那就继续往下看。 LPVOID lpvi;
UINT iLen;
VerQueryValue(buf, _T("//"), &lpvi, &iLen);
此处 buf 是从 GetFileVersionInfo 返回的完整信息。字符串“/”(在 C 中用“//”),你如果把它看作是一个目录,那它就是根信息(有一点像注册表)。VerQueryValue 将 lpvi 置到 VS_FIXEDFILEINFO 的起始处,iLen 为其长度。 // in CModuleVersion
struct TRANSLATION {
WORD langID // language ID
WORD charset; // code page
} m_translation;
为了获取语言信息,CModuleVersion 用 VerQueryValue 函数以 /VarFileInfo/Translation 作为键。 if (VerQueryValue(m_pVersionInfo,"//VarFileInfo//Translation", &lpvi, &iLen) && iLen >= 4)
{
m_translation = *(TRANSLATION*)lpvi;
}
一旦你知道了语言ID和代码页,你就可以得到 CompanyName 和 InternalName 这样的可变信息。实现方法是构造一个如下形式的查询: /StringFileInfo/<langID><codepage>/<keyname> 这里 <langID> 是十六进制 ASCI 形式的语言ID(中文是 0804;US English 是 0409),<codepage> 是代码页,格式为(1252 即 ANSI 的代码页是04e4),<keyname> 是你想要的键,如:CompanyName。为了构造这个查询,你得用 sprintf 或者 CString::Format 来构造字符串: //StringFileInfo//040904e4//CompanyName 然后将这个字符串传给 VerQueryValue。如果你对这些繁琐的细节感到晕菜,不用担心——很幸运,CModuleVersion::GetValue 对所有邋遢细节都进行了封装,所以你只要像下面这样写即可: CString s = ver.GetValue(_T("CompanyName"));
实现了 CModuleVersion,VersionDlg 就简单多了。 它实际上就是一个对话框,这个对话框带有一个编辑框,用于输入模块名称,每当用户在编辑框中敲入模块名称时,MFC 便调用 ON_EN_CHANGE 消息处理例程 CVersionDialog::OnChangedModule。OnChangedModule 例程通过 CModuleVersion 对象及其 GetFileVersionInfo 和 GetDllVersion 函数来获得版本信息,然后将信息显示在对话框的两个静态文本控件中。这个过程很简单。 // 告诉链接器与 version.lib 进行链接 #pragma comment(linker, "/defaultlib:version.lib") 现在,有人可能会问,为什么这些东西如此重要?以及谁会需要这些东西呢?一般来说,如果你编写的是显示文件属性之类的工具程序,那你只是需要获取诸如 CompanyName 和 LegalCopyright 之类的变量。但你也许发现用 CModuleVersion 从自己的应用程序中吸取文件信息很有用,例如,为了在“关于”对话框和启动屏幕中显示版本信息。如果你使用 CModuleVersion,你只需修改资源文件中相应位置的版本信息即可,“关于”对话框和启动屏幕会自动显示当前最新版本信息。 if (version <= 470) // do one thing else if (version==471) // do something else else if (version==472) // do a third thing else // scream 这是一件很郁闷的事情,我敢说这也是微软的大佬们引入 DllGetVersion 来快速获取版本号的一个原因,从而避免了面对让人恐惧的 GetFileVersionInfo 函数,只用它来获取语言 IDs 和代码页(仅在需要获取诸如 CompanyName 这样的信息时使用)。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
作者简介Paul DiLascia 是一名自由作家,软件咨询顾问以及大型 Web/UI 的设计师。他是《Writing Reusable Windows Code in C++》书(Addison-Wesley, 1992)的作者。业余时间他开发 PixeLib,这是一个 MFC 类库,从 Paul 的网站 http://www.dilascia.com 可以获得这个类库。 . |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
本文出自 Microsoft System Journal (MSJ) 的 April 1998 期刊,可通过当地报摊获得,或者最好是 订阅
VersionDlg.cpp //////////////////////////////////////////////////////////////////
// VersionDlg 1998 Microsoft Systems Journal.
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
// VersionDlg illustrates how to use CModuleVersion and DllGetVersion to
// read the version info for a DLL/EXE. The only interesting function
// for the purpose of CModuleVersion is OnChangedModule, which is called
// when the user enters a new module name into the edit control.
//
#include "stdafx.h"
#include "resource.h"
#include "ModulVer.h"
#include "StatLink.h"
#include "TraceWin.h"
//#include <shlwapi.h> // if you have the Nov 1997 SDK installed
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CVersionDialog dialog
//
class CVersionDialog : public CDialog {
public:
CVersionDialog(CWnd* pParent = NULL);
CString m_sModuleName; // module name typed by user
protected:
CStaticLink m_wndLink1; // web link
CStaticLink m_wndLink2; // web link
CStaticLink m_wndLink3; // web link
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
virtual void OnOK();
afx_msg void OnChangedModule();
DECLARE_MESSAGE_MAP()
};
////////////////////////////////////////////////////////////////
// CMyApp
//
class CMyApp : public CWinApp {
public:
CMyApp() { }
virtual BOOL InitInstance();
} theApp;
/////////////////
// Initialize: just run the dialog and quit.
//
BOOL CMyApp::InitInstance()
{
CVersionDialog dlg; // create dialog..
dlg.DoModal(); // ..run it
return FALSE; // ..and quit
}
//////////////////////////////////////////////////////////////////
// CVersionDialog
//
BEGIN_MESSAGE_MAP(CVersionDialog, CDialog)
ON_EN_CHANGE(IDC_EDIT_MODULE, OnChangedModule)
END_MESSAGE_MAP()
CVersionDialog::CVersionDialog(CWnd* pParent) : CDialog(IDD_VERSION, pParent)
{
}
/////////////////
// Initialize dialog: subclass static hyperlinks
//
BOOL CVersionDialog::OnInitDialog()
{
m_wndLink1.SubclassDlgItem(IDC_STATICPD, this,
_T("http://pobox.com/~dilascia"));
m_wndLink2.SubclassDlgItem(IDC_STATICMSJ, this,
_T("http://www.microsoft.com/msj"));
m_wndLink3.SubclassDlgItem(IDC_ICONMSJ, this,
_T("http://www.microsoft.com/msj"));
return CDialog::OnInitDialog();
}
/////////////////
// When user pressed Enter, don't exit
//
void CVersionDialog::OnOK()
{
return; // (don't exit)
}
//////////////////
// Standard MFC DDX data exchange for edit control
//
void CVersionDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_MODULE, m_sModuleName);
}
//////////////////
// User changed the module name: vet version info if I can.
//
void CVersionDialog::OnChangedModule()
{
UpdateData(TRUE); // get dialog data (module name)
CString s;
CModuleVersion ver;
// 1st get version using File version API
//
if (ver.GetFileVersionInfo(m_sModuleName)) {
// display file version from VS_FIXEDFILEINFO struct
s.Format("Version: %d.%d.%d.%d/n",
HIWORD(ver.dwFileVersionMS), LOWORD(ver.dwFileVersionMS),
HIWORD(ver.dwFileVersionLS), LOWORD(ver.dwFileVersionLS));
// display a bunch of string values
static LPCTSTR Keys[] = {
_T("CompanyName"),
_T("FileDescription"),
_T("FileVersion"),
_T("InternalName"),
_T("LegalCopyright"),
_T("OriginalFilename"),
_T("ProductName"),
_T("ProductVersion"),
NULL
};
for (int i=0; Keys[i]; i++) {
CString temp;
temp.Format("%s:/t%s/n", Keys[i], ver.GetValue(Keys[i]));
s += temp;
}
}
// set static text
GetDlgItem(IDC_STATICINFO)->SetWindowText(s);
// 2nd get version using DllGetVersion API
//
s.Empty();
DLLVERSIONINFO dvi;
if (ver.DllGetVersion(m_sModuleName, dvi)) {
s.Format(_T("DLL Version = %d.%02d/nBuild# = %d/n"),
dvi.dwMajorVersion,
dvi.dwMinorVersion,
dvi.dwBuildNumber);
s +=_T("Platform is ");
if (dvi.dwPlatformID == DLLVER_PLATFORM_WINDOWS)
s +=_T("Windows");
else if (dvi.dwPlatformID == DLLVER_PLATFORM_NT)
s +=_T("Windows NT");
else
s += _T("unrecognized");
} else {
s += _T("This file does not implement DllGetVersion.");
}
// set static text
GetDlgItem(IDC_STATICINFO2)->SetWindowText(s);
}
ModulVer.h ////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal
//
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
#ifndef __MODULEVER_H
#define __MODULEVER_H
// tell linker to link with version.lib for VerQueryValue, etc.
#pragma comment(linker, "/defaultlib:version.lib")
#ifndef DLLVERSIONINFO
// following is from shlwapi.h, in November 1997 release of the Windows SDK
typedef struct _DllVersionInfo
{
DWORD cbSize;
DWORD dwMajorVersion; // Major version
DWORD dwMinorVersion; // Minor version
DWORD dwBuildNumber; // Build number
DWORD dwPlatformID; // DLLVER_PLATFORM_*
} DLLVERSIONINFO;
// Platform IDs for DLLVERSIONINFO
#define DLLVER_PLATFORM_WINDOWS 0x00000001 // Windows 95
#define DLLVER_PLATFORM_NT 0x00000002 // Windows NT
#endif // DLLVERSIONINFO
//////////////////
// CModuleVersion version info about a module.
// To use:
//
// CModuleVersion ver
// if (ver.GetFileVersionInfo("_T("mymodule))) {
// // info is in ver, you can call GetValue to get variable info like
// CString s = ver.GetValue(_T("CompanyName"));
// }
//
// You can also call the static fn DllGetVersion to get DLLVERSIONINFO.
//
class CModuleVersion : public VS_FIXEDFILEINFO {
protected:
BYTE* m_pVersionInfo; // all version info
struct TRANSLATION {
WORD langID; // language ID
WORD charset; // character set (code page)
} m_translation;
public:
CModuleVersion();
virtual ~CModuleVersion();
BOOL GetFileVersionInfo(LPCTSTR modulename);
CString GetValue(LPCTSTR lpKeyName);
static BOOL DllGetVersion(LPCTSTR modulename, DLLVERSIONINFO& dvi);
};
#endif
ModulVer.cpp ////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
// CModuleVersion provides an easy way to get version info
// for a module.(DLL or EXE).
//
#include "StdAfx.h"
#include "ModulVer.h"
CModuleVersion::CModuleVersion()
{
m_pVersionInfo = NULL; // raw version info data
}
//////////////////
// Destroy: delete version info
//
CModuleVersion::~CModuleVersion()
{
delete [] m_pVersionInfo;
}
//////////////////
// Get file version info for a given module
// Allocates storage for all info, fills "this" with
// VS_FIXEDFILEINFO, and sets codepage.
//
BOOL CModuleVersion::GetFileVersionInfo(LPCTSTR modulename)
{
m_translation.charset = 1252; // default = ANSI code page
memset((VS_FIXEDFILEINFO*)this, 0, sizeof(VS_FIXEDFILEINFO));
// get module handle
TCHAR filename[_MAX_PATH];
HMODULE hModule = ::GetModuleHandle(modulename);
if (hModule==NULL && modulename!=NULL)
return FALSE;
// get module file name
DWORD len = GetModuleFileName(hModule, filename,
sizeof(filename)/sizeof(filename[0]));
if (len <= 0)
return FALSE;
// read file version info
DWORD dwDummyHandle; // will always be set to zero
len = GetFileVersionInfoSize(filename, &dwDummyHandle);
if (len <= 0)
return FALSE;
m_pVersionInfo = new BYTE[len]; // allocate version info
if (!::GetFileVersionInfo(filename, 0, len, m_pVersionInfo))
return FALSE;
LPVOID lpvi;
UINT iLen;
if (!VerQueryValue(m_pVersionInfo, _T("//"), &lpvi, &iLen))
return FALSE;
// copy fixed info to myself, which am derived from VS_FIXEDFILEINFO
*(VS_FIXEDFILEINFO*)this = *(VS_FIXEDFILEINFO*)lpvi;
// Get translation info
if (VerQueryValue(m_pVersionInfo,
"//VarFileInfo//Translation", &lpvi, &iLen) && iLen >= 4) {
m_translation = *(TRANSLATION*)lpvi;
TRACE("code page = %d/n", m_translation.charset);
}
return dwSignature == VS_FFI_SIGNATURE;
}
//////////////////
// Get string file info.
// Key name is something like "CompanyName".
// returns the value as a CString.
//
CString CModuleVersion::GetValue(LPCTSTR lpKeyName)
{
CString sVal;
if (m_pVersionInfo) {
// To get a string value must pass query in the form
//
// "/StringFileInfo/<langID><codepage>/keyname"
//
// where <langID><codepage> is the languageID concatenated with the
// code page, in hex. Wow.
//
CString query;
query.Format(_T("//StringFileInfo//%04x%04x//%s"),
m_translation.langID,
m_translation.charset,
lpKeyName);
LPCTSTR pVal;
UINT iLenVal;
if (VerQueryValue(m_pVersionInfo, (LPTSTR)(LPCTSTR)query,
(LPVOID*)&pVal, &iLenVal)) {
sVal = pVal;
}
}
return sVal;
}
// typedef for DllGetVersion proc
typedef HRESULT (CALLBACK* DLLGETVERSIONPROC)(DLLVERSIONINFO *);
/////////////////
// Get DLL Version by calling DLL's DllGetVersion proc
//
BOOL CModuleVersion::DllGetVersion(LPCTSTR modulename, DLLVERSIONINFO& dvi)
{
HINSTANCE hinst = LoadLibrary(modulename);
if (!hinst)
return FALSE;
// Must use GetProcAddress because the DLL might not implement
// DllGetVersion. Depending upon the DLL, the lack of implementation of the
// function may be a version marker in itself.
//
DLLGETVERSIONPROC pDllGetVersion =
(DLLGETVERSIONPROC)GetProcAddress(hinst, _T("DllGetVersion"));
if (!pDllGetVersion)
return FALSE;
memset(&dvi, 0, sizeof(dvi)); // clear
dvi.cbSize = sizeof(dvi); // set size for Windows
return SUCCEEDED((*pDllGetVersion)(&dvi));
}
Figure 4Ver.rc
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Written by Paul DiLascia/0"
VALUE "FileDescription",
"VersionDlg Application by Paul DiLascia/0"
VALUE "FileVersion", "1, 0, 0, 1/0"
VALUE "InternalName", "VersionDlg/0"
VALUE "LegalCopyright", "1998 Paul DiLascia/0"
VALUE "LegalTrademarks", "/0"
VALUE "OriginalFilename", "VersionDlg.EXE/0"
VALUE "ProductName", "VersionDlg/0"
VALUE "ProductVersion", "1, 0, 0, 1/0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
Figure 6VS_FIXEDFILEINFO typedef struct tagVS_FIXEDFILEINFO
{
DWORD dwSignature; /* e.g. 0xfeef04bd */
DWORD dwStrucVersion; /* e.g. 0x00000042 = "0.42" */
DWORD dwFileVersionMS; /* e.g. 0x00030075 = "3.75" */
DWORD dwFileVersionLS; /* e.g. 0x00000031 = "0.31" */
DWORD dwProductVersionMS; /* e.g. 0x00030010 = "3.10" */
DWORD dwProductVersionLS; /* e.g. 0x00000031 = "0.31" */
DWORD dwFileFlagsMask; /* = 0x3F for version "0.42" */
DWORD dwFileFlags; /* e.g. VFF_DEBUG | VFF_PRERELEASE */
DWORD dwFileOS; /* e.g. VOS_DOS_WINDOWS16 */
DWORD dwFileType; /* e.g. VFT_DRIVER */
DWORD dwFileSubtype; /* e.g. VFT2_DRV_KEYBOARD */
DWORD dwFileDateMS; /* e.g. 0 */
DWORD dwFileDateLS; /* e.g. 0 */
} VS_FIXEDFILEINFO;
Figure 7 Language IDs and Code Pages
|
我如何获得安装在我的系统上的某个特定的 DLL 的版本信息?我尝试着确定系统安装了哪个版本的 comctl32.dll。我见过有些代码调用 GetProcAddress 来获取各种函数,如 InitCommonControlsEx,以确定基于不同版本的函数调用。对于我来说,这是一个坎儿,到底用什么方法获得版本号?
有两种方法:容易的和难的。容易的方法是调用一个专门用于此目的的函数 DllGetVersion。问题是虽然 comctl32.dll 支持该函数,但并不是所有的 DLLs 都具备它。如果不具备 DllGetVersion,那么就得用难的方法——使用 FileVersion API,这可能是你要遭遇到的最为暧昧的 API 之一。我写了一个类 CModuleVersion 来封装两种方法,同时还写了一个Demo程序 VersionDlg 来示范 CModuleVersion 的使用方法。程序画面如 Figure 1 所示。你可以在编辑框中敲入任何系统模块的名字,VersionDlg 将用 DllGetVersion (如果具备这个函数的话)和 FileVersion API 两种方法显示版本信息。源代码参见 


作者简介
posted on
浙公网安备 33010602011771号