MFC笔记(四)

本文详细介绍了MFC中的线程管理和消息处理机制,包括SDK开发流程、MFC程序框架、消息映射、线程创建、线程同步与通信。重点讲解了用户界面线程与工作者线程的区别,以及如何使用`AfxBeginThread`创建线程。同时,文章还阐述了线程间通信的方法,如布尔变量、消息发送和同步对象(如CEvent、CMutex和CSemaphore)的使用。

Vs中c++应用程序开发:

1、SDK中的流程

开发一个带界面的SDK程序大致流程是这样的.首先自然是要有个main函数做入口点.然后按下面的步骤来(为了讨论方便,只说大概流程,代码也是不完整的)

int_tWinain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, intnCmdShow)

{

  MSG msg;

   InitApplicatio(HINSTANCE hInstance)  //1,注册窗体类,并在这里指定了窗体过程WndProc

   InitInstance(HINSTANCEhInstance, int nCmdShow)   //2,创建窗体

while(GetMessage(&msg, NULL, 0, 0))    //3步消息循环,分派消息

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return(int) msg.wParam;  //4,退出程序

}

BOOL InitApplicatio(HINSTANCEhInstance)

{

  returnRegisterClass(...);

}

BOOL InitInstance(HINSTANCEhInstance, int nCmdShow)

{

CreateWindow(...);//创建窗体

ShowWindow(...);//显示窗体

UpdateWindow(...);//送出WM_PAINT

returnTRUE;

}

LRESULTCALLBACK WndProc(...){} //窗口函数

 

2MFC开发

MFC中有main函数,但是由系统去调用.然后main函数里面执行的操作差不多,只不过它是通过CWinAppCWinThread的指针去调用一些相关的函数.

 

AfxWinInit函数:创建当前应用程序主线程

InitApplication函数:初始化CWINAPP类的一些内部东西,对编程人员来说无意义

InitInstance函数内部通过Create()函数来完成窗口的注册,创建更新和显示。

Run函数:内部就是真正的消息循环机制了(TraslateMessageDisPatchMessage)。

 

在一个windows程序中,winmain函数作为程序的入口函数,一个程序的是:设计窗口(给窗口设置相应的属性)——>注册窗口——>创建窗口——>显示并且更新窗口——>消息循环

但是当创建一个MFC程序时候,并没有看到winmain函数之类的东西,并且之前的程序框架与MFC也大有不同。现在,先来看看MFC中的程序框架以及程序运行机制吧。

由于MFC是对其SDK的封装,所以程序运行逻辑方式应该是一样的。

     MFC程序中,同样还是有winmain函数参与,但是这个WinMain函数是在程序编译连接时,由链接器将该函数链接到程序的。知道WinMain的存在后,那么WinMain又是怎么跟其他类组织在一起的呢?

1CxxxApp中,有一个theApp对象的全局对象,该对象就表示了应用程序本身。     对于全局对象theApp,它是在程序进入WinMain函数之前为其分配的空间,同时,定义其时候会调用它的构造函数。在程序中看到CxxxAppCWinApp的子类,当调用CxxxApp的构造函数之前,会调用其父类的构造函数。程序的初始化在CWinApp构造函数完成。

2AfxWinMain函数:WinMain函数实际上是通过调用AfxWinMain函数来完成其功能的。AfxWinMain源代码在MFC源代码的WINMAIN.CPP文件中。AfxWinMain首先通过调用AfxGetThread函数获得一个CWinThread类型的指针,接着调用AfxGetApp函数获得一个CWinApp类型的指针。由于CWinApp继承自CWinThreadAfxGetThread函数实际上返回的是AfxGetApp函数的结果,因此上面所说的AfxGetThread函数获得一个CWinThread类型的指针,接着调用AfxGetApp函数获得一个CWinApp类型的指针所得到的两个指针实际上是一致的。都指向theApp全局对象。

3InitInstance函数:接下来pThreadpApp调用了三个函数(在AfxWinMain所在的文件WINMAIN.CPP中可以看到)。分别是pApp->InitApplication();

