孙鑫MFC笔记教程(1)--Windows程序的内部运行原理

来源:互联网 发布:中医人工智能系统app 编辑:程序博客网 时间:2024/05/16 19:01

 

Windows程序的内部运行原理

 

操作系统和应用程序之间的关系:

应用程序到操作系统表示应用程序可以通知操作系统执行某个具体的动作

操作系统到应用程序表示操作系统能够将输入设备的变化上传给应用程序。如用户在某个程序活动时按了一下键盘,操作系统马上能够感知到这一事件,并且能够知道用户按下的是哪一个键,操作系统并不决定对这一事件如何作出反应,而是将这一事件转交给应用程序,由应用程序决定如何对这一事件作出反应。

 

windows程序设计是种事件驱动方式的程序设计,主要基于消息的。当用户需要完成某种功能时,需要调用OS某种支持,然后OS将用户的需要包装成消息,并投入到消息队列中,最后应用程序从消息队列中取走消息并进行响应。

每个应用程序OS都为它建立一个消息队列,消息队列是个先进先出的缓冲区,其中每个元素都是一个消息,OS将生成的每个消息按先后顺序放进消息队列中,应用程序总是取走当前消息队列中的第一条消息,应用程序取走消息后便知道用户的操作和程序的状态,然后对其处理即消息响应,消息响应通过编码实现。

 

建一个完整的窗口需要经过下面四个操作步骤

设计一个窗口类。如:WNDCLASS wndcls

注册窗口类。如:RegisterClass(&wndcls)

创建窗口。如:CreateWindow(),CreateWindowEX();

显示及更新窗口。如:ShowWindow(),UpdateWindow();

 

消息的结构:

typedef struct tagMSG {

HWND hwnd; //接收消息的窗口句柄,和哪个窗口相关联。

UINT message; //消息标识,消息本身是什么,如WM_CHAR

WPARAM wParam; //消息的附加信息,具体取决于消息本身。

LPARAM lParam;

DWORD time; //消息投递时间。

POINT pt; //消息投递时,光标在屏幕上的位置。

} MSG;

 

WinMain函数结构:

int WINAPI WinMain(

HINSTANCE hInstance, // 当前实例句柄。

HINSTANCE hPrevInstance, //上一个该程序的实例句柄,32位系统中此值都为NULL

LPSTR lpCmdLine, // 命令行指针

int nCmdShow // (窗口)显示的状态

);

要带参调用WinMain类似于命令行的功能,在Project->setting->Debug->Program arguments项填写参数

 

窗口类WNDClass

typedef struct _WNDCLASS {

UINT style; //窗口的类型

WNDPROC lpfnWndProc; //窗口过程函数指针(回调函数)

int cbClsExtra; //窗口类附加字节,为该类窗口所共享。通常0。

int cbWndExtra; //窗口附加字节。通常设为0。

HANDLE hInstance; //当前应用程序事例句柄。

HICON hIcon; //图标句柄 LoadIcon();

HCURSOR hCursor; //光标句柄 LoadCursor();

HBRUSH hbrBackground; //画刷句柄 (HBRUSH)GetStockObject();

LPCTSTR lpszMenuName; //菜单名字

LPCTSTR lpszClassName; //类的名字

} WNDCLASS;

 

 

源码(在VC中建立Win32 Application):

#include<windows.h>

#include<stdio.h>

 

//回调函数声明

LRESULT CALLBACK WindowProc(

HWND hwnd, // handle to window

UINT uMsg, // message identifier

WPARAM wParam, // first message parameter

LPARAM lParam // second message parameter

);

 

int WINAPI WinMain(

HINSTANCE hInstance, // handle to current instance

HINSTANCE hPrevInstance, // handle to previous instance

LPSTR lpCmdLine, // pointer to command line

int nCmdShow // show state of window

)

