项目需要将一个外部软件嵌入自己编写的qt界面,类似于将外部程序当作自己软件的一个插件,以起到集成的目的。自己刚开始用电脑自带的计算器做例子,但一直没成功,换了其他软件就能成功嵌入。
传统做法:
1)首先打开需要的外部程序;
2)通过spy+获取的外部程序类名;
3)将类名当作函数FindWindow的第一个参数(字符串);
代码:
HWND m_pwHwnd = NULL;
QWidget* m_widget = nullptr;
while (!m_pwHwnd)
{
m_pwHwnd = FindWindow(L"Chrome_WidgetWin_1", NULL);//第一个参数为类名,第二个为标题
}
m_window = QWindow::fromWinId((WId)m_pwHwnd);
m_widget = QWidget::createWindowContainer(m_window, this);
ui->horizontalLayout_5->addWidget(m_widget);
上面代码中的函数FindWindow第一个参数为外部程序类名,需要借助vs菜单栏 工具——Spy++(+)获取得到。
最终效果:
上面的传统做法是利用vs自带工具spy++得到已打开程序的类名或窗口名,进而通过FindWindow函数得到句柄。然而,每次打开的外部程序类名或窗口名都会改变,另外也不可能要求用户通过第三方工具得到这两个值中的一个,所以该方法不大可行。
改进做法:
1)根据exe路径启动程序,并得到进程id号;
2)根据进程id号得到主窗口句柄:通常情况下一个进程内有多个窗口句柄,还需要从得到的N个句柄中找到主窗口句柄(此处可以参考这);
3)将HWND转为WId,进而将外部程序嵌入QWindow、widget:
代码:
//启动外部程序
bool _launchExternalSoftware(QString exePath)
{
/*
*启动外部程序
*/
//QString cmd = "D:/Program Files/InnovMetric/PolyWorks MS 2021/bin/polyworks.exe";
//QString cmd = "D:/Program Files (x86)/Adobe/Reader 11.0/Reader/AcroRd32.exe";
//QString cmd = "D:/Program Files/MVS/MVS/Applications/Win64/MVS.exe";
m_process = new QProcess(this);
connect(m_process, &QProcess::started, this, &MainWindow::on_processStarted);
m_process->setParent(this);
QStringList arg;
arg << "";
m_process->start(exePath, arg);//外部程序启动后,将随主程序的退出而退出
//m_process->start(exePath);//外部程序启动后,将随主程序的退出而退出
return true;
}
//嵌入外部程序
void on_processStarted()
{
qint64 id = m_process->processId();//如果程序没有运行,将会返回0
if (id == 0)
{
QMessageBox::information(NULL, "提示", "程序没有启动");
return;
}
Sleep(2500);
qDebug() << "Status: " << m_process->state();
HWND mainwindowHwnd = FindMainWindow(id);
qDebug() << "mainwindowHwnd: " << mainwindowHwnd;
if (!mainwindowHwnd)
return;
wid = (WId)mainwindowHwnd;
m_window = QWindow::fromWinId(wid);
m_widget = QWidget::createWindowContainer(m_window); //第二个参数是作为window的父类,也可以用layout,此处就不解
ui->scrollArea->setWidget(m_widget);
}
其中,获取主窗口句柄的函数 FindMainWindow 代码如下:
函数声明:
//自定义结构体
struct handle_data {
unsigned long process_id;
HWND best_handle;
};
BOOL IsMainWindow(HWND handle);
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam);
HWND FindMainWindow(unsigned long process_id);//通过进程id号获取主窗口句柄
定义:
BOOL IsMainWindow(HWND handle)
{
return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle);
}
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam)
{
handle_data& data = *(handle_data*)lParam;
unsigned long process_id = 0;
GetWindowThreadProcessId(handle, &process_id);
if (data.process_id != process_id || !IsMainWindow(handle)) {
return TRUE;
}
data.best_handle = handle;
return FALSE;
}
//通过进程id号获取主窗口句柄
HWND FindMainWindow(unsigned long process_id)
{
handle_data data;
data.process_id = process_id;
data.best_handle = 0;
EnumWindows(EnumWindowsCallback, (LPARAM)&data);
return data.best_handle;
}
补充:
嵌入后可能还想让被嵌入的窗口脱离(独立)出去,或实现自己的软件退出,但被嵌入的软件不退出的功能。既然嵌入是给窗口一个父窗口,那么,让父窗口为NULL理论上就可以让被嵌入的窗口独立出去。思路有了,这里就不放具体代码了。
总而言之,只要获得进程,嵌入窗口就没有障碍,这里主要有两个思路获取进程:1)启动一个exe并获取对应进程;2)遍历电脑所有进程,找到想要的进程。