zoukankan      html  css  js  c++  java
  • 在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! "));
    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(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('')};
    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(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('')};
    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

  • 相关阅读:
    搜索框用定时器限制发送请求
    vue的生命周期,钩子函数
    事件委托的实现流程
    在vscode中快速生成vue模板
    JS继承
    各种宽高
    ES6新特性
    python入门学习一
    字符编码
    npm install --save 与 npm install --save-dev 的区别
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13318844.html
Copyright © 2011-2022 走看看