{

//第一步:设计窗口类

WNDCLASS wndcls;

wndcls.cbClsExtra=0;

wndcls.cbWndExtra=0;

 

//The GetStockObject function retrieves a handle to one of the predefined stock pens, brushes, fonts, or palettes.

wndcls.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //得到预定义的画刷,画笔等

wndcls.hCursor=LoadCursor(NULL,IDC_CROSS); //如果采用预定义的光标,则第一参数为NULL

wndcls.hIcon=LoadIcon(NULL,IDI_WARNING);//同上

wndcls.hInstance=hInstance;

wndcls.lpfnWndProc=WindowProc; //回调函数

wndcls.lpszClassName="winmain";    //给本窗口类取个名字

wndcls.lpszMenuName=NULL;

wndcls.style=CS_HREDRAW | CS_VREDRAW; //水平和垂直重画,这在窗口水平和垂直调整大小的时候告知窗口是否需要重画,如果填写上述两个参数,则表示重画,窗口上的内容将清除重画

 

//第二步:注册窗口类

RegisterClass(&wndcls);

//第三步:创建窗口

HWND hwnd;

hwnd=CreateWindow("winmain","this is first lesson",WS_OVERLAPPEDWINDOW,                CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,                NULL,hInstance,NULL);

 

//第四步:显示窗口

ShowWindow(hwnd,SW_SHOWNORMAL);

 

//第五步:重刷一下窗口,该语句可有可无

UpdateWindow(hwnd);

 

MSG msg;

 

//每次循环从消息队列中取出一条消息进行处理If the GetMessage retrieves a message other than WM_QUIT, the return value is nonzero.If the function retrieves the WM_QUIT message, the return value is zero.

while(GetMessage(&msg,NULL,0,0))

{

//该语句将类似于WM_KeyDown和WM_KeyUp转换生成新WM_CHAR投递给系统处理

//The WM_CHAR message is posted to the window with the keyboard focus

//when a WM_KEYDOWN message is translated by the TranslateMessage function.

//WM_CHAR contains the character code of the key that was pressed.

TranslateMessage(&msg);

 

//将消息投递出去给操作系统,操作系统会自动调用回调函数处理

DispatchMessage(&msg);

}

return 0;

}

 

LRESULT CALLBACK WindowProc(

HWND hwnd, // handle to window

UINT uMsg, // message identifier

WPARAM wParam, // first message parameter

LPARAM lParam // second message parameter

)

{

HDC hdc;

switch(uMsg) {

case WM_PAINT:

PAINTSTRUCT ps;

hdc=BeginPaint(hwnd,&ps);

TextOut(hdc,0,10,"hello world!",strlen("hello world!"));

EndPaint(hwnd,&ps);

break;

case WM_CHAR:

char szChar[20];

sprintf(szChar,"char is %d",wParam);

MessageBox(hwnd,szChar,"消息",MB_OK);

case WM_LBUTTONDOWN:

hdc=GetDC(hwnd); //得到当前窗口的上下文句柄

TextOut(hdc,0,50,"beijing2008!",strlen("beijing2008!"));

ReleaseDC(hwnd,hdc);

break;

case WM_CLOSE:

if(IDOK==MessageBox(hwnd,"确认要退出嘛?","消息",MB_OK))

{

//销毁窗口,但进程中还是保留的,该函数直接抛出WM_DESTROY消息

DestroyWindow(hwnd);

}

break;

case WM_DESTROY:

PostQuitMessage(0); //终止消息循环,并抛出WM_QUIT消息,从而导致上面的消息循环退出

default:

//对于代码中没有涉及到的消息,由提交给系统处理,此句一定要加,否则窗口不会出现

return DefWindowProc(hwnd,uMsg,wParam,lParam);

}

return 0;

}

 

源文档 <http://blog.csdn.net/roger_ge/archive/2008/09/13/2922283.aspx>

 

 

关于句柄

(1)句柄(HANDLE),资源的标识。

(2)操作系统要管理和操作这些资源,都是通过句柄来找到

 对应的资源。按资源的类型,又可将句柄细分成图标句柄

(HICON),光标句柄(HCURSOR),窗口句柄(HWND),

 应用程序实例句柄(HINSTANCE)等等各种类型的句柄。

 操作系统给每一个窗口指定的一个唯一的标识号即窗口句柄。

 

源文档 <http://blog.csdn.net/huahuamoon/archive/2008/01/02/2010279.aspx>

 

CALLBACK

 #define CALLBACK    __stdcall    //标准的调用约定

 #define WINAPIV     __cdecl      //c语言调用约定

 在参数传递的顺序和堆栈的清除有些差异.

修改调用约定:

 Projects->Settings->c/c++->Category:Code Generation  

 

函数调用约定有多种

