利用类的非static成员函数作窗口的处理函数

本文介绍了一种在Windows编程中使用静态函数调用非静态成员函数的方法。通过将类实例指针传递给窗口,并在静态处理函数中获取该指针来实现。这种方法常用于WinProc等窗口过程函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实现方法:   

        用类的一个static函数作真正的处理函数,在该函数中调用另一个非static的函数进行处理。

困难:

  static函数不能直接调用非static的函数。

原因:

       static 修饰的函数相当于全局函数,因此在函数运行中this指针无效,也就是说该函数的运行没有针对某个具体的类实例,所以也就不能用某个类中的非static 成员变量。

技巧:

  在创建好窗口后立即利用SetWindowLong函数将类实例的指针传给窗口。

  在static的处理函数中利用GetWindowLong函数取出该指针。

  然后就可以通过该指针调用非static的处理函数了。

例子:

     如果你是想用static修饰的函数(如WinProc)作为窗口的处理函数的话,有解决方法:

class SArea 
{
protect:
  HWND h_Wnd;
public:
  
virtual HWND CreateSArea(LPCTSTR wndName,DWORD dwStyle,int x,int y,int w,int h,HWND hParent);
  
static LRESULT CALLBACK SAreaProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
        
virtual void SProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
}
;


HWND SArea::CreateSArea(LPCTSTR wndName, DWORD dwStyle, 
int x, int y, int w,int h,HWND hParent)
{
  WNDCLASS wc;
    .....
//初始化wc并将SArea::SAreaProc函数设为处理函数
    ::RegisterClass(&wc);
  h_Wnd
=::CreateWindow(..........);
  
if(h_Wnd)
  ::SetWindowLong(h_Wnd,GWL_USERDATA,(LONG)
this);//将this指针传给窗口
  return h_Wnd;
}


LRESULT CALLBACK SArea::SAreaProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     SArea
* pArea=NULL;
     
if(message!=WM_NCCREATE && message !=WM_CREATE)qude//取得刚才传入的指针 
       pArea=(SArea*)::GetWindowLong(hWnd,GWL_USERDATA);
     
if(pArea)//利用该指针调用你想用的某类实例的成员
    pArea->SProc(hWnd,message,wParam,lParam); 
     
return ::DefWindowProc(hWnd,message,wParam,lParam);
}


void SArea::SProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    ......... 
}


 

