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