Visual C++ Windows Programming

Slides:



Advertisements
Similar presentations
第 3 章操作系统基础 3.1 操作系统概述 3.2 操作系统的功能模块 3.3 典型操作系统概述.
Advertisements

软件开发技术基础 第 3 章 操作系统及程序设计 讲授教师:卫颜俊. 主 要 内 容 主 要 内 容 操作系统及其功能 进程管理应用程序设计 内存管理应用程序设计 设备与文件管理应用程序设计 人机接口管理应用程序设计.
人 工 智 慧 報 告 五子棋AI設計 報告者 : 潘輝銘.
第九章 2D遊戲演算法 課前指引 本章中將介紹在遊戲開發過程中,常會運用到一些貼圖技巧,來展現遊戲畫面及動態效果。例如基本貼圖、動畫貼圖、橫向捲軸移動、前景背景移動等,來提高單純2D圖片的變化性。
Memory Pool ACM Yanqing Peng.
動畫與遊戲設計 2D Game 程于芳 老師
第一章 C语言概述 计算机公共教学部.
操作系统结构.
程設一.
類別與物件 Class & Object.
Tree(樹) 什麼是「樹」? 「樹」的範例 「樹」的定義 「樹」的表示法.
Operating System Concepts 作業系統原理 Chapter 3 行程觀念 (Process Concept)
多线程编程基本概念 2008.
第二十九章 DLL / LIB函式庫開發 當我們開發程式到一個階段之後,我們一定會希望各個Component的程式碼可以分開的越清楚越好。而這一章最主要就是要告訴各位讀者,我們常在Windows系統中看到的dll或是lib的檔案該怎麼實作?做出這樣的library我們又該如何運用?為什麼使用dll或是lib有利於我們開發程式?以上這些疑問都將會在這一章中得到解答。
Visual C++ Windows Programming
多核结构与程序设计 杨全胜 东南大学成贤学院计算机系.
Applied Operating System Concepts
Chap 18 類別與物件 夫有土者,有大物也。有大物者,不可以物。 物而不物,故能物物。 明乎物物者之非物也,豈獨治天下百姓而已哉!
第十一章 面向对象设计 第十二章 面向对象实现
Visual C++ Windows Programming
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
第4讲 Windows编程基础 此为封面页,需列出课程编码、课程名称和课程开发室名称。
本單元介紹何謂變數,及說明變數的宣告方式。
Chapter 3 行程觀念 (Process Concept)
第六章 继承性和派生类 胡昊 南京大学计算机系软件所.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
程序设计期末复习 黎金宁
第三章 C++中的C 面向对象程序设计(C++).
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
第5章 文本与字体 2018/12/3 面向对象与可视化 程序设计 --Visual C++ 编程 主讲教师: 唐 龙教授 (计算机科学与技术系) 黄维通博士 (计算机与信息管理中心) 清 华 大 学 2001年2月 2018/12/3 Huang Weitong.
简要回顾 了解课程体系、课程目的和主要内容 掌握Windows编程涉及的一些主要概念 DOS程序和Windows程序的主要区别 窗口和程序
第1章 Windows应用程序框架的 创建与消息处理
第三章 进程互斥与同步 进程通信 Communication.
第3章 Windows 应用程序基础 2018/12/6 第2讲 Windows 应用程序基础 VC++面向对象与可视化程序设计.
第十一讲 MFC常用控件的使用(3) 严宣辉 数学与计算机科学学院
MFC WinSock类的编程 为简化套接字网络编程,更方便地利用Windows的消息驱动机制,微软的基础类库(Microsoft Foundation Class Libary,简称MFC),提供了两个套接字类,在不同的层次上对Windows Socket API函数进行了封装,为编写Windows.
网络游戏开发语言基础 ——Windows程序设计
进程操作.
作業系統 (Operating System)
第4章 MFC编程 4.1 MFC概述 4.2 MFC和Win CObject类 4.4 消息映射的实现
第三章 用户接口与作业管理 用户与操作系统的接口 批处理操作系统的作业管理 作业的基本概念:作业、作业步、作业流 交互式系统作业管理
Visual C++ Windows Programming
第3章 認識處理元.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
第2章 进程和线程 内容提要: 2.1 进 程 概 念 2.2 进程的状态和组成 2.3 进 程 管 理 2.4 线 程.
第7章 繼承/多型/介面 注意: 本投影片僅供本書上課教師使用,非經同意請勿上網轉載或供拷貝.
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
第十五讲 MFC与消息处理 MFC简介 Windows编程机制 MFC应用程序框架原理 创建应用程序框架 消息及其分类 消息映射机制
版权所有 复制必究 第 6 章 MFC原理与方法.
Operation System(OS).
C#程序设计基础 $3 成员、变量和常量.
第十二讲 菜单、工具栏和状态栏 严宣辉 数学与计算机科学学院
中国科学技术大学计算机系 陈香兰 Fall 2013 第三讲 线程 中国科学技术大学计算机系 陈香兰 Fall 2013.
《面向对象程序设计与Visual C++6.0教程》
Visual C++ Windows Programming
Windows 程式設計 (使用 C++ / C#)
_05MessageMap的原理 本节课讲师——void* 视频提供:昆山爱达人信息技术有限公司 官网地址:
MFC及其应用.
第二章 Java基本语法 讲师:复凡.
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
第二章 Java语法基础.
第10章媒体控制接口 10.1 MCI设备类型 10.2 MCI编程步骤 10.3使用MCIWnd窗口类.
辅导课程十一.
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
第9章 多 线 程.
Go 语言编程 —— 平台研发部 吴植民.
第2章 Java语言基础.
手工编写第一个 MFC程序 本节内容 视频提供:昆山爱达人信息技术有限公司 视频录制:yang 官网地址:
Presentation transcript:

Visual C++ Windows Programming 第十一章 訊息處理與多執行緒 台大電機系 李奇錚

大綱 Windows 訊息處理 多執行緒程式設計

記憶體 可定址的記憶體 虛擬記憶體 利用虛擬記憶體的概念來解決記憶體不足的問題 每一個程式會透過OS來對記憶體進行存取 兩個不同程式會使用單獨的記憶體空間,並且雙方不能互相存取對方的記憶體空間 程式在使用記憶體前,要先向OS做申請

記憶體 每個process可以看成是有一個專屬的OS在為其服務,但事實上,這些OS是OS的本身間分身 Process可以自由使用的記憶體空間 Process可以自由使用的記憶體空間 系統DLL記憶體映射檔 OS kernel 2GB 3GB 4GB WinNT的process使用記憶體情況

虛擬MS-DOS Win16 Global Heap 記憶體 虛擬MS-DOS Win16 Global Heap Process可以自由使用的記憶體空間 系統DLL記憶體映射檔 Win 16 Global Heap OS kernel 4MB 2GB 3GB 4GB

記憶體 C, 利用malloc() , free()的方式來向OS要記憶體 C++ 利用new , delete的方式來要記憶體 系統扣除掉所需要的空間後,剩下的空間稱”free memory” 或heap , 而malloc就是要求OS自heap中割出一塊來給使用者使用 OS收到malloc後,就由heap中割出一塊空間給使用者,然後將這塊記憶體的指標(或是說位址)回傳

記憶體 Win32的記憶體配置 HGLOBAL GlobalAlloc(UNIT fuAlloc , DWORD cbAlloc) 要求OS配置一塊記憶體 fuAlloc : 記憶體的屬性 GHND , GMEM_DISCARDABLE , GEME_FIXED , GMED_MOVEABLE , GMEM_NOCOMPACT, GMEM_DDESHARE, GMEM_SHARE… cbAlloc : 配置記憶體的大小,單位是byte 記憶體的屬性 : Discardable memory : 可丟棄記憶體 , 即當主記憶體用盡時,會把那些很久沒有動過的記憶區塊丟到磁碟上,等到要的時候在把他們讀進主記憶體中,而設定成Discardable 就表示這種記憶體可以丟到磁碟上去(也就是較不重要的) Fixed memory : 固定記憶體 , 當記憶體沒有被鎖定時,就可以被OS自由的搬動,以減低碎裂的情況發生, 但fixed這種記憶體是一種例外,也就是這種記憶體,即使記憶體沒有被鎖定,也不能任意搬動 Moveable memory : 可移動記憶體 , 和固定式的相反 , 即只要記憶體沒被鎖定,就可以自由的搬動

記憶體 Win32的記憶體配置 GlobalLock( HGLOBAL hglb) GlobalUnlock( HGLOBAL hglb) 鎖定記憶體區塊 GlobalUnlock( HGLOBAL hglb) 解除鎖定記憶體區塊 GlobalFree(HGLOBAL hglb) 釋放記憶體區塊 GlobalFlags(HGLOBAL hglb) 傳回與指定記憶區塊有關的資訊,以及被鎖定的次數 回傳值的低位元組是被鎖定的次數,也可以利用AND的方式來檢查是否包含下面的旗號 GMEM_DISCARDABLE :可以被丟棄 , GMEM_DISCARED : 已經被丟棄 GlobalSize(HGLOBAL hglb) 指定記憶體區塊的大小 回傳值是0 : handle給的不對,或是已經被丟到磁碟上了 GlobalReAlloc(HGLOBAL hglb , DWORD cbNewSize , UNIT fuAlloc) 更改記憶體區塊的大小

記憶體 Win32的記憶體配置 void FAR* lpBuffer hglb = GlobalAlloc( GPTR, 1024); lpBuffer = Globallock(hglb) …. GlobalUnlock(hglb); GlobalFree(hglb);

Windows 訊息處理 Processes vs. Threads 當我們將一個應用程式載入到記憶體中執行時,該應用程式在記憶體中執行的狀態,一般而言我們統稱之為 process 。更精確的說法是,當我們將應用由硬碟載入記憶體時,系統分配給該應用程式的記憶體空間,程式碼,以及其他系統資源,這些資源的所有權我們將之視為一個 process 。當系統執行了不只一個 process 時,電腦將以 process 做為電腦分配資源的單位。 Process 是系統分配資源的單位,但是 CPU 時間的分配並非依照 process 來進行。真正分配 CPU 時間的基本單位是 thread 。 在每一個 process 裡,一開始只有一個 thread ,此 thread 稱為 primary thread 。而每一個 thread 還可以產生其他的 thread ,前者為 parent thread ,其他則為 child thread 。

Windows 訊息處理 (續) Processes vs. Threads (續) Windows 所號稱的多工環境,基本上並不是真正的多工,因為系統實際上只有一個或者固定數目的 CPU 。在單 CPU 的系統中,所謂的多工是靠一個 CPU 搭配一個分配 CPU 時間的計時器,將 CPU 的計算時間分配給系統中執行的 thread ,由於在 thread 之間切換執行,分配 CPU 時間的速度很快,所以讓人感覺好像每個 thread 同時被執行,以達到多工的「效果」。 當 process 由一個以上的 threads 所組成時,我們稱這個 process 為多執行緒 (multi-threading) 程式。

Processes 產生Child Process CreateProcess( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) lpApplicationName : 可執行檔的完整路徑名稱,包函結尾的延伸檔名( ex .exe) lpCommandLine : 命令列參數,若將lpApplicationName 設成null的話,就要在此加入執行檔的檔名( 就如在command line打入的指定一樣) lpProcessAttributes : 一些和系統安全相關的資訊 lpThreadAttributes : 和lpProcessAttributes 差不多,不過這個是針對Thread的 bInheritHandles : 決定childe process是否繼承parent process的資源 dwCreationFlags : 設定一些特性的選項,以及child process的優先執行權 lpEnvironment : 環境變數字串 lpCurrentDirectory : 工作目錄 lpStartupInfo : 啟動資訊(存放在STARUPINFO中) lpProcessInformation : CreateProcess會把與child process有關的資訊填在PROCESS_INFOMATION中,並回傳此值

dwCreationFlags dwCreationFlags HIGH_PRIORITY_CLASS 最優先執行權 IDLE_PRIORITY_CLASS 系統空閒時再執行 REALTIME_PRIORITY_CLASS 即時執行 CREATE_NEW_CONSOLE 另外開啟一個console來執行child process CREATE_SUSPENDED 產生child process後,不立刻執行,等到呼叫ResumeThread()才開始執行 DEBUG_PROCESS 可以對child process執行除錯 …..

STARUPINFO struct _STARTUPINFO { DWORD cb; LPTSTR lpReserved; LPTSTR lpDesktop; LPTSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO;

PROCESS_INFORMATION typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;

Processes PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.lpReserved = NULL ; siStartInfo.lpReserved2 = NULL ; siStartInfo.cbReserved2 = 0; siStartInfo.lpDesktop = NULL ; siStartInfo.dwFlags = 0; BOOL ret = CreateProcess(“notepad.exe” , “c:\\config.sys” , NULL , NULL , TRUE , 0 , NULL , NULL , &siStartInfo , &piProcInfo);

Processes 和process有關的資訊 終結process GetCurrentProcess(VOID) 回傳process的handle GetCurrentProcessID(VOID) 回傳process的ID ID是一組數字,Win32保證不會有第二個執行的process擁有這個相同的ID GetPriorityClass( HANDLE hProcess) 取得process的priority SetPriorityClass(HANDLE hProcess , DWORD dwPriorityClass) 設定process的priority 終結process ExitProcess() or exit() ExitProcess( UNIT uExitCode) 若process A想要讓process B停止的話,可以在取得process B的handle後呼叫此來做到

Thread Thread 一開始用fork() 會建出一個和元parent process一樣大的child process 但很多時候,我們不需要整個process都copy一份來執行,相對的,僅需要拿出一部分來執行就可以了,也就是建一個比較簡化的process 這些process再同一個記憶體空間裡執行,但各自有各自的Stack 像這種process就叫做light process 或稱thread 每個process可以有一個或一個以上的thread,所以process是分配資源的單位,而thread才是CPU執行的最小單位

Thread Thread 產生Child Thread CreateThread() or 利用AfxBeginThread() AfxBeginThread()除了建立一個thread以外,還會建立一些thread相關的資訊 Thread資訊 GetCurrentThread(VOID) GetCurrentThreadID(VOID) GetThreadPriorty( HANDLE hThread) SetThreadPriority (HANDLE hThread , int nPriority) 終止Thread TerminateThread(HANDLE hThread) or _endthread()

Thread Thread1 Critical Section Thread2 Thread3 同步化 (Synchronization) 可以想像成是一個房間,同一個process中的每一個thread要存取資源,就必須要進到這個房間才能存取資源 達到同步化的一種方式,對於資源的使用,每一個thread必須先進入一個critical section才能取用該資源 InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection) 初始化一個Critical Section物件, EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) 若有多個thread同時呼叫此函式想要進入同一個Critical Section,OS會做選擇並只讓一個thread進入,而其他thread就必須等到該htread結束後,才能進入Critical Section,也就是其他thread會被blocking住 TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) 詢問目前Critical Section的狀態 LeaveEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) 當一個thread進入Critical Section執行結束後,要呼叫此函式告知OS,否則其他被blocking的thread將無法使用該Critical Section DeleteEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) 若要刪除一個Critical Section,可以利用此函式 Thread1 Try Critical Section Thread2 Try Thread3 Try

