Windows消息机制和多线程

来源:互联网 发布:男生发型 知乎 编辑:程序博客网 时间:2024/04/30 22:51

MFC篇
Windows消息处理
单位线程是如何处理消息的
Windows的消息处理机制是用如下代码进行消息处理的:
MSG message;
While(::GetMessage(&message,NULL,0,0)){
 ::TranslateMessage(&message);
 ::DispatchMessage(&message);
}
当消息到达时,由TranslateMessage进行必要的转换,例如:将WM_KEYDOWN消息转换为包含有ASCII字符的WM_CHAR消息,然后由DispatchMessage进行发送,当处理完成后,DispatchMessage返回.
放弃控制
如果在等待方式下,DispatchMessage必须等待处理完成后才能返回,在此之前将不能处理任何消息,而下面的代码可以做到即使没有消息到达程序的情况下也立即返回
MSG message;
While(::PeekMessage(&message,NULL,0,0,PM_REMOVE)){
 ::TranslateMessage(&message);
 ::DispatchMessage(&message);
}
计时器
计时器是不依赖CPU的时钟速度的. 注意的是因为Windows并不是实时的操作系统,所以,如果你指定的周期小于100毫秒的话,计时器事件之间的周期可能不精确.
有了计时器,有时可以替代多线程情况, 例如下面的代码就允许在循环内仍然接收处理消息. 这是一个进度条, 在OnTimer里面改动进度条的显示, 同时可以自定义CANCEL消息, 在OnCancel中将程序终止.
Void CDlg::OnStart()
{
 MSG message;
 SetTimer(0,100,NULL);
 GetDlgItem(IDC_START)->EnableWindow(FALSE); // 使按钮无效
 Volatile int nTemp; //使变更不保存在寄存器中, 因为变量如果保存在寄存器中, 在线程的切换过程中可能会出现值的错误.
 For (m_nCount=0;m_nCount
  For (nTemp=0;nTemp<10000;nTemp++){
   .........
  }
  if (::PeekMessage(&message,NULL,0,0,PM_REMOVE)){
   ::TranslateMessage(&message);
   ::DispatchMessage(&message);
  }
 }
 CDlg::OnOK(); // 线程结束后关闭对话框
}

多线程编程
进程是拥有自己的内存,文件句柄和其他系统资源的运行程序, 单个进程可以包含独立的执行,叫线程.
Windows提供了两种线程, 工作者(worker)线程和用户界面线程, 用户界面线程通常有窗口,且具有自己的消息循环.工作者线程没有窗口,因此它不需要处理消息.
编写工作者线程函数并启动线程
线程体一般是如何形式:
UINT ThreadProc(LPVOID pParam)
{
return 0;
}
启动线程使用:
CwinThread* pThread =
AfxBeginThread(ThreadProc,GetSafeHwnd(),THREAD_PRIORITY_NORMAL);
主线程如何与工作线程使用全局变量通讯
全局变量通讯是最简单而有效的办法.
例如下面的代码:
UINT ThreadProc(LPVOID pParam)
{
 g_nCount = 0;
while(g_nCount<100)
 ::InterlockedIncrement((long*)&g_nCount);
return 0;
}
InterLockIncrement函数在变量加1期间阻塞其他线程访问该变量. 如果不使用此函数而直接使用:g_nCount++的话, 可能会出现错误.
工作者线程与主线程通讯发送消息进行联络
 下面的代码: 当线程完成后发送给父进程消息
 UINT ThreadProc(LPVOID pParam)
{
 .........
 ::PostMessage((HWND)pParam,WM_THREADFINISHED,0,0);
 return 0;
}
使用事件进行线程同步
Cevent类是一个事件类,刚定义时处于"非信号"状态, 可以调用SetEvent()成员函数置它为"信号"状态.
下面的代码: 线程首先等待开始信号, 如果没有置开始信号会一直挂起等待, 同时在运行的过程中等待结束信号, 如果结束信号发生就终止线程.
Cevent g_eventStart,g_eventKill;
UINT ThreadProc(LPVOID pParam)
{
//INFINITE表示等待无限时间
 ::WaitForSingleObject(g_eventtart,INFINITE);
 for (.........) 
{
..........
If(::WaitForSingleObject(g_eventKill,0)==WAIT_OBJECT_0))
Break;
}
return 0;
}
当启动线程时: g_eventStart.SetEvent();
当终止线程时: g_eventKill.SetEvent();
但在终止线程时如果还没有启动线程,则应该先启动线程再终止它.
注意: 线程如果不正常终止会引起内存泄漏, 例如用关闭进程的方法来强制终止线程,或使用Win32 TerminateThread函数.
临界段
使用CcriticalSection类可以将临界段句柄包装起来, 例如:
 CcriticalSection g_cs;
 G_cs.Lock();
 G_nCount++;
 G_cs.Unlock();
