1.Windows SDK程序开发流程
主要分为程序代码和UI资源两部分。
2.以消息未基础,以事件驱动
程序不断的等待外围的输入,判断在处理。操作系统通过捕捉外围输入,以消息的形式进入程序中,程序通过获取的不同消息进行不同的处理。USER模块掌管外围的驱动程序。
程序通过一个循环来获取消息。
MSG msg;
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
其中MSG是一个结构体如下:
typedef struct tagMSG
{
HWND hwnd;
UINT message; // WM_xxx ,例如WM_MOUSEMOVE ,WM_SIZE...
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
获取消息后必须将获取的消息进行处理,这个过程就需要一个窗口处理函数函数,形式如下:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
//....
break;
case WM_CLOSE:
//....
break;
case WM_DESTROY:
//....
break;
//...
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
整个过程如下图:
3.Windows程序
main 是一般C程序的进入点:
int main(int argc, char *argv[]);
{
...
}
WinMain 则是Windows程序的进入点:
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
...
}
// 在Win32 中CALLBACK被定义为__stdcall,是一种函数调用习惯,关系到
// 参数压入到堆栈的次序,以及处理堆栈的责任归属。其它的函数调用习惯还有
// _pascal 和_cdecl
初始化后程序进入消息循环:
while (GetMessage(&msg,...)) {
TranslateMessage(&msg); // 转换键盘消息
DispatchMessage(&msg); // 分派消息
}
其中TranslateMessage是为了将键盘消息转化,DispatchMessage将消息传给窗口函数去处理,当消息发生之时,OS已根据当时状态,为他表明了所属窗口。
而窗口窗口类又已
经标明了窗口函数(也就是wc. lpfnWndProc所指定的函数),所以DispatchMessage可以将消息发送给窗口处理。
消息循环中的DispatchMessage通过USER,模块将消息送到窗口函数,窗口函数通过Switch/case来判断消息种类,由于窗口函数是右系统所调用,所以这就是一种回调函数,用于Windows系统调用,还不会由我们调用。
整个流程是消息经过输入,再经由消息循环的抓取,再传输给窗口进而给窗口函数,
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
所有消息都要经过处理,所以Switch/case中的default必须调用DefWindwosProc,这个是windows内部预设的消息处理函数。
首先,定义一个MSGMAP_ENTRY 结构和一个di m 宏:
struct MSGMAP_ENTRY {
UINT nMessage;
LONG (*pfn)(HWND, UINT, WPARAM, LPARAM);
};
#define dim(x) (sizeof(x) / sizeof(x[0]))
其中pfn是一个函数指针,用出指针处理nMessgae消息,接下来定义两个数组_messageEntries[]和_commandEntries[],将消息和处理方法关联起来:
// 消息与处理方法之对照
struct MSGMAP_ENTRY _messageEntries[] =
{
WM_CREATE, OnCreate,
WM_PAINT, OnPaint,
WM_SIZE, OnSize,
WM_COMMAND, OnCommand,
WM_SETFOCUS, OnSetFocus,
WM_CLOSE, OnClose,
WM_DESTROY, OnDestroy,
} ;
// Command-ID 与处理方法对照
struct MSGMAP_ENTRY _commandEntries =
{
IDM_ABOUT, OnAbout,
IDM_FILEOPEN, OnFileOpen,
IDM_SAVEAS, OnSaveAs,
} ;
窗口函数的设计如下:
//----------------------------------------------------------------------
//
窗口函数
//----------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
int i;
for(i=0; i < dim(_messageEntries); i++) { // 消息对照表
if (message == _messageEntries[i].nMessage)
return((*_messageEntries[i].pfn)(hWnd, message, wParam, lParam));
}
return(DefWindowProc(hWnd, message, wParam, lParam));
}
//----------------------------------------------------------------------
// OnCommand -- 专门处理WM_COMMAND
//----------------------------------------------------------------------
LONG OnCommand(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
int i;
for(i=0; i < dim(_commandEntries); i++) { // 命令对照表
if (LOWORD(wParam) == _commandEntries[i].nMessage)
return((*_commandEntries[i].pfn)(hWnd, message, wParam, lParam));
}
return(DefWindowProc(hWnd, message, wParam, lParam));
}
//----------------------------------------------------------------------
LONG OnCreate(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
...
}
//----------------------------------------------------------------------
LONG OnAbout(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
...
}
//----------------------------------------------------------------------
这样一来WndProc和OnComand就不用改变了,每有新要处理的消息,只要添加到_messageEntries[]和_commandEntries[]两个数组中就行了。
对话框有两种:
- 模态对话框:当子对话框运行的时候父窗口不能使用
- 无模对话框:子对话框和父窗口共同运行
一张图说明问题:
4.Windows程序生命周期
- 在初始化过程中调用CreateWindow创建窗口,同时CreateWindow发送WM_CREATE消息,用于初始化动作。
- 在程序运行过程中不断的使用GetMessage获取存储的消息,如果获取的消息是WM_QUIT,GetMessage就返回0同时While循环就结束,从而就结束运行。
- DispatchMessage通过USER模块将消息发送到窗口函数。
- 不断重复2和3。
- 当点击关闭按钮时,窗口发送WM_CLOSE消息,这个消息一般是DefWindwosProc进行处理。
- 当DefWindwosProc收到WM_CLOSE后嗲偶哦那个DestoryWindow把窗口清理,DestoryWindow本身会发送WM_DESTORY消息。
- 窗口函数对WM_DESTORY消息进行处理,通过调用PostQuitMessage处理。
- 通过PostQuitMessage发送WM_QUIT消息,让GetMessage获取消息让2处理。
5.空闲时间处理
当消息队列中没有消息的时候,就存在空闲时间,获取这个时间可以通过如下代码:
while (TRUE) {
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE) {
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
OnIdle ();
}
}
PeekMessage
返回
TRUE
的条件是有消息,如果没有消息返回
FALSE
GetMessage
返回
TRUE
的条件是有消息且该消息不为WM_QUIT
返回 ,
FALSE
的条件是有消息且该消息
为
WM_QUIT
相同点:
PeekMessage函数与GetMessage函数都用于查看应用程序消息队列,有消息时将队列中的消息派发出去。
不同点:
无论应用程序消息队列是否有消息,PeekMessage函数都立即返回,程序得以继续执行后面的语句(无消息则执行其它指令,有消息时一般要将消息派发出去,再执行其它指令)。GetMessage函数只有在消息对立中有消息时返回,队列中无消息就会一直等,直至下一个消息出现时才返回。在等的这段时间,应用程序不能执行任何指令。
6.进程与线程
进程:执行中的程序。
线程:在一个程序内部也可以实现多个任务并发执行,其中每个任务称为线程,线程是比进程更小的执行单位,它是在一个进程中独立的控制流,即程序内部的控制流。
内核对象是系统的一种资源,内核对象一经创建,任何应用程序都可以使用。系统通过一个使用计数来管理内核对象,常用的内核对象如下:
内核对象 |
产生方法 |
event |
CreateEvent |
mutex |
CreateMutex |
semaphore |
CreateSemaphore |
file |
CreateFile |
file-mapping |
CreateFileMapping |
process |
CreateProcess |
thread |
CreateThread |
内核对象产生方式不同,但是都会获得一个handle句柄作为识别,每被使用一次,使用计数加1,其结束的时候都是调用CloseHandle。其中process对象是一个数据结构,系统通过它来管理进程。
执行一个程序,就会产生一个进程,最直接的就是通过windows的shell执行一个App.exe应用程序,当双击就可运行.流程如下:
通过CreateProcess创建进程:
BOOL
WINAPI
CreateProcessW(
__in_opt LPCWSTR lpApplicationName,
__inout_opt LPWSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCWSTR lpCurrentDirectory,
__in LPSTARTUPINFOW lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
<待续>