<think>我们参考用户的问题:如何在Qt中通过工调用其他成员函数并实现异步执行以避免界面卡死。 核心思想:将耗时的操放在子线程中执行,通过信号槽机制与主线程通信。 步骤: 1. 创建一个工(Worker),它包含需要异步执行的函数。 2. 将工对象移动到子线程中(使用moveToThread)。 3. 通过信号触发工的槽函数(即需要异步执行的函数)。 4. 工完成后,工发送信号通知主线程(例如更新界面)。 注意:不要在工中直接操界面元素(如UI控件),因为Qt规定GUI操必须在主线程。更新界面应该通过信号发送数据给主线程,由主线程的槽函数执行。 下面是一个具体的示例: 假设我们有一个外部`ExternalClass`,它有一个成员函数`longRunningTask`,我们想在子线程中执行它。 步骤1:定义工(Worker) 工用是封装对`ExternalClass`成员函数的调用。我们可以将外部的对象为工的成员,或者通过参数传递。 步骤2:在工中定义一个槽函数,该槽函数内部调用外部成员函数。 步骤3:主线程创建子线程和工对象,并将工对象移动到子线程。 步骤4:通过信号触发工的槽函数。 步骤5:工执行完成后,发送信号通知主线程。 具体代码: 首先,定义工(Worker): ```cpp // worker.h #include <QObject> #include "externalclass.h" // 包含外部的头文件 class Worker : public QObject { Q_OBJECT public: explicit Worker(QObject *parent = nullptr) : QObject(parent) { // 可以在构造函数中初始化外部对象,或者通过set方法设置 m_external = new ExternalClass; } ~Worker() { delete m_external; } public slots: void doWork() { // 调用外部成员函数(耗时操) m_external->longRunningTask(); // 任务完成,发送信号 emit workFinished(); } signals: void workFinished(); private: ExternalClass *m_external; // 外部对象 }; ``` 然后,在需要调用异步操的地方(例如主窗口): ```cpp // mainwindow.h #include <QMainWindow> #include <QThread> #include "worker.h" class MainWindow : public QMainWindow { Q_OBJECT public: // ... 其他代码 private slots: void on_startButton_clicked(); // 假设有一个按钮点击触发 void handleWorkFinished(); // 处理工完成 private: QThread *workerThread; Worker *worker; }; // mainwindow.cpp #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // ... 界面初始化 // 创建线程和工对象 workerThread = new QThread(this); worker = new Worker(); worker->moveToThread(workerThread); // 关键:将worker移动到新线程 // 连接信号槽 connect(workerThread, &QThread::finished, worker, &QObject::deleteLater); // 线程结束时删除worker connect(this, &MainWindow::startWork, worker, &Worker::doWork); // 主线程通过信号触发工 connect(worker, &Worker::workFinished, this, &MainWindow::handleWorkFinished); workerThread->start(); // 启动线程(此时线程的事件循环开始运行) } void MainWindow::on_startButton_clicked() { // 发送信号,触发工线程执行 emit startWork(); // 注意:startWork是MainWindow的自定义信号,需要在mainwindow.h中声明 } void MainWindow::handleWorkFinished() { // 更新UI,提示任务完成 // 注意:这个函数是在主线程执行的,所以可以安全操UI statusBar()->showMessage("Task completed."); } ``` 注意: 1. 在`MainWindow`的析构函数中,需要正确停止线程: ```cpp MainWindow::~MainWindow() { workerThread->quit(); // 退出事件循环 workerThread->wait(); // 等待线程结束 } ``` 2. 外部`ExternalClass`的`longRunningTask`函数应该是一个耗时的操,但注意它不能操任何界面元素。 3. 如果外部对象需要从主线程传递数据,可以通过工的公共方法设置,或者通过信号传递参数。 4. 如果外部函数有参数,可以在`doWork`槽函数中传递,或者通过信号传递参数给`doWork`。 例如,修改`doWork`槽函数以接收参数: ```cpp // worker.h public slots: void doWork(int param1, const QString &param2) { m_external->longRunningTask(param1, param2); emit workFinished(); } ``` 然后,在连接信号时,使用带参数的信号: ```cpp // mainwindow.h signals: void startWork(int, const QString&); // 触发时 emit startWork(100, "example"); ``` 通过这种方式,我们实现了在工中调用其他成员函数,并在子线程中异步执行,从而避免界面卡死。 引用说明: - 使用`moveToThread`将工对象移动到子线程,这样工对象的槽函数将在子线程中执行[^1]。 - 通过信号槽机制(默认为Qt::AutoConnection)实现跨线程调用,当信号和槽在不同线程时,会自动使用队列连接(Qt::QueuedConnection)[^2]。 - 注意不要在子线程中直接操UI,应通过信号将结果发送到主线程,由主线程更新UI[^1]。 相关问题: 1. 如何在Qt中传递复杂数据型(如图像)给工线程处理? 2. 如何在工线程中处理多个不同的任务? 3. 如何实现工线程的进度更新并在主界面显示进度条? 4. 当工线程出现异常时,如何安全地终止线程并通知主线程? 5. 如何管理多个工线程以实现线程池的功能?</think>### 在Qt中通过工异步调用其他成员函数避免界面卡死 在Qt中实现异步执行的关键是**将耗时操移出主线程**,通过工调用其他成员函数时,需遵循以下步骤: #### ✅ 实现方案(信号槽+moveToThread) ```cpp // 1. 定义工(封装异步调用逻辑) class AsyncInvoker : public QObject { Q_OBJECT public: explicit AsyncInvoker(OtherClass* target) : m_target(target) {} public slots: void executeAsync() { // 此处调用其他成员函数 m_target->longRunningMethod(); // ⚠️ 耗时操 emit finished(); } signals: void finished(); private: OtherClass* m_target; // 指向目标的指针 }; // 2. 主线程中使用(UI实现) void MainWindow::startAsyncTask() { // 创建目标实例(需要调用的) OtherClass* targetObj = new OtherClass(); // 创建工+子线程 QThread* workerThread = new QThread(this); AsyncInvoker* invoker = new AsyncInvoker(targetObj); invoker->moveToThread(workerThread); // 关键步骤![^1] // 连接信号槽 connect(workerThread, &QThread::started, invoker, &AsyncInvoker::executeAsync); connect(invoker, &AsyncInvoker::finished, [=] { // 异步完成后的处理(主线程执行) workerThread->quit(); invoker->deleteLater(); targetObj->deleteLater(); }); connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater); workerThread->start(); // 启动线程 } ``` #### 🔧 关键技术解析 1. **`moveToThread`核心用** 将工对象移动到子线程,确保其槽函数在子线程执行: ```cpp invoker->moveToThread(workerThread); // 对象关联到线程[^1] ``` 2. **异步触发机制** 通过线程的`started`信号触发工函数: ```cpp connect(thread, SIGNAL(started()), worker, SLOT(doWork())); ``` 3. **跨线程函数调用(高级场景)** 使用`QMetaObject::invokeMethod`实现任意线程调用: ```cpp // 在工线程调用主线程函数 QMetaObject::invokeMethod(mainWindowObj, "updateUI", Qt::QueuedConnection, Q_ARG(QString, result)); // 在主线程调用工线程函数 QMetaObject::invokeMethod(workerObj, "processData", Qt::QueuedConnection, Q_ARG(DataPacket, data)); ``` 参数说明: - `Qt::QueuedConnection`:异步调用(默认跨线程) - `Qt::DirectConnection`:同步调用(同线程) - `Qt::BlockingQueuedConnection`:阻塞式跨线程调用[^2] 4. **内存安全管理** 使用父子关系+`deleteLater`自动释放资源: ```cpp connect(worker, &Worker::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); ``` #### ⚠️ 关键注意事项 1. **线程边界规则** - 所有GUI操必须在主线程 - 被调用的成员函数不能访问UI组件 - 共享数据需用`QMutex`保护 2. **错误调用示例(导致界面卡死)** ```cpp // 错误!直接调用会阻塞主线程 void MainWindow::onClick() { otherClass->longRunningMethod(); // 界面卡死 } ``` 3. **正确异步调用示例** ```cpp // 正确!通过信号触发异步执行 void MainWindow::onClick() { emit startAsyncCall(); // 信号连接到工对象 } ``` #### 💡 最佳实践建议 1. **封装可重用工** ```cpp class AsyncExecutor : public QObject { Q_OBJECT public: template<typename Func> static void runAsync(Func func) { QThread* thread = new QThread; QObject* worker = new AsyncWorker(func); worker->moveToThread(thread); connect(thread, &QThread::started, [=] { func(); thread->quit(); }); connect(thread, &QThread::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); } }; // 使用示例 AsyncExecutor::runAsync([=]{ otherObj->heavyCalculation(); // 在子线程执行 }); ``` 2. **使用QtConcurrent简化操** ```cpp #include <QtConcurrent> void MainWindow::startTask() { QFuture<void> future = QtConcurrent::run([=]{ otherClass->cpuIntensiveTask(); }); } ``` > **总结**:通过`moveToThread`+信号槽机制,工可安全调用其他成员函数而不阻塞主线程。关键点是: > 1. 工对象必须移动到子线程 > 2. 通过信号触发函数调用 > 3. 使用`QueuedConnection`进行跨线程通信 > 4. 避免在工线程操UI[^1][^2] --- ### 相关问题 1. 如何在异步操执行过程中实时更新进度条? 2. 多线程环境下如何安全传递`QImage`等大型对象? 3. `QtConcurrent`与`QThread`两种多线程方案有何性能差异? 4. 工线程抛出异常时如何安全通知主线程? 5. 如何实现带优先级的多线程任务队列?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值