由于NSIS实现自定义界面,以及把安装包和升级包合并太过复杂,在各种尝试失败之后决定自己写安装包和卸载包。
原本项目中已经有一个让程序只运行一次的代码,使用QSystemSemaphore和QSharedMemory实现的。
QSystemSemaphore sema("Key",1,QSystemSemaphore::Open);
sema.acquire();//在临界区操作共享内存 SharedMemory
QSharedMemory mem("Object");//全局对象名
if (!mem.create(1))//如果全局对象以存在则退出
{
NDialog dialog(QApplication::focusWidget());
NMessageBox Msg(&dialog,NMessageBox::ErrMsg,QObject::tr("软件运行中,再次打开前请先关闭。"));//获取最后一条错误信息
dialog.setWidget(&Msg,QObject::tr("软件打开错误"));
dialog.exec();
sema.release();
return 0;
}
sema.release();
但后来发现,这段代码只支持同时运行两个程序,当第三个程序运行时,只会在后台出现,不会有任何界面提示,即使把前两个程序关闭也不行。直到打开任务管理器,把所有程序全部结束掉,再打开才能正常使用。
这种场景在日常使用并不常见,但是如果加入安装/卸载程序,三种程序都用这种方式,出现的概率就大很多,于是引入QtSingleApplication。
由于使用pri文件方式在修改代码时出现了问题(也可能是配置问题),实在懒得深究了,就直接把代码添加到目录里面了。
- 下载qtsingleapplication压缩包,并解压。
- 在项目文件夹中新建两个文件夹qtlockedfile和qtsingleapplication
- 将qtsinglecoreapplication.cpp/.h、qtsingleapplication.cpp/.h、qtlocalpeer.cpp/.h 共6个文件,放到qtsingleapplication文件夹
- 将qtlockedfile.cpp/.h、qtlockedfile_win.cpp、qtlockedfile_unix.cpp 共4个文件,放到qtlockedfile文件夹
- 将上面的qtsingleapplication文件夹中所有文件,以及qtlockedfile文件夹中的qtlockedfile.cpp/.h文件,共8个文件全部添加到项目中
- 在.pro文件中添加
win32 { SOURCES += qtlockedfile/qtlockedfile_win.cpp } unix { SOURCES +=qtlockedfile/qtlockedfile_unix.cpp }
- 在代码的main.cpp中,将原来的
#include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); NUninstallDialog w; w.show(); return a.exec(); }
改为
#include "qtsingleapplication/qtsingleapplication.h" int main(int argc, char *argv[]) { QtSingleApplication a(QLatin1String("UninstallId"),argc, argv); if(a.isRunning()) { NMessageDialog dialog(QApplication::focusWidget()); dialog.setText(false,QObject::tr("有相同Uninstall卸载程序正在运行,再次打开前请先关闭。")); dialog.exec(); return 1; } NUninstallDialog w; w.show(); return a.exec(); }
这样修改后,打开多少个程序,就会出现多少提示框,不会有程序在后台不出现的情况了。
- 不知道是不是我下载的版本问题,还是哪里配置不对,编译后会有几个问题。看了一下代码,一个是因为命名空间的关系,根据提示在qtlockedfile.cpp和qtlockedfile_win.cpp开头加入using namespace QtLP_Private;。另一个是因为分了两个文件夹后的路径问题,在qtlocalpeer.cpp中,将#include "qtlockedfile.cpp"和#include "qtlockedfile_win.cpp"改为#include "../qtlockedfile/qtlockedfile.h"和#include "../qtlockedfile/qtlockedfile_win.cpp"(有需要也可以将_unix的也改一下)。
如果仅仅是解决打开多个程序,以上代码就能满足要求。但由于项目是需要同时检测安装包、卸载包、执行文件的,除了它们自己本身需要只允许一次以外,还需要这三个程序只能运行其中一个。
由于QtSingleApplication是继承QApplication的,而QT程序只允许有一个QApplication类。因此需要在QtSingleApplication中添加一个独立查询AppId的函数。
- 在QtSingleApplication中添加公有成员函数
//QtSingleApplication.h bool isAppIdRuning(const QString &appId);
//QtSingleApplication.cpp bool QtSingleApplication::isAppIdRuning(const QString &appId) { QtLocalPeer lpeer(this, appId); return lpeer.isClient(); }
- 将main.cpp改为
int main(int argc, char *argv[]) { QtSingleApplication a(QLatin1String("UninstallId"),argc, argv); if(a.isRunning()) { NMessageDialog dialog(QApplication::focusWidget()); dialog.setText(false,QObject::tr("有相同Uninstall卸载程序正在运行,再次打开前请先关闭。")); dialog.exec(); return 1; } if(a.isAppIdRuning("SetupId")) { NMessageDialog dialog(QApplication::focusWidget()); dialog.setText(false,QObject::tr("Setup.exe安装程序正在运行,请关闭后再打开本卸载程序。")); dialog.exec(); return 1; } if(a.isAppIdRuning("xxxId")) { NMessageDialog dialog(QApplication::focusWidget()); dialog.setText(true,QObject::tr("xxx.exe运行中,是否强行关闭?")); if(dialog.exec() != QDialog::Accepted) { return 1; } } NUninstallDialog w; w.show(); return a.exec(); }
- 为了保证界面效果,语言翻译和皮肤的加载,要放到if(a.isRunning())前哦!
做安装包和卸载包其实还有几个问题没有攻克,只是这个刚好做起来有点麻烦,所以记录一下。