pThread->InitInstance();    pThread->Run();三个函数。这三个函数完成了程序所需要的几个步骤:设计窗口类,注册窗口类,显示窗口类,消息循环,以及窗口过程函数。(通过进一步的跟进每个函数可以发现这三个函数最终需要调用跟win32中程序完成这些功能的函数的影子!)

设计和注册窗口:MFC已经为我们预定义了一些默认的标准窗口类,而注册窗口类是由AfxEndDeferRegisterClass函数完成。(AfxEndDeferRegisterClass函数首先获得窗口类的信息,如果该窗口类已经注册,直接返回一个真值,否则,调用RegisterClass函数注册该窗口类。其与win32SDK中所使用的函数是一样的啊!)

创建窗口:窗口的创建是由CWnd类中的CreateEx函数实现的,在MFC中,CFrameWnd类的Create函数内部调用了上述的CreateEx函数,而前者又是由CrameWnd中的LoadFrame函数调用!!!在CreateEx函数的实现过程中,调用了PreCreateWindow,而后者是一个虚函数,故此调用是调用子类的PreCreateWindow函数,之所以这样是在窗口产生之前让程序员能够有机会修改窗口的外观。参数类型是一个CREATESTRUCT结构体。

显示与更新窗口CxxxApp类中有一个m_pMainWnd的成员变量,是一个CWnd类型的指针,保存了应用程序窗口的指针。通过它调用与SDK中一样的ShowWindowUpdateWindow函数完成任务。

消息循环:前面已经提到过pThread->Run();这个函数就是实现了消息循环。在这个函数中的for循环中,执行了我们认识的TransLateMessageDisPatchMessage两个函数;

窗口过程:在AfxEndDeferRegisterClass函数的源程序中,其中有一行言谢眼熟的代码:wndcls.lpfnwndProc= DefWindowProc;当然MFC程序并不是把所有的消息都交给DefWindowProc窗口过程来处理。MFC中使用的是消息映射机制!

一个MFC消息响应函数在程序中有三处相关的信息:函数原型,函数实现,用来关联消息和消息响应函数的宏。(分别分布在头文件及源文件中)

头文件中在两个AFX_MSG 注释宏之间是消息响应函数的原型的声明。

       // Generated message map functions

       //{{AFX_MSG(CVideoNetDlg)

 

       //}}AFX_MSG

       DECLARE_MESSAGE_MAP()

源文件中有两处:(1AFX_MSG_MAP注释宏之间的消息映射宏,通过这个宏把消息与消息响应函数关联起来;

 

BEGIN_MESSAGE_MAP(CVideoNetDlg,CDialog)

       //{{AFX_MSG_MAP(CVideoNetDlg)

       ON_WM_SYSCOMMAND()

       ON_WM_PAINT()

       ON_WM_QUERYDRAGICON()

。。。。。。。。

       //}}AFX_MSG_MAP

END_MESSAGE_MAP()

2)源文件中消息响应函数的实现代码。

MFC中的消息映射机制的实现方法:在每个能接收和处理消息的类中,定义一个消息和消息的处理函数静态对照表,即消息映射表。在消息映射表中,消息与对应的消息处理函数指针成对出现。某个类能处理的所有的消息及其对应的消息处理函数的地址都列在这个类所对应的静态表中。当有消息需要处理时,程序只要搜索该消息静态表,查看表中是否函数该消息,就可以知道该内能够处理此消息。如果能,则依照静态表找到对应的消息处理函数。否则,将消息传给其父类。

 

 

MFC 消息

M F C应用程序中传输的消息有三种类型:窗口消息、命令消息和控件通知。

窗口消息(Window Message)一般与窗口的内部运作有关,如创建窗口、绘制窗口和销毁窗
口等。通常,消息是从系统发送到窗口,或从窗口发送到窗口。
若需要窗口消息的完全的列表,请参考M F C文档。


命令消息
命令消息一般与处理用户请求相关,当用户单击一个菜单项或工具栏时,命令消息产生,
并被发送到能处理该请求的类对象(如,装载文件、编辑文本和保存选项等)


