在写AutorunLoadViewer的过程中,需要能够获取可执行文件的图标和一些特定的文件信息(比如公司名还有文件版本.etc)
但是似乎MFC并没有提供现成的类库,于是只能自己通过API实现相应的功能

获取文件图标
关于如何获取文件图标,你可以很容易的在MSDN找到一个API——ExtracIcon
但是如果你仔细阅读文档说明,就会发现这个API并不符合我们的要求。因为ExtracIcon是从可执行文件的RC资源中找存在的图标,而不是返回在Win中显示的图标
所以,我们应该使用的API是SHGetFileInfo(关于这个API的详情,请自己查询相关文档)
每个文件都有一个SHFILEINFO结构,其中就包含了我们需要的图标。而我们要做的,就是利用SHGetFileInfo填充这个结构,然后获取图标资源即可
06 | HICON CQueryExeInfo::QueryExeIcon( LPCTSTR lpszExePath) |
11 | DWORD_PTR dwRet = ::SHGetFileInfo(lpszExePath, 0, &FileInfo, sizeof (SHFILEINFO), SHGFI_ICON); |
16 | hIcon = FileInfo.hIcon; |
这里需要注意的是,我们获取的图标是系统资源,不需要时我们应该手动对其进行清理。
由于我把这些函数封装在了CQueryExeInfo中,所以清理图标的函数是static成员变量,好处是独立于实际类对象
06 | void CQueryExeInfo::FreeIconBuffer( HICON hIcon) |
获取文件信息
在Win中,至少在目前为止,获取可执行文件的信息一直是一件很蛋疼的事情,以为我们要用到几个很“暧昧”的函数
和前面的SHFILEINFO类似,可执行文件的公司名、版本号之类的东西也保存在一个信息结构中,所以我们还是需要获取这个结构
CQueryExeInfo对应的成员函数是GetExeVersionInfo(protected)
06 | DWORD CQueryExeInfo::GetExeVersionInfo( LPCTSTR lpszExePath) |
11 | dwInfoSize = ::GetFileVersionInfoSize(lpszExePath, 0); |
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$要这么设计
06 | DWORD CQueryExeInfo::SetLangCodePage( LPCTSTR lpszExePath) |
08 | DWORD dwVersionInfoLen = GetExeVersionInfo(lpszExePath); |
09 | UINT * puCodeLangBuffer; |
13 | if (!dwVersionInfoLen) |
16 | TRACE(_T( "File not found!\n" )); |
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()); |
31 | dwLangSet = MAKELONG(HIWORD(puCodeLangBuffer[0]), LOWORD(puCodeLangBuffer[0])); |
有了代码页的数据,就可以获取相应的信息了,比如我们要获取CompanyName
07 | BOOL CQueryExeInfo::QueryExeCompanyName( LPCTSTR lpszExePath, CString& sCompanyName) |
09 | DWORD dwLangSetRet = SetLangCodePage(lpszExePath); |
18 | TCHAR szText[MAX_STRINGTABLE_LEN] = {_T( '\0' )}; |
19 | _stprintf(szText, _T( "\\StringFileInfo\\%08x\\CompanyName" ), dwLangSetRet); |
21 | ASSERT(m_uSizeOfData); |
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)
06 | void CQueryExeInfo::FreeVersionBuffer() |
08 | if (m_lpVersionBuffer != NULL) |
10 | delete [] m_lpVersionBuffer; |
11 | m_lpVersionBuffer = NULL; |
14 | if (m_szInfoKey != NULL) |
如果我们要获取文件版本,那么该怎么做呢?
仔细分析下上面获取CompanyName的代码,发现指定类型只需要酱紫的代码
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的实现如下:
07 | BOOL CQueryExeInfo::QueryExeFileVersion( LPCTSTR lpszExePath, CString& sFileVersion) |
09 | DWORD dwLangSetRet = SetLangCodePage(lpszExePath); |
18 | TCHAR szText[MAX_STRINGTABLE_LEN] = {_T( '\0' )}; |
19 | _stprintf(szText, _T( "\\StringFileInfo\\%08x\\FileVersion" ), dwLangSetRet); |
21 | ASSERT(m_uSizeOfData); |
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
头文件如下
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