文章目录
- 项目场景:
- 问题描述
- 原因分析:
- 解决方案:
- 解决方案一:
- 解决方案二:
- 总结
项目场景:
工业上位机通常需要对测试的各种数据做一个报表导出功能,报表导出一般是基于一个模板导出。也就是先打开一个报表模板,在这个模板上进行数据的导入,最后再保存。
问题描述
在导出报表功能开发中,由于软件突发的崩溃导致报表导出中断,此时报表模板处于打开状态,重开软件就会出现异常。
原因分析:
由于每次打开软件都会清空报表模板缓存目录的所有文件,同时关闭的软件时候也会清空对应的目录文件。这里有报表目录的缓存目录这样做是为了确保报表模板的一致性,不会出现重复模板。由于软件在导出报表的时候出现崩溃,所以报表模板并没有在软件关闭的时候被删除,这就导致文档的进程始终运行。
解决方案:
解决方案一:
直接关闭整个wps或者word即可
。
这个方案属于一刀切,如果用户打开了其他的Word文档呢?那就影响了客户的使用体验。所以不合适。
方案的思路是,首先通过Windows的API获取到WPS的进程,将他关闭。
解决方案二:
关闭指定的文档,同时保留窗口状态(最大化最小化等)
这个方案是最优解,但是实现起来存在一些问题。
有两种方式去处理,第一种是通过Windows的API去找对应的PID,接着找对应文档名称,再发送信息让对应的进程关闭这个文档。
第二种是使用微软的COM接口,使用
Q
A
x
O
b
j
e
c
t
QAxObject
QAxObject来处理。
第一方式,我查了很多资料,翻遍了AI,都不能很好关闭的指定文档名称对应的进程,主要是找到这个指定文档进程,找不到。找到软件的PID可以。或许这里是我没找到,方法是有的,毕竟我对WIndowsAPI确实不是很熟悉。第一种方法,遂罢。
第二种方式,通过QAxObject来做。问题在于,怎么拿到已经存在的进程的COM接口?
网上关于QAxObject操作Word以及自己写相关接口,基本都是类似下面的写法
QAxObject* m_word=new QAxObject();
m_word->setControl("kwps.Application");
我们可以看到这其实是新建了一个对象,相当于新开了一个进程,并不是我们已经打开的文档对应的进程。
其实更好的情况应该是类似于拷贝构造函数,如下的写法
IDispatch* existPoint;//这里表示我们要的已经打开的WPS进程对应的COM指针
QAxObject* m_word=new QAxObject(existPoint);
所以现在的问题就成了如何找到这个进程对应的COM指针。
这里踩了不少坑,这里就不做阐述了,直接放一下代码,再配一下说明。
主要的流程就是获取打开指定名称的进程的唯一ID,即windows中的_GUID,接着转换一下格式,来获取当前已经激活进程的对应ID,再获取这个进程的COM接口,在使用QAxObject操作word那一套进行处理,关闭之类。这个VBA接口要参考一下微软的文档。
微软Word接口参考
void CloseWPSDocument(const std::string& documentpath)
{
IDispatch* pDisp=nullptr;
CLSID clsid;//进程的唯一ID: _GUID
std::string strProcessID="kwps.Application";//由于数字的进程号直接转wchar_t类型有很多问题,所以这里直接用进程名称了
//下面就是各种转格式,满足GetActiveObject函数的参数类型要求
wchar_t wp[1024]{};
int wide_len=MultiByteToWideChar(GetACP(),0,strProcessID.c_str(),-1,nullptr,0);
MultiByteToWideChar(GetACP(),0,strProcessID.c_str(),-1,wp,wide_len);
HRESULT hr=CLSIDFromProgID(wp,&clsid);
//这个函数就把获取到的进程的COM接口返回给这个pDisp指针
hr=GetActiveObject(clsid,nullptr,(IUnknown **)&pDisp);
//IDispatch是COM中的一个接口,用于提供对象的属性和方法的动态访问。任何实现了IDispatch接口的对象都可以通过QAxObject进行操作
QAxObject* m_word=new QAxObjcet(pDisp);
//记录窗口状态
int windowstate=m_word->property("WindowState").toInt();
if(!m_word->isNull())
{
QAxObject* m_documents=m_word->querySubObject("Documents");
QAxObject* m_document=m_documents->querySubObject("Open(QString&"),QString::fromStdString(documentPath));
if(m_document)
m_document->dynamicCall("Close(bool)",false);
}
//关闭文件后窗口会保持打开所以这里需要恢复初始状态
m_word->setProperty("WindowState",windowsate);
delete m_word;
}
总结
这篇博客的意义是记录一下删除文件出现异常的处理,由于网上没看到合适的解决方案,所以这里做一个记录。