Thread 同步化 (Synchronization) Mutex 不同於利用Critical Section的方式,利用Mutex的話,可以讓不同process的不同thread都參予資源的競爭 就如同Critical Section一樣,要能夠使用資源的thread,就要先取得Mutex才能使用資源 無名Mutex (unnamed Mutex) & 具名Mutex (named Mutex) CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes , BOOL bInitialOwner , LPCTSTR lpName) lpMutexAttributes : 和系統的安全設定有關 bInitialOwner : 若是true,表示建立起mutex的那個thread一開始就擁有這個mutex lpName : 這個mutex的名稱,若是採用此參數,即為一個具名Mutex 對於和建立起這個mutex的thread同process的thread來說,可以直接透過此mutex的handle來存取這個mutex 但對於那些不同process的thread呢?只好呼叫OpenMutex來存取mutex OpenMutex( DWORD dwDesiredAccess , BOOL bInheritHandle , LPCTSTR lpName) dwDesiredAccess : 是存取的方式,通常設為MUTEX_ALL_ACCESS即可 bInheritHandle : 決定child process是否也可以使用這個OpenMutex()所傳回來的handle lpName : 就是該mutex的名稱 ReleaseMutex( HANDLE hMutex) 釋放mutex,如此其他thread才能使用到

Thread Thread1 Access Resource Thread2 Getting Mutex Thread3 Thread4

