MFC用户线程的创建以及退出

公司的生产测试工具用了N多年了。N多年的是多少年?不知道是不是现在的一些小同学还不知道VC6.0(还不知道可能不妥,仅仅调侃,无意冒犯,应该说是没用过),生产测试工具当时还是用VC6.0开发的,感觉这和现在去打仗用的还是清朝的“神威无敌大将军”一样。所以,我这个起步VC6.0,但是又半路出家的程序猿再恶补一下当年丢下的东西。

工具的UI和功能其实都很简单,但是要优化的地方也是很多。

今天只说一下涉及到UI线程也就是界面线程部分,因为真正的测试工具的功能比多,这里仅仅用demo的方式记录一下自己恶补的一些东西。

代码下载链接:https://download.csdn.net/download/yangkunhenry/14110419

基本内容

首先,主界面是个对话框,利用MFC很容易创建一个基于对话框的程序,因为这几天刚好要做一个售后工具,所以工具的名字就叫做after sales tool了,听起来比较俗气,见谅。

对话框大概是这个样子,比较简陋,中间的编辑框作用就不说了(是RichEdit2,刚开始犯了错误,因为在app的InitInstance中没有加入AfxInitRichEdit2();,导致程序刚开始运行就报错:“警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。”),不是重点。这里主要是 在鼠标点下Setting的时候产生另外一个Setting 对话框,如下面:

这里的setting对话框原来是在主进程中做的,设置退出之后,主进程就退出了,这样不好。

所以这里将ASL Setting界面用用户线程的方式去做

创建界面线程

界面线程和工作线程是 线程的两个基本类型,最近在CSDN论坛上也问了不少大牛,界面线程就是处理界面相关的,其他啥也不要干。

前面说是在按下确定的时候去创建界面线程。

界面线程的创建函数:AfxBeginThread

MSDN:

CWinThread* AfxBeginThread(
   AFX_THREADPROC pfnThreadProc,
   LPVOID pParam,
   int nPriority = THREAD_PRIORITY_NORMAL,
   UINT nStackSize = 0,
   DWORD dwCreateFlags = 0,
   LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL 
);
CWinThread* AfxBeginThread(
   CRuntimeClass* pThreadClass,
   int nPriority = THREAD_PRIORITY_NORMAL,
   UINT nStackSize = 0,
   DWORD dwCreateFlags = 0,
   LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL 
);

pThreadClass 
从 CWinThread 派生的对象的 RUNTIME_CLASS。

MSDN中两个创建线程的函数,创建界面线程的话我们就用后面这个,第一个参数是CRuntimeClass指针的这个函数。

显然我们要为这个参数做下准备,我们要提供这样一个类型的参数:CRuntimeClass 

pThreadClass是 从CWinThread派生的对象的RUNTIME_CLASS

我这里建立了一个CSetting的类,当然基类就是CWinThread了

#pragma once
#include <afxwin.h>
#include "stdafx.h"
#include "CSettingDlg.h"
class CSetting :
	public CWinThread
{
	DECLARE_DYNCREATE(CSetting)
protected:
	CSetting();
	~CSetting();
public:
	BOOL InitInstance();
	virtual int ExitInstance();
	CSettingDlg *m_psetting_dlg;
	afx_msg void OnThreadInfo(WPARAM wParam, LPARAM lParam);
protected:
	DECLARE_MESSAGE_MAP()	/* 本身宏中已经有protected:但是为了防止直接在后面追加成员变量,所以这里还是再把protected写上*/
public:
	BOOL m_b_dev_id;
};

