在写AutorunLoadViewer的过程中,需要能够获取可执行文件的图标和一些特定的文件信息(比如公司名还有文件版本.etc)
但是似乎MFC并没有提供现成的类库,于是只能自己通过API实现相应的功能
获取文件图标
关于如何获取文件图标,你可以很容易的在MSDN找到一个API——ExtracIcon
但是如果你仔细阅读文档说明,就会发现这个API并不符合我们的要求。因为ExtracIcon是从可执行文件的RC资源中找存在的图标,而不是返回在Win中显示的图标
所以,我们应该使用的API是SHGetFileInfo(关于这个API的详情,请自己查询相关文档)
每个文件都有一个SHFILEINFO结构,其中就包含了我们需要的图标。而我们要做的,就是利用SHGetFileInfo填充这个结构,然后获取图标资源即可
02 | 输
入: lpszExePath(LPCTSTR) - [in]文件路径 |
03 | 输
出: HCION - Handle:成功, NULL:非正确文件类型或不存在 |
06 | HICON CQueryExeInfo::QueryExeIcon( LPCTSTR lpszExePath) |
11 | DWORD_PTR dwRet
= ::SHGetFileInfo(lpszExePath, 0, &FileInfo, sizeof (SHFILEINFO),
SHGFI_ICON); |
16 | hIcon
= FileInfo.hIcon; |
这里需要注意的是,我们获取的图标是系统资源,不需要时我们应该手动对其进行清理。
由于我把这些函数封装在了CQueryExeInfo中,所以清理图标的函数是static成员变量,好处是独立于实际类对象
02 | 输
入: hIcon(HICON) - 图标句柄 |
06 | void CQueryExeInfo::FreeIconBuffer( HICON hIcon) |
08 | //
此函数为static,可以不依赖类对象使用 |
获取文件信息
在Win中,至少在目前为止,获取可执行文件的信息一直是一件很蛋疼的事情,以为我们要用到几个很“暧昧”的函数
和前面的SHFILEINFO类似,可执行文件的公司名、版本号之类的东西也保存在一个信息结构中,所以我们还是需要获取这个结构
CQueryExeInfo对应的成员函数是GetExeVersionInfo(protected)
02 | 输
入: lpszExePath(LPCTSTR) - [in]可执行文件路径 |
03 | 输
出: DWORD - Size:VersionInfo的字节数, FALSE:获取版本信息失败 |
04 | 功
能: 加载目标文件的VersionInfo |
06 | DWORD CQueryExeInfo::GetExeVersionInfo( LPCTSTR lpszExePath) |
11 | dwInfoSize
= ::GetFileVersionInfoSize(lpszExePath, 0); |
18 | //
申请内存,用于保存VersionInfo |
19 | m_lpVersionBuffer
= new TCHAR [dwInfoSize]; |
20 | ASSERT(m_lpVersionBuffer
!= NULL); |
21 | bRet
= ::GetFileVersionInfo(lpszExePath, NULL, dwInfoSize, m_lpVersionBuffer); |
其中m_lpVersionBuff是成员变量,类型是LPVOID,因为数据类型无法确定。这个变量保存了我们获得的文件版本信息结构
有了这个结构信息,我们就可以从里面“抽出”我们想要的东西。但是在做之前,我们还需要做一件事情——设置语言代码页(language codepages)
我们需要的东西保存在一个叫String Struct的东西里,而这个东西依赖lang-codepage。
呃,我也不明白为什么M$要这么设计
02 | 输
入: lpszExePath(LPCTSTR) - [in]可执行文件路径 |
03 | 输
出: DWORD - Size:LangCodePage, FALSE:设置代码页失败 |
06 | DWORD CQueryExeInfo::SetLangCodePage( LPCTSTR lpszExePath) |
08 | DWORD dwVersionInfoLen
= GetExeVersionInfo(lpszExePath); |
09 | UINT *
puCodeLangBuffer; |
16 | TRACE(_T( "File
not found!\n" )); |
20 | //
在获取Info之前,需要先设置lang-codepage |
21 | TCHAR szCodeLang[]
= _T( "\\VarFileInfo\\Translation" ); |
23 | if (!::VerQueryValue(m_lpVersionBuffer,
szCodeLang, ( LPVOID *)&puCodeLangBuffer,
&m_uSizeOfData)) |
25 | TRACE(_T( "Query
Code page faild. Error Code is %d\n" ),
::GetLastError()); |
30 | //
高字节段为Lang-Id,低字节段为Code-Page |
31 | dwLangSet
= MAKELONG(HIWORD(puCodeLangBuffer[0]), LOWORD(puCodeLangBuffer[0])); |
有了代码页的数据,就可以获取相应的信息了,比如我们要获取CompanyName
02 | 输
入: lpszExePath(LPCTSTR) - [in]可执行文件路径 |
03 | sCompanyName(CString&)
- [out]接受CompanyName |
04 | 输
出: BOOL - TRUE:成功, FALSE:失败 |
05 | 功
能: 获取目标文件的CompanyName |
07 | BOOL CQueryExeInfo::QueryExeCompanyName( LPCTSTR lpszExePath,
CString& sCompanyName) |
09 | DWORD dwLangSetRet
= SetLangCodePage(lpszExePath); |
17 | //
lang-codepage必须以hex形式表示 |
18 | TCHAR szText[MAX_STRINGTABLE_LEN]
= {_T( '\0' )}; |
19 | _stprintf(szText,
_T( "\\StringFileInfo\\%08x\\CompanyName" ),
dwLangSetRet); |
24 | if (!::VerQueryValue(m_lpVersionBuffer,
szText, &m_szInfoKey, &m_uSizeOfData)) |
26 | TRACE(_T( "Query
Company Name Failed. Error Code is %d\n" ),
::GetLastError()); |
31 | sCompanyName.Format(_T( "%s" ),
( LPCTSTR )m_szInfoKey); |
这里我们多次用到了FreeVersionBuffer,这个函数用于清理缓存区。
m_lpVersionBuffer需要我们自己释放,而m_uSizeOfData需要清零(zero out)
04 | 功
能: 清理VersinoInfo占用的内存空间 |
06 | void CQueryExeInfo::FreeVersionBuffer() |
08 | if (m_lpVersionBuffer
!= NULL) |
10 | delete []
m_lpVersionBuffer; |
11 | m_lpVersionBuffer
=NULL; |
14 | if (m_szInfoKey
!= NULL) |
如果我们要获取文件版本,那么该怎么做呢?
仔细分析下上面获取CompanyName的代码,发现指定类型只需要酱紫的代码
1 | //
lang-codepage必须以hex形式表示 |
2 | TCHAR szText[MAX_STRINGTABLE_LEN]
= {_T( '\0' )}; |
3 | _stprintf(szText,
_T( "\\StringFileInfo\\%08x\\[string-keyword]" ),
dwLangSetRet); |
里面的string-keyword指定了CompanyName、Fileversion这样的字段。关于详细的字段列表,请在MSDN中以String Structure为关键字搜索
而我们获取FileVersion的实现如下:
02 | 输
入: lpszExePath(LPCTSTR) - [in]可执行文件路径 |
03 | sFileVersion(CString&)
- [out]接受FileVersion |
04 | 输
出: BOOL - TRUE:成功, FALSE:失败 |
05 | 功
能: 获取目标文件的FileVersion |
07 | BOOL CQueryExeInfo::QueryExeFileVersion( LPCTSTR lpszExePath,
CString& sFileVersion) |
09 | DWORD dwLangSetRet
= SetLangCodePage(lpszExePath); |
17 | //
lang-codepage必须以hex形式表示 |
18 | TCHAR szText[MAX_STRINGTABLE_LEN]
= {_T( '\0' )}; |
19 | _stprintf(szText,
_T( "\\StringFileInfo\\%08x\\FileVersion" ),
dwLangSetRet); |
24 | if (!::VerQueryValue(m_lpVersionBuffer,
szText, &m_szInfoKey, &m_uSizeOfData)) |
26 | TRACE(_T( "Query
FileVersion Failed. Error Code is %d\n" ),
::GetLastError()); |
31 | sFileVersion.Format(_T( "%s" ),
( LPCTSTR )m_szInfoKey); |
如你所见,大部分代码其实都一样。
其实,我们完全可以只写一个QueryExeInfo,然后设置一个Info的枚举类型,将枚举类型和保存string-keyword的TCHAR*数组形成映射关系。
只是,这样可能导致可读性下降
还要注意的是,FileVersion的那几个API需要显式指定version.lib
头文件如下
view
source
print?03 | #pragma
comment(lib, "version.lib") |
05 | class CQueryExeInfo
: public CObject |
09 | virtual ~CQueryExeInfo(); |
10 | HICON QueryExeIcon( LPCTSTR lpszExePath); |
11 | static void FreeIconBuffer( HICON hIcon); |
12 | BOOL QueryExeCompanyName( LPCTSTR lpszExePath,
CString& sCompanyName); |
13 | BOOL QueryExeFileVersion( LPCTSTR lpszExePath,
CString& sFileVersion); |
16 | DWORD GetExeVersionInfo( LPCTSTR lpszExePath); |
17 | void FreeVersionBuffer(); |
18 | DWORD SetLangCodePage( LPCTSTR lpszExePath); |
21 | LPVOID m_lpVersionBuffer; |
ok,至此功能就完成了,详细代码请翻阅SRC