Thread 同步化 (Synchronization) Event Event功能就有點像紅綠燈,如果thread是車子的話,那就要交給Event來控制是否一台車可以通行(存取資源) 自動和手動兩種 自動 : 每次只放一台車通過,隨即轉成紅燈 手動 : 變成綠燈後,可以放所有的車子通過,且燈號不變,直到手動將燈號改變為止 CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes , BOOL bManualReset , BOOL bInitialState , LPCTSTR lpName) lpEventAttributes : 設定安全管制 bManualReset : 表示為自動或手動 bInitialState : 代表一開始的狀態,及是否要亮綠燈 lpName : event的名稱 也分兩種 : 具名和匿名,同樣的,用具名的方式,可以利用OpenEvent讓其他process的thread作存取 OpenEvent( DWORD dwDesiredAccess , BOOL bInheritHandle , LPCTSTR lpName) SetEvent( HANDLE hEvent) 將event設為綠燈 PulseEvent() ResetEvent(HANDLE hEvent) 因為手動的event不會自動做綠轉紅的動作,所以要交給這個函式來李楚

Thread Event Ex. CEvent myEvent; void func() { myEvent.Lock(); //…… myEvent.SetEvent(); }

Thread 同步化 (Synchronization) CSemaphore 利用CSemaphore可以用來管制某一段程式,讓其在同一時間內.只能被指定數目的thread執行 利用Lock , Unlock的方式來做 CSemaphore DempSemaphore(iCount , lMaxCount , “Demo”) void func() { DempSemaphore.Lock(); //……………. DempSemaphore.Unlock(); }

Thread 同步化 (Synchronization) Deadlock & starvation

Thread 等待物件狀態改變 DWORD WaitForSingleObject( HANDLE hHANDLE , DWORD dwMilliseconds) 這個函式是用來等待hHandle的狀態發生變化,如果在dwMilliseconds個微秒內,他所等待的事件沒有發生改變,就不等了!! dwMilliseconds可以射程INFINITE,做無限期等待 可以檢查WaitForSingleObject()的回傳值來確認是哪一種條件成立而返回 WAIT_ABANDONED : 若等待的對象是mutex則可能回傳此 WAIT_OBJECT_0 : 等待的條件成立 WAIT_TIMEOUT :愈時 DWORD WaitForMultipleObject( DWORD nCount , CONST HANDLE *lpHANDLE , BOOL bWaitAll , DWORD dwMilliseconds) 可以同時等待數個不同的物件 lpHANDLE : HANDLE的陣列 nCount : 陣列長度 回傳型態 WAIT_OBJECT_0+k : k是一個數字,表示第k個物件狀態改變了!! WAIT_ABANDONED_0+k : k一樣是個數字,表示第k個mutex物件放棄了

Thread GUI Thread & 非GUI Thread GUI Thread 凡是在thread中會建立視窗的 會有分的原因在於’訊息佇列’ ’訊息佇列’是相當耗資源的,因此盡量能免則免

Thread 非GUI Thread 一般都是利用AfxBeginThread()來建立 可以輕易的轉成GUI Thread

Thread 也可以利用CWinThread產生Thread Step1 : 建立一個衍生自CWinThread的新類別 Step2 : 利用CWinThread::CreateThread()來建立新Thread CreateThread(DWORD dwCreateStyle = 0 , UNIT nStackSize = 0 , LPSECURITY_ATRIBUTES lpsecurityAttrs = NULL) dwCreateStyle 設定為CREATE_SUSPENED的話,那這個thread產生之後會先暫停不動,直到程式在呼叫ResumeThread()才會起來 nStackSize : Stack的初始大小,設為0是表示和父thread一樣多 Step3 : 利用CWinThread:: InitInstance()開始執行 必須override這個函式 m_pMainWnd : 建立起來thread主視窗的指標 利用ExitInstance()離開

Thread 利用標準函式庫所提供的函式來建立thread _beginthread() _endthread()

Windows 訊息處理 (續) 單執行緒 (single-threading) 程式的訊息處理 在典型的 Windows 程式中,主執行緒會具備一個訊息處理迴圈的執行片段 (參考第一章的範例) ,藉由訊息偵測函式 GetMessage() 決定是否有訊息被處理。若沒有任何訊息要處理,則程式會被暫停 (suspend) ,讓其他程式執行。 倘若有訊息要被處理,則會接著執行 TranslateMessage() 函式,將鍵盤訊息轉換為輸入訊息之後,接著執行 DispatchMessage() 函式。這個函式會呼叫程式中的訊息處理函式,而訊息處理函式則會依據訊息的不同加以處理。處理完之後, DispatchMessage() 才會返回。 在 MFC 的架構中, MFC 會幫我們架構訊息處理函式,因此我們只要在訊息映射表中適當地填空,MFC 就會幫我們根據訊息呼叫適當的函式。

Windows 訊息處理 (續) 單執行緒程式的控制權釋放 假定我們的程式中有一個會持續耗用相當大的 CPU 資源的計算 (例如求一個 1000x1000 大小矩陣的反矩陣,或者是求 RSA-2048 密碼系統某明文的對應密文) 。在過去的 Windows 3.1 的時代,這段計算可能使得電腦停止回應﹔而即使是今日的 32-bit Windows 系統,這段計算仍然可能使得我們的程式停止回應。 為了避免我們的電腦或程式停止回應,我們可以在這個複雜計算的核心加入一小段與我們的訊息迴圈有類似行為的一段敘述。 PeekMessage() 函式運作方式和 GetMessage() 相當類似,但是它只會檢查該瞬間程式的訊息佇列 (message queue) 中是否有任何訊息,若有訊息則將訊息傳回,而沒有訊息的時候也會立即返回並告知程式無訊息。

Windows 訊息處理 (續) 計時器在單執行緒程式中的多工效果 Windows 的計時器 (timer) 功能能夠讓程式每隔固定時間就收到訊息。我們可以藉由處理計時器的函式,使得我們看起來具有多工的效果。 計時器的使用方法相當簡單,不論是直接使用 SDK 或者在 MFC 中。在 MFC 中,我們只要呼叫 CWnd 的成員函式 SetTimer() ,並且利用 ClassWizard 的幫助建立處理WM_TIMER 訊息的函式。一旦你啟動計時器時, WM_TIMER 訊息會不斷地送到你的視窗中,直到你呼叫 CWnd::KillTimer() 函式,或者計時器的視窗被 destroy 為止。 必要時,你可以使用數個計時器,並以整數值識別之。要注意的是,因為 Windows 並非即時作業系統,若你指定的時間間隔小於 100ms ,可能會不夠精確。

*Dlg.h ... class CExam11_1Dlg : public CDialog { // Implementation private: int m_nTimer; int m_nCount; enum { nMaxCount = 10000 }; };

*Dlg.cpp ... CExam11_1Dlg::CExam11_1Dlg(CWnd* pParent /*=NULL*/) : CDialog(CExam11_1Dlg::IDD, pParent) { //{{AFX_DATA_INIT(CExam11_1Dlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_nCount = 0; } void CExam11_1Dlg::OnStart() // TODO: Add your control notification handler code here MSG message; m_nTimer = SetTimer(1, 100, NULL); ASSERT(m_nTimer != 0); GetDlgItem(IDC_START)->EnableWindow(FALSE); volatile int nTemp1, nTemp2; for(m_nCount = 0; m_nCount < nMaxCount; m_nCount++) { for(nTemp1 = 0; nTemp1 < 100; nTemp1++) { for(nTemp2 = 0; nTemp2 < 10000; nTemp2++) {

*Dlg.cpp if(::PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) { ::TranslateMessage(&message); ::DispatchMessage(&message); } CDialog::OnOK(); void CExam11_1Dlg::OnCancel() { // TODO: Add extra cleanup here if(m_nCount == 0) { CDialog::OnCancel(); } else { m_nCount = nMaxCount; //CDialog::OnCancel(); void CExam11_1Dlg::OnTimer(UINT nIDEvent) // TODO: Add your message handler code here and/or call default CProgressCtrl *pBar = (CProgressCtrl *)GetDlgItem(IDC_PROGRESS); pBar->SetPos(m_nCount * 100 / nMaxCount); CDialog::OnTimer(nIDEvent);

多執行緒程式設計 Why Multi-threading? 並不是每個程式都需要寫成多執行緒程式,因為當我們建立一個多執行緒程式時,不論在除錯以及撰寫難度上,都會提高很多,因為撰寫多執行緒程式就好像駕馭一台多頭馬車,控制不好馬車就可能會被拆散。 因為多執行緒的觀念,提供了程式設計師一個可以維持使用者介面不凍結,而又能同時處理瑣碎工作的機會。 多執行緒程式的意義,在於我們可以把一些工作,分配給其他 thread 執行,讓應用程式不會為了執行這些工作而造成整個視窗介面遭到凍結,致使無法回應使用者的訊息。

多執行緒程式設計 (續) 執行緒的種類 大體而言,一個行程的所有程式碼和資料空間對於該行程中的所有執行緒都是可用的。例如兩個執行緒都能夠存取相同的全域變數。 執行緒是由作業系統管理,每個執行緒都有自己的堆疊。 Windows 提供兩種執行緒:工作者執行緒 (worker threads) 和使用者介面執行緒 (UI threads) 。 UI 執行緒擁有視窗,所以它有自己的訊息迴路。 工作者執行緒沒有視窗,所以不需要處理訊息。工作者執行緒程式比較容易撰寫,也比較常用。

CWinThread

Resources IDR_THREAD IDM_PLAY

MainFrm.h ... class CMainFrame : public CFrameWnd { protected: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; CChildView m_wndView; CToolBar m_threadToolBar; };

MainFrm.cpp int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { ... SetTimer(1, 100, NULL); m_threadToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC); m_threadToolBar.LoadToolBar(IDR_THREAD); m_threadToolBar.EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_threadToolBar); return 0; } void CMainFrame::OnTimer(UINT nIDEvent) // TODO: Add your message handler code here and/or call default Invalidate(); CFrameWnd::OnTimer(nIDEvent);

ChildView.h ... #define WM_BBSORTSTOP (WM_USER + 10) #define WM_QSORTSTOP (WM_USER + 11) struct THREAD_INFO { int *array; CWnd *wnd; } ; ///////////////////////////////////////////////////////////////////////////// // CChildView window class CChildView : public CWnd { // Construction public: CChildView(); // Attributes int m_BBSortArray[30]; int m_QSortArray[30]; CWinThread *m_BBSort, *m_QSort; THREAD_INFO if_BBSort, if_QSort;

ChildView.h // Operations public: static UINT ThreadBBSort(LPVOID pParam); static UINT ThreadQSort(LPVOID pParam); static int qsort_partition(int *, int, int); static void qsort_sort(int *, int, int); // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CChildView) protected: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //}}AFX_VIRTUAL // Implementation virtual ~CChildView(); // Generated message map functions //{{AFX_MSG(CChildView) afx_msg void OnPaint(); afx_msg void OnPlay(); afx_msg void OnUpdatePlay(CCmdUI* pCmdUI); //}}AFX_MSG afx_msg LRESULT OnBBSortStop(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnQSortStop(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() };

ChildView.cpp // ChildView.cpp : implementation of the CChildView class // ... CChildView::CChildView() { int i; CTime t = CTime::GetCurrentTime(); for(i = 0; i < 30; i++) m_BBSortArray[i] = m_QSortArray[i] = 20; m_BBSort = NULL; m_QSort = NULL; srand((unsigned)t.GetTime()); } BEGIN_MESSAGE_MAP(CChildView,CWnd ) //{{AFX_MSG_MAP(CChildView) ON_WM_PAINT() ON_COMMAND(IDM_PLAY, OnPlay) ON_UPDATE_COMMAND_UI(IDM_PLAY, OnUpdatePlay) //}}AFX_MSG_MAP ON_MESSAGE(WM_BBSORTSTOP, OnBBSortStop) ON_MESSAGE(WM_QSORTSTOP, OnQSortStop) END_MESSAGE_MAP()

ChildView.cpp (續) void CChildView::OnPaint() { CPaintDC dc(this); // device context for painting CBrush bbrush(RGB(0, 0, 255)), wbrush(RGB(255, 255, 255)), rbrush(RGB(255, 0, 0)); int i; // TODO: Add your message handler code here dc.FillRect(CRect(0, 0, 1023, 767), &wbrush); if(m_BBSort) dc.FillRect(CRect(16, 16, 19, 19), &rbrush); for(i = 0; i < 30; i++) { dc.FillRect(CRect(20 + 1, 20 + 5 * i + 1, 25 + m_BBSortArray[i] * 5 - 1, 20 + 5 * i + 4), &bbrush); } if(m_QSort) dc.FillRect(CRect(16, 216, 19, 219), &rbrush); dc.FillRect(CRect(20 + 1, 220 + 5 * i + 1, 25 + m_QSortArray[i] * 5 - 1, 220 + 5 * i + 4), &bbrush); // Do not call CWnd::OnPaint() for painting messages void CChildView::OnPlay() // TODO: Add your command handler code here

ChildView.cpp (續) for(i = 0; i < 30; i++) m_BBSortArray[i] = m_QSortArray[i] = 1 + rand() % 140; if_BBSort.array = m_BBSortArray; if_BBSort.wnd = this; m_BBSort = AfxBeginThread(ThreadBBSort, (LPVOID) &if_BBSort); if_QSort.array = m_QSortArray; if_QSort.wnd = this; m_QSort = AfxBeginThread(ThreadQSort, (LPVOID) &if_QSort); } UINT CChildView::ThreadBBSort(LPVOID pParam) { int i, j, t; int *a = ((THREAD_INFO *)pParam)->array; CWnd *wnd = ((THREAD_INFO *)pParam)->wnd; for(i = 0; i < 30; i++) { for(j = 28; j >= i; j--) { if(a[j] > a[j+1]) { t = a[j+1]; a[j+1] = a[j]; a[j] = t; volatile x1, x2; for(x1 = 0; x1 < 1000; x1++) { for(x2 = 0; x2 < 1000; x2++) { }

ChildView.cpp (續) } wnd->PostMessage(WM_BBSORTSTOP, 0, 0); return 0; UINT CChildView::ThreadQSort(LPVOID pParam) { // int i, j, t; int *a = ((THREAD_INFO *)pParam)->array; CWnd *wnd = ((THREAD_INFO *)pParam)->wnd; qsort_sort(a, 0, 29); wnd->PostMessage(WM_QSORTSTOP, 0, 0); int CChildView::qsort_partition(int *a, int p, int r) { int x, i, j, t; x = a[p]; i = p - 1; j = r + 1; while(1) { do j = j - 1; while(!(a[j] <= x)); do i = i + 1; while(!(a[i] >= x)); if (i < j) { t = a[i]; a[i] = a[j];

ChildView.cpp (續) a[j] = t; volatile x1, x2; for(x1 = 0; x1 < 1000; x1++) { for(x2 = 0; x2 < 1000; x2++) { } } } else { break; return j; void CChildView::qsort_sort(int *a, int p, int r) { int q; if(p < r) { q = qsort_partition(a, p, r); qsort_sort(a, p, q); qsort_sort(a, q+1, r);

ChildView.cpp (續) afx_msg LRESULT CChildView::OnBBSortStop(WPARAM wParam, LPARAM lParam) { // m_BBSort->SuspendThread(); // delete m_BBSort; m_BBSort = NULL; return 0; } afx_msg LRESULT CChildView::OnQSortStop(WPARAM wParam, LPARAM lParam) { // m_QSort->SuspendThread(); // delete m_QSort; m_QSort = NULL;