控件通知
通常,控件通知在某些重要事件发生时,由控件窗口发送到父窗口,如打开一个组合框。

控件通知为父窗口进一步控制子窗口提供了机会。例如,打开一个组合框时,父窗口可以用
组合框初建时得不到的消息填充它。

ON_COMMAND ON_MESSAGE都是将消息处理函数加入消息路由表中,但是ON_COMMAND对应的消息ID一直都是WM_COMMAND,而ON_MESSAGE的消息IDON_COMMAND的第一个参数

 

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 UpdateData(TRUE)

  ——刷新控件的值到对应的变量。(外部输入值交给内部变量)

  即:控件的值—>变量。

  UpdateData(FALSE)

  —— 拷贝变量值到控件显示。(变量的最终运算结果值交给外部输出显示)

即:变量值—>控件显示。

 

 

模态对话框

CDialogproty dlg;

dlg.DoModal(); //此时调用onInitDialog函数

 

模态对话框,出现之后,必须得到响应,程序才能继续向下进行。直接生成一个对象,然后改对象.doModal();

非模态对话框,出现之后,不必得到响应,程序继续向下走。用new 新建

CTestDlg*dlg=new CTestDlg; //此时调用构造函数,并不会调用OnInitDialog()函数

 

dlg->Create(IDD_TEST_DLG,NULL);//调用OnInitDialog()函数,而不会调用构造函数。

 

dlg->ShowWindow(SW_SHOW); //显示,否则默认不显示

 

非模态对话框的销毁:

在对话框退出时销毁自己:在对话框中重载OnOK与OnCancel在函数中调用父类的同名函数,然后调用DestroyWindow()强制销毁窗口,在对话框中映射WM_DESTROY消息,在消息处理函数中调用delete this;强行删除自身对象。

 void CTestDlg1::OnOK()

{

CDialog::OnOK();或OnCancel()

DestroyWindow();

}

void CTestDlg1::OnDestroy()

{

CDialog::OnDestroy();

delete this;

}

也可以向父窗口发送消息,强行删除自己。

void CTestDlg2::OnDestroy()

{

CDialog::OnDestroy();

/*向其他窗口发送消息,将自身指针作为一个参数发送*/

m_pParent->PostMessage(WM_DELETE_DLG,(WPARAM)this);

}

在父窗口的文件中:

/*在头文件中添加函数定义*/

afx_msg LONGOnDelDlgMsg(WPARAM wP,LPARAM lP);

/*添加消息映射代码*/

ON_MESSAGE(WM_DELETE_DLG,OnDelDlgMsg)

END_MESSAGE_MAP()

/*实现消息处理函数*/

LONGCMy53_s1View::OnDelDlgMsg(WPARAM wP,LPARAM lP)

{

delete(CTestDlg2*)wP;

return 0;

}

 

 

使用DECLARE_DYNCREATE宏可以使每个CObject的派生类的对象具有运行时动态创建的能力。框架利用这种能力来动态创建对象

 

在类的.H模块中加入DECLARE_DYNCREATE宏,然后在每个需要访问这个类的对象的.CPP模块中包含这个模块。

如果在类声明中包含了DECLARE_DYNCREATE,那么必须在类的实现中包含IMPLEMENT_DYNCREATE宏。

 

 

 

Windows线程: CWinThread

Windows以线程封装消息循环,封装消息循环的线程叫做用户界面线程,即UI线程。该线程可以创建并撤销窗口。此外,还有一种线程叫做工作者线程,它是辅助UI线程工作的,它没有消息循环,不能处理系统事件和窗口消息,也不能关联主窗口。主线程和辅线程虽然享有共同的虚拟地址空间,但各自占用独立的CPU时间片,参与系统资源的竞争。所以,可以使用辅线程完成经常性的、耗费机时的数据处理工作(例如网络通信),减轻UI线程的负担,确保UI线程及时响应用户的窗口操作。根据需要,一个应用程序中也可以创建多个UI线程。

MFC应用程序通过调用AfxBeginThread函数并给定不同的参数来自动创建两种线程,而不需要程序自己创建。

 

1. MFC应用程序中的线程可由对象CWinThread表示,CWinThread类派生自CcmdTarget类;