用户接口线程
 一般使用用户接口线程是想要得到多个顶级窗口,例如你想允许用户运行程序的多个实例,但是你想让所有的实例都共享内存,IE就是这样的.你可以从CwinThread来派生一个类,使用AfxBeginThread的重载版本来启动该线程,派生的这个类具有它自己的InitInstance函数,并且具有自己的消息循环.

Win32 SDK篇
事件的使用方法
HANDLE g_hCloseEvent = NULL;
g_hCloseEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (g_hCloseEvent == NULL)
        return FALSE;
 设置信号: SetEvent(g_hCloseEvent);
线程的创建方法
 线程体的一般形式:
 DWORD WINAPI ThreadProc(LPVOID pParam)
 {
  return 0;
}
创建时:
HANDLE hReceiveThread = NULL;
UINT ThreadID;
hReceiveThread =
CreateThread(NULL,0,ThreadProc,hWnd,0, &ThreadID);
if ( hReceiveThread == NULL )
  return FALSE;
// 优先级为普通
SetThreadPriority(hReceiveThread,THREAD_PRIORITY_NORMAL);
临界区的使用方法
 CRITICAL_SECTION csRecvRead  = {0}; 
 InitializeCriticalSection(&csRecvRead); // 临界区初始化
 EnterCriticalSection(&csRecvRead); // 使用临界区变量
 pRightBuffer = pRightBuffer + len;
 LeaveCriticalSection(&csRecvRead);

-----------------------------------------------------------

VC++消息映射的思考

作者:郝庆欣

在学习VC++的时候,大家都不可避免的用到消息映射。我们都知道C++是一种面向对象的编程语言,VC++中为什么这样来实现消息映射呢?
  首先要明白一个包含了消息处理的Windows程序是如何工作的。
  一般来说一个包含了消息处理的Windows程序至少要包含两个函数
   第一个:
    int WINAPI WinMain(
      HINSTANCE hInstance,   // handle to current instance
      HINSTANCE hPrevInstance,  // handle to previous instance
      LPSTR lpCmdLine,     // command line
      int nCmdShow     // show state
    );
   第二个:
   long FAR PASCAL WndProc(HWND hWnd,WORD message,WORD wParam,LONG lParam);
   我们不必纠缠程序实现的细节,只要明白在第一个函数WinMain中要注册WndProc函数,通俗一些的理解就是WinMain告诉Windows系统,听着,我知道你要产生很多消息,我这里有一个WndProc函数负责处理你传递来的各种消息。当然消息的格式都是系统规定好的。

其次要明白C++中是如何实现多态性的。

我们知道多态性实现的关键是晚绑定(或者称为后期绑定),其实质就是编译器并没有在编译期间指定调用函数的绝对地址,而是指定了某个类内部该函数的偏移地址。

为了实现上面的功能,编译器为我们作了手脚

1、  在每个带有虚函数的类中,编译器秘密放置了一个指针,称为Vpointer

2、  当系统运行时,为每个类创建一个VTABLE,其中包含了可以调用虚函数地址。

3、  Vpointer出始化,指向VTABLE,通过在Vtable中偏移,来找到正确的需要调用的函数地址。

然后是MFC对Window API进行的封装

当我们利用MFC框架开发程序的时候,尤其是开发界面应用程序的时候,必定要用到CWnd或者派生于CWnd的类。根据面向对象的设计原则,对于CWnd的一些通用函数,例如窗口大学改变(OnSize),窗口移动(OnMove),最好是在CWnd中声明为虚函数,然后在继承的类里面重载他们。但是,这样以来,每个相关的派生类都要有一个Vpointer和一套记录Vtable,而CWnd中通用函数是如此至多,CWnd的派生类也很多,必然会导致系统在运行是占用过多的资源(内存),这样显然是不合适的。

 

那么MFC是如何实现的呢?

答案就是在CWnd基类中尽可能的少用虚函数,采用消息映射机制来代替。

大家可以看一下CWnd的类中的函数,就会发现这一点。

CWnd::OnMove 
afx_msg void OnMove( int x, int y );

上面这个函数就不是虚函数。

 最后的问题消息映射是如何实现的呢?

 用一句话说,就是利用宏定义来实现面向过程的消息处理。

例如在VC中有如下的消息映射宏。

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)  //{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE() 

//}}AFX_MSG_MAP

ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)

END_MESSAGE_MAP()

经过编译后,代码被替换为如下形式(这只是作讲解,实际情况比这复杂得多):

//BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) 

CMainFrame::newWndProc(...)

{

switch(...)

{

//{{AFX_MSG_MAP(CMainFrame)

// ON_WM_CREATE() 

case(WM_CREATE):

OnCreate(...);

break;

//}}AFX_MSG_MAP

// ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)

case(WM_COMMAND):

if(HIWORD(wP)==ID_FONT_DROPDOWN)

{

DoNothing(...);

}

break;

//END_MESSAGE_MAP()

}

}

这样,VC++就消除了对部分虚拟函数的需要,从而节省了内存空间。

 

参考资料:

  Thingking in C++,Bruce Eckel;