事件驱动系统:
Windows程序的运行本质:Message Based,Event Driven。
1.Win32程序开发流程:
Windows程序分为”程序代码“和”UI资源“两个部分。
UI资源是指功能菜单、对话框外貌、程序图标、光标形状等等东西。
这些UI资源的实际内容(二进制代码)是借助各种工具产生的,并以各种扩展名存在,如.ico,.bmp,.cur等。
程序必须在一个所谓的资源描述文档.rc文件中描述它们。
RC编译器(rc.exe)读取文件的描述后将所有的UI资源文件集中制作出了一个.res文件,再与程序代码结合在一起,最后才是一个完整的Windows可执行文件。
2.函数库:
Windows支持动态链接,应用程序所调用的Windows API函数是在执行期才链接上的。
DLL Dynamic Link Library:
包括.dll .exe .fon .mod .drv .ocx都是所谓的动态链接库库函数
Windows调用的函数可以分为C runtimes和Windows API两大部分。
# C runtimes:
libc.lib 静态链接版本
msvcrt.lib 动态链接版本的import函数库 链接它需要msvcrt40.dll
# Windows API:
由操作系统本身(主要是user32.dll,kernel32.dll,gdi32.dll)
3.以消息为基础,以事件驱动之Message Based,Event Driven
所有的GUI系统,都是以消息为基础的事件驱动系统。
Windows程序的运行是依靠外部发生的事件来驱动。
程序不断等待(利用while循环),等待任何可能的输入,然后做出判断,然后在作出相应的处理。
MSG msg;
while(GetMessage(msg,NULL,NULL,NULL)){
TranslateMessage(msg);
DispatchMessage(msg);
}
这里的输入是操作系统捕捉到,以消息形式(一种数据结构)进入程序之中。
typedef struct tagMSG{
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam,
DWOR time,
POINT pt
}MSG;
输入分类:
由硬件装置所产生的消息,放在系统队列(system Queue)
由Windows系统和其他应用程序传过来的消息,放在程序队列application Queue
窗口函数:Windows Procedure or Windows Function
接收并处理消息的主角就是窗口,每一个窗口都应该有一个函数处理消息,这个函数成为窗口处理函数。
程序进入点WinMain:
当Windows的外壳(shell)侦察到一个使用者想要执行一个Windows程序,于是调用加载器将该程序加载,然后调用C startup code,后者再调用WinMain,开始执行程序。
WinMain的四个参数由操作系统传入。
窗口类注册与窗口诞生
程序必须在产生窗口之前先利用API函数RegisterClass设定属性,此操作为注册窗口类。
窗口函数:
窗口函数通常通过利用switch/case方式判断消息种类,以决定处置方式。
由于它是被Windows系统调用,所以是一种callback函数,意思是在你的程序中,被Windows系统调用。
窗口函数一般形式:
LRESULT CALLBACK WndProc(
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam
);
无论什么消息都必须被处理,所以switch/case中default:必须调用系统默认的DefWindowProc,这是Windows内部默认的消息处理函数。
窗口函数设计为callback形式,才能开放一个接口给操作系统调用。
消息映射的雏形:
试图把窗口函数的内容设计得更模块化,更一般化,一种做法:MFC消息映射表格
struct MSGMAP_ENTRY{
UNINT nMessage,
LONG (*pfn)(HWND,UINT,WPARAM,LPARAM);
};
struct MSGMAP_ENTRY _messageEntries[] = {
WM_CREATE,Oncreate,
WM_PAINT,OnPainte,
.................
}
这样就形成了消息与消息处理例程的关联性建立起来。
MFC将其中的操作封装得更好更精致。
对话框的运行:
Windows对话框以其与父窗口的关系,分为两类:
# 令其父窗口无效,直到对话框结束 modal对话框,比较常用
# 父窗口与对话框共同运行 modelless对话框
为了做出一个对话框,程序员必须准备两样东西:
1)对话框模板dialog template:
在rc文件中定义的一个对话框外貌,以各种方式定义对话框的大小、字形等
2)对话框函数dialog procedure:
类似于窗口函数,但是它通常只处理WM_INITDIALOG和WM_COMMAND两个消息。
对话框的各个控件也是小小窗口,各有自己的窗口函数,它们以消息与其管理者(父窗口)沟通。而所有控件传来的消息都是WM_COMMAND。
Modal对话框的激活和结束,靠的是DialogBox和EndDialog两个API函数
模块定义文件.def
Windows需要一个模块定义文件,将模块名称、程序段和数据段的内存特性、模块堆heap的大小、堆栈大小、所有callback函数等登记下来。
Windows程序的生与死
1)程序初始化过程中调用CreateWindow,为程序建立一个窗口,作为程序的屏幕。
CreateWindow产生窗口后送出WM_CREATE直接给窗口函数,后者于是可以在此时做一些初始化工作。
2)在程序活着的过程中,不断以GetMessage从消息队列抓取消息。如果这个消息是WM_QUIT,getMessage会传回0结束while循环,从而结束整个程序
3)DispatchMessage通过Windows USER模块的协助和监督,把分派至窗口函数,消息函数在此处被判别并处理。
4)重复2,3
5)当使用者按下系统菜单中的Close命令项时,系统送出WM_CLOSE。通常系统不会拦截这个消息,于是DefWindowProc处理它
6)DefWindowProc收到WM_CLOSE消息后,调用DestroyWindow把窗口清除,DestroyWindow本身又送出WM_DESTROY
7)程序对WM_DESTROY的标准反应是调用PostQuitMessage。
8)PostQuitMessage只是送出WM_QUIT消息,准备让消息循环中的GetMessage取得,如步骤2,结束程序
空闲时间idle time的处理onidle
空闲时间,系统中没有任何的消息等待处理。
两个不同的API:
# PeekMessage()
# GetMessage()如果再次执行发现消息队列依旧为空,它会取回控制权,让程序在运行一段时间
3.console程序
console程序与Dos程序的区别:
# 编写方式
在IDE下开发,利用Windows编译器、链接器做出来的程序,就是所谓的Win32程序。
如果程序是以main为进入点,调用C runtime函数和“不牵扯GUI”的Win32 API函数,那么就是一个console程序。
console窗口将成为其标准输入输出装置。
过去在DOS环境下编译的程序,成为DOS程序,它不可能调用Win32 API.
# 程序功能
由于console程序可以调用KERNEL32.DLL等,它可以使用Windows提供的各种高级功能,包括产生进程,执行线程,取得虚拟内存,刺探操作系统的各种数据。
但是由于不能调用GUI相关API,不能有华丽的外表。
DOS和console程序都可以做printf输出和cout输出,也可以做scanf输入和cin输入。
# 可运行文件格式
DOS:MZ
console和所有Win32程序一样,所谓的PE格式。
4.进程与线程Process and Thread
核心对象 创建方式 结束方式
Event CreatEvent CloseEvent
Mutex
Semaphore
File
File_Mapping
Process
Thread
系统给予核心对象一个计数值usage count,没使用一次,对应计数值加1.
进程的诞生和死亡
执行一个程序必然就产生一个进程process。
最直接的方式就是双击一个可执行文件图标,执行起来的APP进程其实就是shell调用CreateProcess激活的:
1)shell调用CreateProcess激活APP.exe
2)系统产生一个“进程核心对象”,计数值+1
3)系统为此进程建立了一个4GB地址空间
4)加载器将必要的代码加载到上述地址空间中,包括APP的程序、数据以及所需的动态链接函数库DLLs。
它们被记录在可执行文件(PE文件格式)的.idata section
5)系统为进程建立一个线程,称为主线程primary thread,线程才是CPU时间的分配对象
6)系统调用C runtime函数库的Startup Code。
7)Startup code调用APP程序的WinMain函数
8)App程序开始运行
9)使用者关闭APP主窗口,使WinMain中的消息循环结束掉,WinMain结束
10)回到Startup code
11)回到系统,系统调用ExitProcess结束进程
通过这种方式运行的所有Windows程序都是shell的子进程。
线程的诞生和死亡:
执行代码是线程的工作,一个进程建立起来,主线程就产生了,我们可以使用CreateThread产生额外的线程,系统就会帮助我们完成:
1)配置”线程对象“,其handle将成为CreateThread的返回值
2)设定计数值为1
3)配置线程的context
4)保留线程的堆栈
5)将context中的堆栈指针缓存器SS和指令指针缓存器IP设定妥当
线程的结束,或寿终正寝(执行完毕,系统调用ExitThread),或未得善终(被别的线程强制终结TerminateThread)
使用_beginthreadex取代CreateThread。
线程优先级Priority
优先级是线程调度的重要依据。线程的优先级范围从0到31(最高)。
首先需要指定”优先级等级“给进程,然后指定”相对优先级“给该进程所拥有的线程
等级 代码 优先级
idle IDLE_PRIORITY_CLASS 4
normal NORMAL_PRIORITY_CLASS 9(前台)或7(后台)(前台线程一般应该比后台线程调高)
high HIGH_PRIORITY_CLASS 13
realtime REALTIME_PRIORITY_CLASS 24