2、CWinThread对象代表在一个应用程序内运行的线程

3、CWinThread对象允许一个应用程序拥有多个线程;

4、CWinThread对象支持两种线程类型:用户界面线程和工作者线程;

5、用户界面线程可以由CWinThread类派生,也可以是CWinApp类或其派生类。但为安全起见,应由CWinThread类派生。

6、任何使用MFC的线程必须由MFC创建,创建一个线程必须调用AfxBeginThread函数。

 

用户界面线程:

1) 用户界面线程拥有自己的消息循环来处理界面消息,具有收发消息的功能,处理从消息队列取得的消息;

 

2) 用户界面线程通常要与用户交互

 

3) 用户界面线程可由CWinApp类派生(注:CWinApp类由CWinThread类派生),也可以由CWinThread类直接派生。

 

4) 一个应用程序的主线程通常由CWinApp类派生,主线程应该是用户界面线程。

 

工作者线程

1) 工作者线程没有自己的消息循环,一般用来完成后台的工作,如后台计算、打印、与其它设备的串行数据通信等,这些工作的共同特点就是耗时。

2) 为了不影响主线程与用户的交互,通常耗时的工作交给工作者线程来完成;

3) 工作者线程可由CWinThread类直接派生。

 

MFC线程:

AfxBeginThread函数有两种调用格式,可以根据需要分别用来创建工作者线程和用户界面线程。

MFC创建工作者线程:

函数AfxBeginThread返回指向CWinThread类的指针。

CWinThread* AfxBeginThread(
      AFX_THREADPROC    pfnThreadProc,

      LPVOID      pParam, 

      int              nPriority =THREAD_PRIORITY_NORMAL,

      UINT       nStackSize = 0,

      DWORD dwCreateFlags = 0,

     LPSECURITY_ATTRIBUTES   lpSecurityAttrs = NULL
);

 

①   pfnThreadProc:线程函数的地址,该参数不能设置为NULL,线程函数必须定义成全局函数或者类的静态成员函数。

②   pParam:要传递给线程函数的参数;

3、nPriority:要启动的线程的优先级,默认优先级为THREAD_PRIORITY_NORMAL(普通优先级),

④   nStackSize:新线程的堆栈大小,如果设置为0,则使用默认大小,在应用程序中一般情况下线程的默认堆栈大小为1M;

⑤   dwCreateFlags:线程创建标志,该参数指定线程的初始状态,它可以被指定为下列标志:    0:线程在创建后立即执行

                          CREATE_SUSPENDED:线程在创建后立即挂起

⑥   lpSecurityAttrs :指向SECURITY_ATTRIBUTES结构的指针,结构中指定了线程的安全属性。如果为NULL,则与 创建它的线程的安全属性相同。

⑦   

例如:CWinThread*  pWinThread;

pWinThread=AfxBeginThread(参数

要注意:

pWinThread->m_bAutoDelete=false;

……

……

……

delete   pWinThread;

 

即要将CWinThread类的数据成员m_bAutoDelete设为false,并且在退出进程前,将指向线程类CWinThread的指针pWinThread删除。

 

 

MFC创建用户界面线程:

CWinThread* AfxBeginThread(

  CRuntimeClass*   pThreadClass,

   int              nPriority =THREAD_PRIORITY_NORMAL,

  UINT         nStackSize = 0,

  DWORD   dwCreateFlags = 0,

  LPSECURITY_ATTRIBUTES  lpSecurityAttrs = NULL
);

创建用户界面线程的步骤:

 

1) 从CWinThread类派生一个新类,派生类必须用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏来声明和实现;

2) 重载派生类的InitInstance、ExitInstance等函数,在InitInstance函数中添加代码。

 

 

 

线程启动时的初始状态可以通过AfxBeginThread( )函数的dwCreateFlags参数指定。如下:

0:线程在创建后立即执行;

CREATE_SUSPENDED:线程在创建后立即挂起;

线程优先级:

 

 

 

 

 

 

线程间通信:

一、用简单的布尔型变量实现线程间的通信

