在C/C++中获取可执行文件的图标和信息

在写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!\n"));
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\n"), ::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(LPCTSTRlpszExePath, 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('\0')};
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\n"), ::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('\0')};
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(LPCTSTRlpszExePath, 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('\0')};
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\n"), ::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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值