目录
1、通过代码制作安装包程序,不再使用专用的打包工具
2、问题说明
3、为啥路径中包含环境变量%ProgramFiles%会报找不到路径呢?
4、使用API Monitor监测QQ安装包在创建桌面快捷方式时都调用哪些COM组件的接口
5、同时勾选IShelllinkDatalist接口类的接口,重新开启监测
6、最后
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战进阶(已更新到400多篇,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585Windows C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlC++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html 为了解决项目中遇到的问题,我们多次使用API Monitor探测主流软件在实现类似功能时调用了哪些API或COM接口,参考API接口调用及参数设置。本文通过一个项目实例分享一下API Monitor工具的使用方法和相关细节,以供借鉴或参考。
1、通过代码制作安装包程序,不再使用专用的打包工具
为了更自主的控制软件安装包、更方便地定制安装包的UI界面,我们不再借助InstallShield、Inno Setup、NSIS等专业打包工具,我们自己用代码编写一个安装包程序,实现这些打包工具类似的功能,比如:
1)将要打包的所有文件做成压缩包,作为资源添加到安装包工程中。执行安装时从工程资源中将压缩包取出来,然后进行解压。
2)执行文件的拷贝操作。将打包的文件拷贝到安装目录中,并将部分文件拷贝到系统相应的目录中。
3)注册相关控件。程序中可能包含ocx或者ATL控件,这些控件在安装时需要向系统注册。
4)创建桌面快捷方式和开始菜单快捷方式。
5)向注册表中写入安装卸载注册表信息。其中最重要的是安装路径信息,卸载程序时需要从注册表中读取安装路径,将文件从安装路径中删除掉。
2、问题说明
在创建桌面快捷方式时遇到了问题。软件的exe主程序换了新的图标,但安装好安装包之后,桌面快捷方式的图标并没有更新。于是尝试到快捷方式和属性中手动去更新快捷方式图标。结果在64位系统中,右键点击桌面快捷方式,打开快捷方式的属性页面:(以腾讯会议的桌面快捷方式为例,我们的软件不是腾讯会议哈!)
点击更改图标按钮,弹出如下页面:
点击确定,却报如下的错误:
路径中包含了一个%ProgramFiles%的环境变量,找不到对应的exe主程序路径。
所以,此处主要有两个问题:
1)exe主程序更新了,但安装后桌面快捷方式图标没有更新;
2)尝试到快捷方式属性中去手动更新快捷方式图标时,指向的exe主程序路径中居然包含了%ProgramFiles%环境变量,导致找不到exe主程序。
快捷方式图标的更新,应该是通过该快捷方式指向的exe程序的图标去更新的。所以,第一个问题可能是第二个问题导致的。
3、为啥路径中包含环境变量%ProgramFiles%会报找不到路径呢?
为什么通过这个包含%ProgramFiles%环境变量的路径找不到主程序呢?因为我们的程序是32位的,在64位系统中会默认安装到C:\Program Files (x86)路径中,而在64位系中%ProgramFiles%环境变量指向的应该是C:\Program Files,所以找不到快捷方式指向的exe程序。
在64位系统中,32位程序和64位程序的默认安装路径是有区别的,32位程序会默认安装到C:\Program Files (x86)路径中,64位程序会默认安装到C:\Program Files路径中。写代码时,调用的API接口获取的都是C:\Program Files路径,系统会根据当前程序的版本,返回重定向后的路径,即32位程序返回C:\Program Files (x86)路径,64位程序返回C:\Program Files路径。
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到500多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:
C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795
常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!
专栏3:(本专栏涵盖了多方面的内容,是当前重点打造的专栏,专栏文章已经更新到400多篇,持续更新中...)
C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。
专栏4:
VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。
专栏5:
Windows C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根据多年C++软件开发实践,详细地总结了Windows C++ 应用软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
4、使用API Monitor监测QQ安装包在创建桌面快捷方式时都调用哪些COM组件的接口
我们创建的桌面快捷方式出现上述问题,应该是我们写的创建快捷方式的代码有问题,估计是漏调用了一些接口了。于是想到可以使用API Monitor工具监测一下主流软件,看看它们都调用了哪些接口。QQ是主流软件,功能完备强大,于是决定去监测QQ的安装包程序。
我们原先创建快捷方式的代码如下:(网上的通用代码)
/*函数功能:创建快捷方式 函数参数: lpszFileName 指定快捷方式指向的实际exe文件路径 lpszLnkFileDir 指定目录,不能为NULL。 lpszLnkFileName 快捷方式完整路径 wHotkey 为0表示不设置快捷键 pszDescription 备注 iShowCmd 运行方式,默认为常规窗口
*/
BOOL CreateFileShortcut( LPCSTR lpszFileName, LPCSTR lpszLnkFileDir, LPCSTR lpszLnkFileName, LPCSTR lpszWorkDir, WORD wHotkey, LPCTSTR lpszDescription, int iShowCmd )
{ if ( lpszLnkFileDir == NULL ) return FALSE; HRESULT hr; IShellLink *pLink; //IShellLink对象指针 IPersistFile *ppf; //IPersisFil对象指针 //创建IShellLink对象 hr = CoCreateInstance( CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&pLink ); //从IShellLink对象中获取IPersistFile接口 hr = pLink->QueryInterface(IID_IPersistFile, (void**)&ppf); if (FAILED(hr)) { pLink->Release(); return FALSE; } //目标 if ( lpszFileName == NULL ){pLink->SetPath(_pgmptr);}else{pLink->SetPath(lpszFileName);} //工作目录 if (lpszWorkDir != NULL){pLink->SetPath(lpszWorkDir);}//快捷键 if ( wHotkey != 0 ){ pLink->SetHotkey(wHotkey);}//备注 if (lpszDescription != NULL){pLink->SetDescription(lpszDescription);}//显示方式 pLink->SetShowCmd( iShowCmd ); //快捷方式的路径 + 名称 char szBuffer[MAX_PATH] = { 0 }; if (lpszLnkFileName != NULL) //指定了快捷方式的名称{sprintf(szBuffer, "%s\\%s", lpszLnkFileDir, lpszLnkFileName);}else { //没有指定名称,就从取指定文件的文件名作为快捷方式名称 char *pstr; if (lpszFileName != NULL){pstr = (char*)strrchr(lpszFileName, '\\');}else{ pstr = strrchr(_pgmptr, '\\');} if (pstr == NULL) { ppf->Release(); pLink->Release(); return FALSE; }//注意后缀名要从.exe改为.lnk sprintf(szBuffer, "%s\\%s", lpszLnkFileDir, pstr); int nLen = strlen(szBuffer); szBuffer[nLen - 3] = 'l'; szBuffer[nLen - 2] = 'n'; szBuffer[nLen - 1] = 'k'; }//保存快捷方式到指定目录下 WCHAR wsz[MAX_PATH]; //定义Unicode字符串 MultiByteToWideChar(CP_ACP, 0, szBuffer, -1, wsz, MAX_PATH); hr = ppf->Save(wsz, TRUE); ppf->Release(); pLink->Release(); return SUCCEEDED(hr);
}
上述代码是从网上拷贝过来的,很多时候这些从网上拷贝的代码,可能缺少验证与测试,或多或少都有些问题,比如代码考虑的不够全面、某些细节处理有问题(比如资源没释放,某些处理细节的接口没调用等)! 所以,我们需要审视一下这些拷贝来的代码,看看代码中是否存在缺陷或者考虑不全的地方,再就是根据实际测试遇到的问题,对代码进行改进!
从上述代码看,创建快捷方式主要用到了IShellLink和IPersistFile两个COM接口,可以在API Monitor中选择监控这两个COM接口类的所有的成员函数。
API Monitor不仅仅可以监测系统API函数的调用,还可以监测COM组件接口的调用。
先启动QQ的安装包程序,如下所示:
先不要点立即安装,等API Monitor开启对该QQ安装包进程的监测后,再点立即安装。
因为QQ安装包程序肯定是以管理员权限运行的,所以启动API Monitor时也要以管理员权限启动,否则开启监控时会因为权限不对等开启监控失败。右键点击API Monitor exe程序,选择以管理员身份运行:
启动后在左边的API函数列表中勾选IShellLink和IPersistFile两个COM组件接口类的所有接口,如下所示:
然后在右下角的进程列表中找到QQ安装包进程,右键点击之,在弹出的右键菜单中点击“Start Monitoring”开启监控:
然后切换到QQ安装包界面,点击立即安装,进入安装过程:
API Monitor就在监测QQ安装包创建快捷方式的相关操作了(一般会在安装结尾时去创建快捷方式的)。
安装包安装完成后,切换到API Monitor窗口,监测到了IShellLink和IPersistFile两个COM组件类接口的调用,如下所示:
从上述记录还看到,还使用到了IShelllinkDatalist接口类,所以API Monitor还要监控IShelllinkDatalist接口类的接口,所以整个监测过程要重头来过。
5、同时勾选IShelllinkDatalist接口类的接口,重新开启监测
重新启动QQ安装包程序,重新以管理员权限运行API Monitor工具,在API Monitor左边的API函数列表中将IShelllinkDatalist接口类的接口都勾选上:
然后在左下角的进程列表中找到QQ安装包进程,开启监控。待QQ安装包执行完成后,API Monitor监测到了如下的结果:
上面截图中圈中的接口调用,我们代码中是没有的,于是参考这些接口调用以及对应的参数(API Monitor中不仅能看到函数名称,还能看到具体的参数类型以及参数值),把这几个接口加到代码中,然后上述问题就解决了。新增的代码如下:(代码中调用的COM接口的含义,见下面代码中的注释)
// 新增代码1、解决快捷图标不刷新、更改图标时找不到文件的问题
pLink->SetIconLocation( lpszTargetFilePath, 0 ); // 图标路径会被解析成环境变量(x64下更改图标时系统提示找不到文件)
IShellLinkDataList *pShellLinkDataList = NULL;
hr = pLink->QueryInterface( IID_IShellLinkDataList, (void**)&pShellLinkDataList );
if ( FAILED(hr) )
{ppf->Release();pLink->Release();return FALSE;
}// 新增代码2:避免图标路径被解析成环境变量
DWORD dwFlags = SLDF_DEFAULT;
pShellLinkDataList->GetFlags( &dwFlags );
if ( (dwFlags & SLDF_HAS_EXP_ICON_SZ) == SLDF_HAS_EXP_ICON_SZ )
{pShellLinkDataList->SetFlags( SLDF_DEFAULT );pShellLinkDataList->RemoveDataBlock( EXP_SZ_ICON_SIG );
}
pShellLinkDataList->Release();
修改后的创建快捷方式的完整代码如下:
// 1、得到公共用户桌面路径
BOOL GetPublicUserDesktopPath( TCHAR *pszDesktopPath )
{LPITEMIDLIST ppidl = NULL; if ( SHGetSpecialFolderLocation( NULL, CSIDL_COMMON_DESKTOPDIRECTORY, &ppidl ) == S_OK )//if ( SHGetFolderLocation(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, NULL, 0 , &ppidl ) == S_OK ){BOOL flag = SHGetPathFromIDList( ppidl, pszDesktopPath );CString strLog;strLog.Format( _T("[CProcessLogic::GetPublicUserDesktopPath]CSIDL_COMMON_DESKTOPDIRECTORY path: %s"), pszDesktopPath );uilog( strLog );CoTaskMemFree( ppidl );return flag;}return FALSE;
}// 2、创建桌面快捷方式
void CreateDesktopShortCut()
{::CoInitialize( NULL );TCHAR achDesktopDir[MAX_PATH] = { 0 }; BOOL bRet = GetPublicUserDesktopPath( achDesktopDir );if ( !bRet ){::CoUninitialize();return;}// 假设主程序的名称为 TestDlg.exe,桌面快捷方式的名称为TestDlg.lnk // m_strInstallPath为当前用户选择的安装路径CString strExePath;strExePath.Format( _T("%s\\TestDlg.exe"), m_strInstallPath);CString strLnkPath;strLnkPath.Format( _T("%s\\TestDlg.lnk"), achDesktopDir);CreateFileShortcut( strRunTrueLinkPath, strLnkPath, 0, NULL );::CoUninitialize();
}/*函数功能:创建快捷方式(封装的创建快捷方式的公用函数) 函数参数: lpszFileName 指定快捷方式指向的实际exe文件路径 lpszLnkPath 快捷方式完整路径 wHotkey 为0表示不设置快捷键 pszDescription 备注 iShowCmd 运行方式,默认为常规窗口
*/
BOOL CreateFileShortcut( LPCTSTR lpszTargetFilePath, LPCTSTR lpszLnkPath, WORD wHotkey, LPCTSTR lpszDescription, int iShowCmd )
{ if ( lpszTargetFilePath == NULL || lpszLnkPath == NULL ) {return FALSE;}HRESULT hr; IShellLink *pLink; // IShellLink对象指针 IPersistFile *ppf; // IPersisFil对象指针 //::CoInitialize( NULL );// 创建IShellLink对象 hr = CoCreateInstance( CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&pLink ); if ( FAILED( hr ) ) {//::CoUninitialize()return FALSE;}// 从IShellLink对象中获取IPersistFile接口 hr = pLink->QueryInterface( IID_IPersistFile, (void**)&ppf ); if ( FAILED( hr ) ) { pLink->Release(); //::CoUninitialize()return FALSE; } // 快捷方式指向的实际文件路径 if ( lpszTargetFilePath != NULL ){CString strWorkDirectry = lpszTargetFilePath;strWorkDirectry = strWorkDirectry.Left( strWorkDirectry.ReverseFind( _T('\\') ) );pLink->SetPath( lpszTargetFilePath );pLink->SetWorkingDirectory( strWorkDirectry );// 新增代码1、解决快捷图标不刷新、更改图标时找不到文件的问题pLink->SetIconLocation( lpszTargetFilePath, 0 ); // 图标路径会被解析成环境变量(x64下更改图标时系统提示找不到文件)IShellLinkDataList *pShellLinkDataList = NULL;hr = pLink->QueryInterface( IID_IShellLinkDataList, (void**)&pShellLinkDataList );if ( FAILED(hr) ){ppf->Release();pLink->Release();return FALSE;}// 新增代码2:避免图标路径被解析成环境变量DWORD dwFlags = SLDF_DEFAULT;pShellLinkDataList->GetFlags( &dwFlags );if ( (dwFlags & SLDF_HAS_EXP_ICON_SZ) == SLDF_HAS_EXP_ICON_SZ ){pShellLinkDataList->SetFlags( SLDF_DEFAULT );pShellLinkDataList->RemoveDataBlock( EXP_SZ_ICON_SIG );}pShellLinkDataList->Release();} // 快捷键 if ( wHotkey != 0 ){ pLink->SetHotkey( wHotkey );}// 备注 if ( lpszDescription != NULL ){pLink->SetDescription( lpszDescription );}// 显示方式 pLink->SetShowCmd( iShowCmd ); // 保存快捷方式到指定目录下 // 要将路径字符串转化成宽字节字符串,COM接口ppf->Save要传入宽字符BOOL bUnicode = FALSE;WCHAR* pwTemp = NULL;
#ifdef _UNICODEpwTemp = (LPTSTR)lpszLnkPath;bUnicode = TRUE;
#elseint nLen = MultiByteToWideChar( CP_ACP, 0, lpszLnkPath, -1, NULL, 0 );pwTemp = new WCHAR[nLen+1];memset( pwTemp, 0, (nLen+1)*sizeof(WCHAR) );MultiByteToWideChar( CP_ACP, 0, lpszLnkPath, -1, pwTemp, nLen+1, NULL, NULL );
#endifhr = ppf->Save( pwTemp, TRUE ); ppf->Release(); pLink->Release(); if ( !bUnicode && pwTemp != NULL ){delete []pwTemp;pwTemp = NULL;}//::CoUninitialize();return SUCCEEDED( hr );
}
6、最后
本文通过使用API Monitor监测目标程序来解决实际项目中的问题,详细讲解了API Monitor使用过程与相关细节,有一定的实战参考价值,希望能对大家有所帮助。