二、通过消息的发送和处理来实现线程和主程序之间的通信

1.调用::PostMessage( ),其原型为:

BOOL PostMessage(      

 HWND     hWnd,     //要发送到的窗口的句柄 

 UINT     Msg,     //消息的ID值

 WPARAM   wParam, //消息的第一个参数    

   LPARAM  lParam  //消息的第二个参数

 );

如果执行成功返回非零值,不成功返回0

 

或者:

PostThreadMessage

函数功能:该函数将一个消息放入(寄送)到指定线程消息队列里,不等待线程处理消息就返回。

函数原型:BOOLPostThreadMessage(DWORD idThread,UINTMsg,WPARAM wParam,LPARAM IParam);

在调用::PostThreadMessage(ptest->m_nThreadID,WM_QUIT,0,0); 之前先建立线程ptest的消息循环,使用PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE);建立线程消息循环

idThread:其消息将被寄送的线程线程标识符。如果线程没有消息队列,此函数将失败。线程第一次调用一个Win 32 USER或GDI函数时,系统创建线程的消息队列Msg:指定将被寄送的消息的类型。

wParam:指定附加的消息特定信息。

IParam:指定附加的消息特定信息。

返回值:如果函数调用成功,返回非零值。如果函数调用失败,返回值是零。若想获得更多的错误信息,请调用GetLastError函数如果idThread不是一个有效的线程标识符或由idThread确定的线程没有消息队列,GetLastError返回ERROR_INVALID_THREAD_ID。

 

三、使用同步类来实现线程之间的通信和控制

在VC++环境中使用同步对象:

    CEvent类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。事件告诉线程何时去执行某一给定的任务,从而使多个线程流平滑。

每一个CEvent对象可以有两种状态:有信号状态(signaled)和无信号状态(nonsignaled)。线程监视位于其中的CEvent类对象的状态,并在相应的时候采取相应的操作。

•    bInitiallyOwn:

   若bInitiallyOwn为TRUE,则使CMultilock类对象和CSingleLock类对象的线程可用;否则,要访问资源的线程必须等待。该参数的默认值为FALSE。

•    bManualReset:

   指定要创建的CEvent对象是属于手工事件对象还是自动事件对象。若为TRUE,则为手工事件对象,否则为自动事件对象。该参数默认值为FALSE。

•    lpszName:

   指定要创建的事件对象的名字,如果该事件对象将跨进程使用,则此参数不能为NULL。如果该参数和一个已经存在的CEvent对象相同,则该构造函数返回一个对这个已存在对象的引用;如果参数和一个已存在的非CEvent类的同步对象(如CMutex)相同,则对象创建失败;

•    lpsaAttribute:

   指向SECURITY_ATTRIBUTES结构的指针,该参数决定要创建的事件对象的安全属性,一般置为NULL。

 

BOOL SetEvent ( ):

   将CEvent类对象的状态设置为有信号状态,并且释放所有等待的线程;如果该事件是人工事件,则CEvent类对象保持为有信号状态,直到调用成员函数ResetEvent()将其重新设为无信号状态时为止,这样该事件就可以释放多个线程;如果CEvent类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent类对象由系统自动重置为无信号状态,除非一个线程被释放。

   如果该函数执行成功,则返回非零值,否则返回零。

3. BOOL  ResetEvent( )

   该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。

   如果该函数执行成功,返回非零值,否则返回非零。

4. BOOL PulseEvent( )

   发送一个事件脉冲,该函数完成一系列操作后才返回。对于自动事件,PulseEvent()将事件设置为有信号状态,等待一个线程被释放,将事件重置为无信号状态,然后PulseEvent()返回;对于人工事件,则将等待该事件的所有线程被释放,事件被自动重置为无信号状态,然后PulseEvent()返回。

   一个CEvent对象在线程中被创建后,自动处于无信号状态,但在另一个线程中可以调用Win32 APIWaitForSingleObject()函数来监视其状态。

5. BOOL Unlock( )

        该函数用来释放事件对象,即把Lock锁定的线程解锁。

        如果线程拥有事件对象,并且事件对象是一个自动事件对象,则返回非零值,否则返回0。

