MFC user thread creation and exit

The company's production test tools have been used for many years. How many years are N years? I don’t know if some of the current students don’t know VC6.0 (I don’t know it may be wrong, just ridicule, no offense, it should be said that I have never used it), the production test tool was still developed with VC6.0 at that time, I feel This is the same as the "Invincible General" of the Qing Dynasty who is used to fight now. Therefore, I started with VC6.0, but the programmer who had become a monk halfway through, make up for what I left behind.

The UI and functions of the tool are actually very simple, but there are many places to optimize.

Today I will only talk about the UI thread, which is the part of the interface thread, because the real test tool has more functions. Here I only use demo to record some of the things I have made.

Code download link: https://download.csdn.net/download/yangkunhenry/14110419

Fundamental contents

First of all, the main interface is a dialog box. It is easy to create a dialog box-based program with MFC. Because it happens to be an after-sales tool these days, the name of the tool is called after sales tool. It sounds tacky, sorry.

The dialog box looks like this, rather simple, and the role of the edit box in the middle is not mentioned (It is RichEdit2, I made an error at the beginning, because AfxInitRichEdit2(); was not added to the InitInstance of the app, which caused an error when the program started running: "Warning: If you use MFC controls on the dialog, you cannot #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS."), not the point. Here is mainly to generate another Setting dialog box when the mouse clicks Setting, as shown below:

The setting dialog box here was originally made in the main process. After the setting exits, the main process exits, which is not good.

So here the ASL Setting interface is done in the way of user threads

Create interface thread

Interface thread and worker thread are the two basic types of threads. Recently, I have asked a lot of big cows on the CSDN forum. The interface thread is related to the interface, and nothing else.

As mentioned earlier, the interface thread is created when you press OK.

Interface thread creation function: 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。

There are two functions for creating threads in MSDN. When creating interface threads, we will use the latter one. The first parameter is this function of the CRuntimeClass pointer.

Obviously we have to prepare for this parameter, we have to provide such a type of parameter: CRuntimeClass 

pThreadClass is the RUNTIME_CLASS of an object derived from CWinThread

I created a CSetting class here, of course the base class is 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;
};

Method of creating thread class:

  1. Create a subclass of CWinThread, here CSetting;
  2. Add DECLARE_DYNCREATE(CSetting) in the class declaration.h. The function of DECLARE_DYNCREATE is actually very simple. It is to add a static member static const CRuntimeClass classSetting; the pointer to this member variable will be used later
  3. Add IMPLEMENT_DYNCREATE(CSetting, CWinThread) to the definition of the class. Of course, this must be the specific definition of the declaration part in the previous .h. What is it? This is about the specific process of how CRunTimeClass* comes from. You can open a separate blog to explain this. I will talk about this in detail when I have time. Mark here.
  4. Overload InitInstance and ExitInstance of CWinThrad. Of course, there are some questions about how to fill in them when overloading? Then I tell you to return TRUE in InitInstance, why? Look at the realization of CWinThread's InitInstance:
    /
    // CWinThread default implementation
    
    BOOL CWinThread::InitInstance()
    {
    	ASSERT_VALID(this);
    
    	return FALSE;   // by default don't enter run loop
    }

    Look at the note: by default don't enter run loop, which means that if it returns FALSE, it will not run the run loop of CWinThread. Therefore, when some students reload this function, instead of returning TRUE, they return CWinThread: :InitInstance, then you define the message response function, then there will be no message response when you call PostThreadMessage. You can check it in thrdcore.cpp (you can search it yourself or single-step tracking under the C drive) For the implementation of CWinThread function, you can see the code in _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();
    	}

    As you can see, if InitInstance returns FALSE, then the else below will definitely not get in.

  5. Add the pointer of the Dlg you want to create (how to create a dialog box class? Mark) m_psetting_dlg
  6. Because we need to send messages from the main process to the interface thread, we need to write the message response function afx_msg void OnThreadInfo(WPARAM wParam, LPARAM lParam);
  7. Of course, DECLARE_MESSAGE_MAP is also essential. Usually when we add messages to simple dialog boxes, we automatically add message response functions through the MFC class wizard. We found that there is such a code in the code, but we currently have many window types Class, MFC class wizard does not support adding non-window type message response functions, so we need to write it ourselves, this is the sentence that needs to be written in the class declaration, and the other is the following.
  8. Add in the class definition
    BEGIN_MESSAGE_MAP(CSetting, CWinThread)
    	ON_THREAD_MESSAGE(WM_THREADINFO, &CSetting::OnThreadInfo)
    END_MESSAGE_MAP()

     

  9. Of course we need to define the corresponding WM_THREADINFO in advance, what I defined here is #define WM_THREADINFO WM_USER+50
  10. After doing the above, we look at a thread class and create it, then we can use m_setting_ui_thread = AfxBeginThread(RUNTIME_CLASS(CSetting)); to create a thread, the type of m_setting_ui_thread is CWinThread* type. The implementation of AfxBeginThread is of course done in our process. We can send a message to notify the thread to create a dialog after creating the thread m_setting_ui_thread. Of course, we can also directly create the dialog dialog box in the inistance of the thread class CSetting. Send The way of the message
    m_setting_ui_thread->PostThreadMessage(WM_THREADINFO, 2, 0);

     

  11. After this post message, in the message response function of our thread class
    //显示消息处理函数
    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 a Dlg class, create window, update window, display window, so the interface window is created.

Thread exit:

Here we find that this Dlg is new in the message response function, so we also need to delete this object when the thread exits. The thread exit is the thread class exit, and the thread class exit is the thread destructor call. So we are here to take the delete operation in the destructor:

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

Of course, the premise is that our thread exits, and thread exit is automatically exiting thread run. Although we have not implemented thread run here, run in CWinThread is still doing a message loop for us. Once the run receives the WM_QUIT message, it exits Now, the run method of CWinThread:

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));
	}
}

See it:

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

The comment is very clear, pmup message, but quit on WM_QUIT, and we have also seen that if the run exits, the ExitInstance action will be executed

So to summarize, we need to send a WM_QUIT message to the thread at a certain place in the process where you need to let the thread exit.

We will let the thread exit when we click OK in the main process dialog box:

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();
}

After calling PostThreadMessage, we just send the WM_QUIT message to the message loop of the interface thread, but whether the run function goes to the exit point or not is another matter. In general, it can go almost quickly. But we still have to use WaitForSingleObject to wait for the thread to become signaled, so as to avoid that some operations of the thread have not been fully executed, we perform other actions, for example, if you specify m_bAutoDelete = FALSE when creating a thread class; then you You need to manually delete m_setting_ui_thread; if you have not waited until the thread run is finished, and you have not waited until ExitInistance is executed, then delete m_setting_ui_thread will cause an unknown error.

 

Another reminder, do not call functions such as Afxmessagebox in the interface thread. I encountered a program error caused by calling Afxmessagebox when debugging.

Guess you like

Origin blog.csdn.net/yangkunhenry/article/details/112433106