创建线程类的方法:

  1. 创建CWinThread的子类,这里的CSetting;
  2. 在类声明.h中添加DECLARE_DYNCREATE(CSetting) ,DECLARE_DYNCREATE作用其实很简单,就是填加了一个静态成员static const CRuntimeClass classSetting;后面会用到这个成员变量的指针
  3. 在类的定义中添加IMPLEMENT_DYNCREATE(CSetting, CWinThread),当然这肯定是前面.h中声明部分的具体定义了,这里面具体是什么呢?这里面是关于CRunTimeClass* 是怎么来的的具体过程。这个可以单独开一篇博客去讲解了,有时间我再具体说一下这个,这里打mark.
  4. 重载CWinThrad的InitInstance和ExitInstance,当然,有些疑问的是,在重载的时候里面怎么去填?那我告诉你InitInstance中要返回TRUE ,为什么呢?看下CWinThread的InitInstance的实现:
    /
    // CWinThread default implementation
    
    BOOL CWinThread::InitInstance()
    {
    	ASSERT_VALID(this);
    
    	return FALSE;   // by default don't enter run loop
    }

    看注释:by default don't enter run loop ,意思是说如果是返回FALSE,则不会跑CWinThread的run 循环,所以,有些同学在重载这个函数的时候,不是返回TRUE,而是返回CWinThread::InitInstance,那么这样你定义了消息响应函数,那么你去调用PostThreadMessage的时候也不会消息响应,在thrdcore.cpp(在C盘下你可以自己搜下或者单步跟踪下)中可以查下有CWinThread的函数实现,可以看下_AfxThreadEntry这个函数中有这样的代码:

    	// else -- check for thread with message loop
    	else if (!pThread->InitInstance())
    	{
    		ASSERT_VALID(pThread);
    		nResult = pThread->ExitInstance();
    	}
    	else
    	{
    		// will stop after PostQuitMessage called
    		ASSERT_VALID(pThread);
    		nResult = pThread->Run();
    	}

    看到了吧,如果InitInstance返回的是FALSE,那么下面的else肯定跑不进去。

  5. 添加你要创建的Dlg的指针(如何创建对话框类?打mark)m_psetting_dlg
  6. 因为我们需要从主进程中发送消息给界面线程,所以需要写消息响应函数afx_msg void OnThreadInfo(WPARAM wParam, LPARAM lParam);
  7. 当然DECLARE_MESSAGE_MAP也必不可少,平时我们在为简单的对话框添加消息的时候,通过MFC类向导自动添加了消息响应函数之后,我们发现代码中就有这样一句代码,但是我们目前不少窗口类型的类,MFC类向导不支持添加非窗口类型的消息响应函数,所以我们需要自己写,这是类声明中需要写这一句话,另外就是下面要说的。
  8. 在类定义的时候添加
    BEGIN_MESSAGE_MAP(CSetting, CWinThread)
    	ON_THREAD_MESSAGE(WM_THREADINFO, &CSetting::OnThreadInfo)
    END_MESSAGE_MAP()
  9. 当然我们需要提前定义对应的WM_THREADINFO,我这里定义的是#define   WM_THREADINFO WM_USER+50
  10. 以上做了之后呢,我们看一个线程类就创建了,那我们就可以利用 m_setting_ui_thread = AfxBeginThread(RUNTIME_CLASS(CSetting)); 去创建一个线程了,m_setting_ui_thread的类型是CWinThread*类型。执行AfxBeginThread这个当然是在我们进程去做的,我们可以在创建线程m_setting_ui_thread之后用发送消息的方式去通知线程创建一个dialog,当然也可以在线程类CSetting的inistance中直接创建dialog对话框,这里采用发送消息的的方式用
    m_setting_ui_thread->PostThreadMessage(WM_THREADINFO, 2, 0);
  11. 这个post message之后,在我们线程类的消息响应函数中
    //显示消息处理函数
    void CSetting::OnThreadInfo(WPARAM wParam, LPARAM lParam)
    {
    	if (wParam == 2)
    	{//启动
    		m_psetting_dlg = new CSettingDlg;
    		m_psetting_dlg->Create(IDD_DLG_SETTING);
    		m_psetting_dlg->UpdateData(FALSE);
    		m_psetting_dlg->ShowWindow(TRUE);
    	}
    	//return 0;
    }

    new一个Dlg类,创建窗口,更新窗口,显示窗口,这样界面窗口就创建完了。

线程退出:

这里我们发现这个Dlg是我们在消息响应函数中new出来的,所以我们也需要在线程退出的时候delete掉这个对象,线程退出也就是线程类退出,线程类退出也就是线程的析构函数调用,所以我们这里是放在析构函数中取做delete操作:

CSetting::~CSetting()
{
	if (m_psetting_dlg != NULL) {
		delete m_psetting_dlg;
		m_psetting_dlg = NULL;
	}
}

当然前提是我们线程要退出,线程退出也就是自动退出线程的run,这里虽然我们没有实现线程的run,但是CWinThread中run还是在为我们不断的去做消息循环,一旦run收到了WM_QUIT消息就退出了,CWinThread的run方法:

int CWinThread::Run()
{
	ASSERT_VALID(this);
	_AFX_THREAD_STATE* pState = AfxGetThreadState();

	// for tracking the idle time state
	BOOL bIdle = TRUE;
	LONG lIdleCount = 0;

	// acquire and dispatch messages until a WM_QUIT message is received.
	for (;;)
	{
		// phase1: check to see if we can do idle work
		while (bIdle &&
			!::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))
		{
			// call OnIdle while in bIdle state
			if (!OnIdle(lIdleCount++))
				bIdle = FALSE; // assume "no idle" state
		}

		// phase2: pump messages while available
		do
		{
			// pump message, but quit on WM_QUIT
			if (!PumpMessage())
				return ExitInstance();

			// reset "no idle" state after pumping "normal" message
			//if (IsIdleMessage(&m_msgCur))
			if (IsIdleMessage(&(pState->m_msgCur)))
			{
				bIdle = TRUE;
				lIdleCount = 0;
			}

		} while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));
	}
}

看到了吧:

// pump message, but quit on WM_QUIT
			if (!PumpMessage())
				return ExitInstance();

注释写的很清楚,pmup message,but quit on WM_QUIT, 同时我们也看到了,如果run退出了,那么会执行ExitInstance动作

因此总结一下,我们要在进程中某一个地方,你需要让线程退出的地方,去发送给线程一个WM_QUIT的消息,

我们就在主进程的对话框中的确定点击的时候去让线程退出:

void CdwaftersalestoolDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	/*
		发送WM_QUIT线程给UI线程
	*/
	if (m_setting_ui_thread) {
		m_setting_ui_thread->PostThreadMessage(WM_QUIT, 0, 0);
		if (WAIT_OBJECT_0 == WaitForSingleObject(m_setting_ui_thread->m_hThread, INFINITE)) {
			CDialogEx::OnOK();
		}
	}else
		CDialogEx::OnOK();
}

在调用了PostThreadMessage的之后,我们只是把WM_QUIT消息发给界面线程的消息循环,但是run函数有没有跑到退出的地方,还是另外一回事,总的来说它几乎很快就可以跑到,但是我们还是要用WaitForSingleObject去等待线程变为有信号的,这样才能避免线程的一些操作还没有完全执行,我们就执行其他动作了,比如如果你在创建线程类的时候指定m_bAutoDelete =FALSE;那么你需要手动去delete m_setting_ui_thread;如果还没有等到线程run跑完,没有等到ExitInistance执行完就去delete m_setting_ui_thread了那么就会造成未知的错误。

另外提醒一点,不要再界面线程中调用Afxmessagebox这样的函数,我在调试的时候遇到因为调用了Afxmessagebox导致的程序错误。

猜你喜欢

转载自blog.csdn.net/yangkunhenry/article/details/112433106