当在线程的执行函数中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。如果经过dwMilliseconds毫秒后,hHandle指向的对象变为有信号状态,则WaitForSingleObject()返回,线程被释放,且返回值为WAIT_TIMEOUT;如果在挂起的dwMilliseconds毫秒内,线程所等待的对象在某一时刻变为有信号,则该函数立即返回,返回值为WAIT_OBJECT_0。

 

CWinThread的几个函数:

1、 虚函数InitInstance用户界面线程经常重载该函数,工作者线程一般不使用

“初始化实例”的意思,可见,它是在实例创建时首先被调用的。应用程序总要重载这个虚函数,进行系统设置,创建运行环境。例如,主窗口一定要在InitInstance()中创建,因为该函数退出后就进入该线程的消息循环

2、虚函数Run

该函数提供UI线程的消息循环,即反复地提取消息,分发消息,直到收到WM_QUIT退出循环,线程随即结束。在循环中,如果当前没有收到消息,则调用空闲消息处理程序OnIdle() 。

 

3、虚函数ExitInstance用户界面线程经常重载该函数,工作者线程一般不使用

 

与InitInstance()相反,该函数是在退出消息循环时执行,一般被框架调用,做最后的清理工作。但如果调用InitInstance()失败,ExitInstance()也会被调用可以重载ExitInstance(),为线程做相关的清理工作。不要在除重载的Run()函数外的地方调用它。如果将CWinThread成员变量m_bAutoDelete设为TRUE,CWinThread::ExitInstance()会删除当前的CWinThread对象。所以,如果在堆栈中构造了UI线程对象,可以利用默认的ExitInstance()自动将它删除。

 

文档视图结构

文档为应用程序数据基本元素的集合,构成应用程序所使用的数据单元,提供管理和维护数据的手段。文档对象负责数据的读写操作,数据通常保存在文档对象的成员变量中,文档类通过序列化 Serialize对象将成员变量的数据保存到文件中。

视图为数据的用户窗口,为用户提供了文档的可视的数据显示,将文档中的部分或全部内容在窗口中显示出来。视图负责显示和编辑文档数据,但不负责存储。

       文档视图结构的组成:

1、 应用程序类  CWinApp

派生自CWinApp类,负责唯一的全局应用程序对象的创建、初始化、运行和退出清理过程。

2、 CMainFrame

CMainFrame定义了程序的主框架窗口,由框架窗口类CFrameWnd派生而来。对单文档应用,主框架窗口定义了全部的应用界面的特性,是程序和用户之间交换信息的主要界面。

应用程序初始化主框架窗口时,向主框架窗口发送一个WM_CREATE 消息,主框架窗口调用OnCreate函数处理消息,对主框架窗口进行初始化,完成创建工具栏、状态栏等。之后,系统调用ShowWindow函数,将主框架窗口显示在屏幕上。

3、 文档模版类

文档模版用于指定程序的实例、框架窗口、文档和视图之间的联系。通常在InitInstance 中被创建,然后由他来创建文档对象和主框架窗口。MFC提供文档模板类CDocTemplate来支持文档模版(基类),派生了两个类CSingleDocTemplate 用于单文档,CMultipleDocTemplate用于多文档,由CSingleDocTemplate和CMultipleDocTemplate来定义对象。

4、 文档类

 

文档类的所有数据成员都为公有的,这样视图类可以方便的访问。

MFC中由文档类CDocument来实现文档的操作。派生文档类一般用于完成对文档对象数据的读写操作。一般类的数据成员的初始化是在构造函数中完成的,构造函数调用结束时对象才真正存在。文档类的数据成员的初始化是在OnNewDocument成员函数中完成,此时文档对象已经存在。单文档界面的程序中,应用程序启动时,文档对象就已经创建,直到主框架窗口被关闭时才销毁。

File-New操作时,程序重新初始化问的文档对象的数据成员,初始化操作由应用程序对象的OnFileNew消息处理成员函数通过调用OnNewDocument成员函数来完成。