1、__stdcall调用约定相当于16位动态库中经常使用的PASCAL调用约定。在32位的VC++5.0中PASCAL调用约定不再被支持(实际上它已被定义为__stdcall。除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall调用约定。两者实质上是一致的,即函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。

_stdcall是Pascal程序的缺省调用方式,通常用于Win32   Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上 "@ "和参数的字节数。

2、C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。

_cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。

3、__fastcall调用约定是 "人 "如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。

_fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上 "@ "前缀,在函数名后加上 "@ "和参数的字节数。  

4、thiscall仅仅应用于 "C++ "成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

5、naked   call采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESIEDIEBXEBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。

naked   call不产生这样的代码。naked   call不是类型修饰符,故必须和_declspec共同使用。

关键字   __stdcall__cdecl__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting.../C/C++   /Code   Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz/Gd/Gr。缺省状态为/Gd,即__cdecl

要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定,至于函数名修饰约定,可以通过其它方法模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏,它可以将出函数翻译成适当的调用约定,在WIN32中,它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。

 

源文档 <http://topic.csdn.net/t/20050410/00/3923401.html>

 

 

源文档 <http://blog.csdn.net/huahuamoon/archive/2008/01/02/2010279.aspx>

 

 

 

下面要讲的是最重要的一点,那就是消息循环,先看看孙老师的例子吧:

 MSG msg; //定义一个消息变量

 while (GetMessage (&msg, NULL, 0, 0) )

 {

  TranslateMessage ( &msg);//对取到的消息进行转换翻译消息,意思是对取到的

  //消息对进行转换,当我们按下键盘的某个键的时候,系统将发出一个WM_KEYDOWN

  //和WM_KEYUP 两个消息,参数当中提供的是我们键盘的一个虚拟扫描码,

  //该函数可以将它们转换成WM_CHAR 消息。并将转换后的新消息投递到消息队列中。

  //这些过程不会影响原来的消息,只会产生新的消息。

  

  DispatchMessage (&msg); //将取到的消息传到窗口回调函数去处理,

  //也就是将消息路由给了操作系统,操作系统再调用

  //窗口过程函数(在设计窗口类时指定)

  

 }//endof while()

 

◇ GetMessage 从调用线程的消息队列中获取消息 

 BOOL GetMessage( //调用这个函数的,会从消息队列里中取出一条消息,利用消息结构体的变量lpMsg返回

    LPMSG lpMsg,  //是一个消息结构体的一个指针

    HWND hWnd,  //窗口句柄,指定是要获取那一个窗口的的消息,如果将之设置为NULL,表示想要获取属于调用线程的任何窗口的消息

    UINT wMsgFilterMin,  //指示最小的一个消息值。

    UINT wMsgFilterMax  //指示消息的最大值

  );

 wMsgFilterMin 可以用WM_KEYFIRST 来指示第一个键盘消息,用 WM_MOUSEFIRST 来指示第一个鼠标消息

 wMsgFilterMax   也可用 WM_KEYLAST 指示最后一个键盘消息,用 WM_MOUSELAST 来指示最后一个鼠标消息。

 如果将 wMsgFilterMin 和 wMsgFilterMax 设为0,则返回所有可以利用的消息,没有范围的过滤。

   

 在MSDN中这样解释lpMsg:

  lpMsg

  [out] Pointer to an MSG structure that receives message information from the thread's message queue.

  注意:[out]表明在传参的时候我们没有必要对结构体内部的数据成员初始化,我们只需要定义一个结构体的变量,将它的地址放在这里就可以了。通过函数的调用,会自动填充消息结构体当中的内部的成员变量。

  

◇ 回顾一下:

 当一个应用程序建立的时候,操作系统会为这个应用程序分配一个消息队列,与该程序相关的消息操作系统都会将它们放到这个消息队列当中,应用程序利用GetMessage()从消息队列当中取出一条具体的消息,利用TranslateMessage()将WM_KEYDOWN 和 WM_KEYUP 转换为一个WM_CHAR 消息,放到消息队列当中。利用DispatchMessage()将这个消息投递出去,分发出去,可以理解为分发给了操作系统,操作系统再利用在设计窗口类时指定的回调函数结消息进行处理。这个回调函数对不同的消息回有不同的响应,这样就完成了整个消息的循环。

 

下面来看看回调函数要怎么写,先看MSDN:

  LRESULT CALLBACK WindowProc( //LRESULT是一个long型

    HWND hwnd,  //handle to window

    UINT uMsg,  //message identifier

    WPARAM wParam, //first message parameter

    LPARAM lParam  //second message parameter

  );

注意:窗口回调过程函数就是要写成上面的形式,函数名字可以更改,参数类型不能变。

 

源文档 <http://blog.csdn.net/teshorse/archive/2006/07/23/963670.aspx>