在关闭应用程序并删除文档对象,或用File-Open命令打开一个文档,需要清理文档中的数据。文档数据的清理也不是在析构函数中完成的,而是在文档类的DeleteContents成员函数中完成。文档类的析构只用于清除那些在对象生存期都将存在的数据项。

5、 视图类

视图在MFC的CView类中被实例化。该类通常表示一个依附于框架窗口并占据它的用户区的子窗口,没有边框。

 

 

MFC线程操作

 

1、 MFC线程分为用户界面线程和工作者线程

工作者线程没有界面,没有消息处理循环。

一般只有一个线程执行体(处理函数),线程启动的时候就进入线程执行体进行执行,当执行完成之后自动退出线程。一般将耗时的操作设为工作者线程。

        工作者线程一般不用ClassWizard,创建比较方便。一般通过AfxBeginThread(

AFX_THREADPROC pfnThreadProc, //线程执行体函数指针

 LPVOID pParam,                             //向线程传递参数

Int nPriority = THREAD_PRIORITY_NORMAL,//线程的优先级

UINT nStackSize  = 0,                     //线程所占的堆栈尺寸

DWORD dwCreateFlags = 0, //线程创建标志

LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //线程的安全结构

)

常用的为CWinThread* thread =AfxBeginThread((AFX_THREADPROC)ThreadWork, NULL) 实现。

 

        用户界面线程一般使用ClassWizard进行创建,需要实现InitInstance() 和ExitInstance(),具有消息循环。可以接受消息并进行处理。

通过PostThreadMessage给线程发送消息; 消息的处理映射通过BEGIN_MESSAGE_MAP和END_MESSAGE_MAP中的ON_THREAD_MESSAGE实现。

可以通过CreateThread()进行创建或者

AfxBeginThread(

CRuntimeClass* pThreadClass  //用户界面线程

Int nPriority = THREAD_PRIORITY_NORMAL, //优先级

UINT nStackSzie = 0,  //线程创建标志

LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL//线程的安全结构

)进行创建。

用户界面线程 h文件中 DECLARE_DYNCREATE 和 cpp文件中 IMPLEMENT_DYNCREATE宏相匹配实现类的反射,即可以通过类名得到该类的对象。

CRuntimeClass *pClass =RUNTIME_CLASS(CUIThread);

CUIThread* thread = pClass ->CreateObject();

 

 

用户界面线程和工作者线程可以通过 SuspendThread() ,ResumeThread(), ExitThread()来进行线程的控制。

线程的终止可以通过 线程自身使用ExitThread或线程使用 PostQuitMessage实现

或者通过在其他线程调用TerminateThread实现。

 

 

线程的同步和互斥

同一个进程的各个线程的互斥可以通过临界区类 CCriticalSection实现。

ClassCCriticalSection: public CSyncObject

{

Public:

 CCriticalSection();

Virtual BOOL Unlock();

BOOL Lock();

BOOL Lock(DWORD dwTimeOut);

}

在同一个进程的各个线程中使用同一个CCriticalSection进行

criticalSection.Lock();

//互斥操作

criticalSection.UnLock();

 

在不同的进程的线程间或者不同进程间实现临界资源的互斥访问可以通过CMutex 互斥锁。

速度较CCriticalSection慢

Class CMutex: public CSyncObject

{

Public:

CMutex(BOOL bInitiallyOwn = FALSE, LPCSTRlpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL);

}

bInitiallyOwn:说明初始状态下,该类的对象处于锁定状态

lpszName : 该系统对象的名称。如果指定了该对象的名称,就可以实现进程之间的共享。

lpsaAttribute: 该对象安全描述符的属性,一般设为NULL

 

该对象可以通过调用父类的Lock(), UnLock()进行加锁或解锁,有了lpszName之后,该对象就可以跨进程操作。

在同一个进程的各个线程之间实现互斥:

CMutex mutex;

各个线程内部通过:

Mutex.Lock();

//进行互斥操作

Mutex.UnLock();

在不同进程或不同进程的线程之间进行互斥;

进程A中创建共享资源[进程之间进行共享资源的互斥]

HANDLE hMappingSend;

//创建内存映射文件

hMappingSend =CreateFileMapping((HANDLE)0XFFFFFFFF, NULL, PAGE_READWRITE, 0, 4, “SENDDATA”);

//把数据映射为整数

Int *a = (int*)MapViewOfFile(hMappingSend, FILE_MAP_ALL_ACCESS, 0, 0, 0);

CMutex mutex(FALSE, “SendMutex”);//生成名称为”SendMutex”的互斥量

While(1){

mutex.Lock();

*a = *a + 1;

mutex.UnLock();

}

另一个进程中:

HANDLE hMappingSend;

//创建内存映射文件

hMappingSend =CreateFileMapping((HANDLE)0XFFFFFFFF, NULL, PAGE_READWRITE, 0, 4, “SENDDATA”);

//把数据映射为整数

Int *a = (int*)MapViewOfFile(hMappingSend, FILE_MAP_ALL_ACCESS, 0, 0, 0);

CMutex mutex(FALSE, “SendMutex”);//生成名称为”SendMutex”的互斥量

While(1)

{

mutex.Lock();

*a = *a + 2;

mutex.UnLock();

}

 

信号量对象实现线程同步

CSemaphore: public CSynObject

{

CSemaphore(LONG lInitialCount = 1, LONGlMaxCount = 1, LPCSTR pstrName = NULL,

LPSECURITY_ATTRIBUTES lpsaAttributes =NULL);

}

lInitialCount :信号量的初始值。默认为1,相当于互斥量CMutex

lMaxCount 是信号量的最大值, 为资源的最大数目

pstrName 为信号量的名称

lpsaAttributes 信号量的安全属性,一般为NULL

 

 

使用事件对象实现线程的同步

类CEvent对象代表一个事件,它允许一个线程通知另一个线程某个事件的发生。

class CEvent

{

Public:

CEvent(BOOL bInitiallyOwn = FALSE, BOOLbManualReset = FALSE,

LPCTSTR lpszName = NULL,  LPSECURITY_ATTRIBUTES lpsaAttribute = NULL);

BOOL SetEvent();

BOOL PulseEvent();

BOOL ResetEvent();

Virtual BOOL Unlock();

}

bInitiallyOwn 表示初始化的状态,默认为非激发状态。bManualReset 说明时间是否手动

lpszName不是空时,该事件对象可以跨进程使用

DWORD WaitForSingleObject

{

HANDLE hHandle; //等待事件句柄

DWORD dwMillseconds;   //等待的最长时间

}

hHandle 事件句柄代表事件、互斥量、信号量、线程和进程。

事件对象含有 手动类型和自动类型。

手动类型:手动调用SetEvent()和ResetEvent()函数对事件设置或释放状态

自动类型:由事件自动由信号状态回到非信号状态

PulseEvent() 选择一个等待自动类型事件的线程或进程运行,并使事件自动由信号态返回非信号态。

 

使用消息实现线程之间的通信

发送消息函数

LRESULT SendMessage

(

HWND hWnd; //目标窗口句柄

UINT Msg; //消息

WPARAM wParam; //第一个消息参数

LPARAM lParam //第二个消息参数

)

SendMessage函数将消息直接发送给线程的消息队列,并等待消息处理函数的处理,当消息处理函数完成处理后,调用返回。 阻塞调用

BOOL PostMessage

( HWND hWnd ,//目标窗口句柄

UINT Msg, //消息

WPARAM wParam,//第一个消息参数

LPARAM lParam//第二个消息参数

)

PostMessage函数把消息发送到消息队列,并不等待消息处理函数处理就直接返回。非阻塞调用

BOOL PostThreadMessage

( DWORD idThread, // 线程号

UINT Msg, // 消息

WPARAM wParam, //第一个消息参数

LPARAM lParam //第二个消息参数

) PostThreadMessage函数和PostMessage类似。

从消息队列中取出一条消息:

GetMessage //阻塞调用,如果消息队列有消息时立即返回,如果没有消息处于阻塞状态

PeekMessage //不论消息队列有没有消息,都会立即返回,有消息时返回消息,没消息返回FALSE。

 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值