注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

jasonyang9的博客

随便写写

 
 
 

日志

 
 

(怀旧系列)VC程序设计(孙鑫老师)听课笔记:01 Windows程序内部运行原理  

2012-12-29 16:15:23|  分类: programming |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

(怀旧系列)VC程序设计(孙鑫老师)听课笔记:01 Windows程序内部运行原理 - jasonyang9 - jasonyang9的博客
 

=======================
Windows程序内部运行原理
=======================

Windows应用程序、操作系统和计算机硬件之间的相互关系
===================================================

  应用程序 ------ 消息队列

   |    |
  3|    |4
   |    |
  操作系统
   |    |
  1|    |2
   |    |
输入输出设备


Windows程序设计和DOS程序设计完全不同,是一种基于事件驱动方式的程序设计模式,主要是基于消息的。当用户需要完成某种功能时,主要是调用操作系统的某种支持,操作系统将用户的需要包装成消息,投递到消息队列中,应用程序从消息队列中取走消息,并进行响应。消息队列是一个先进先出的缓冲区。
1表示操作系统能够操纵设备,以执行特定的功能。例如,让声卡发出声音、让显卡画出图形。
2表示操作系统能够感知输入系统设备状态的变化。例如,鼠标移动、键盘按下,并能知道鼠标的具体位置,键盘按下的是哪一个字符。
应用程序开发者不需要1和2实现的具体细节。
3表示应用程序可以通知操作系统执行某个具体的动作,如操作系统能够控制声卡发出声音,但它并不知道应该何时发出何种声音,需要应用程序告诉操作系统该发出什么样的声音。
在应用程序中要完成某个功能,都是以函数调用的形式实现的。应用程序以函数调用的方式来通知操作系统执行相应的功能。操作系统所能够完成的每一个特殊功能通常都有一个函数与其对应。操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用。这些函数的集合就是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称Windows API。如CreateWindow就是一个API函数,应用程序调用这个函数,操作系统就会按照该函数提供的参数信息产生一个相应的窗口。
4表示操作系统能够将输入设备的变化上传给应用程序。如用户在某个程序活动时按了一下键盘,操作系统感知到这一事件,并且能够知道用户按下的是哪一个键。操作系统并不决定对这一事件如何作出反应,而是将这一事件转交给应用程序,由应用程序决定如何对这一事件作出反应。对事件作出反应的过程就是消息响应。

操作系统通过消息机制(Message)将感知到的事件传递给应用程序。操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序。

typedef struct tagMSG {
HWND hwnd; // 指向窗口的句柄
UINT message; // 标识一个具体的消息,系统定义成类似WM_LBUTTONDOWN等宏来表示
WPARAM wParam; // 关于消息的附加信息,例如WM_CHAR消息附加具体按键值等
LPARAM lParam; // 关于消息的附加信息
DWORD time; // 消息被传递的时间
POINT pt; // 消息被传递时,鼠标光标在屏幕上的位置
} MSG;


句柄(HANDLE)是资源的标识,操作系统要管理和操作这些资源,都是通过句柄来找到对应的资源。按资源的类型,可以将句柄细分成图标句柄(HICON),光标句柄(HCURSOR),窗口句柄(HWND),应用程序实例句柄(HINSTANCE)等等各种类型的句柄。
操作系统给每一个窗口指定的一个唯一的标识即窗口句柄。
句柄类似指针。

WPARAM和LPARAM其实是同一种数据类型,将它们定义为2种类型是为了区分变量的用途。

int x, y;
x = 30;
y = 30;

x和y既可以用来表示坐标点,也可以表示宽度和高度等等。

typedef int WIDTH
typedef int HEIGHT
WIDTH x;
HEIGHT y;

从变量的类型上就可以知道x和y是用来表示宽度和高度的。

(通过变量名可以更好的区分变量的用途,使用匈牙利命名法可以同时知道其类型)

WORD类型为16位整数,DWORD为32位整数。
POINT类型为一个结构体,含有x和y变量。

WinMain函数
===========

是Windows程序的入口函数,此函数由操作系统调用。

int WINAPI WinMain(
HINSTANCE hInstance, // 当前应用程序的实例句柄
HINSTANCE hPrevInstance, // 前一个应用程序的实例句柄(由同一个应用程序代码执行所产生的,基于Win32的应用程序这个参数总是NULL)
LPSTR lpCmdLine, // 命令行参数(指向字符串类型的指针)
int nCmdShow // 指定程序窗口如何显示
);


DOS程序的入口函数为main函数,而Windows程序的入口函数为WinMain函数。

应用程序的实例是由应用程序代码(编译后的二进制文件)执行后形成的一个运行中的程序,一份应用程序代码可以执行多次,形成多个运行中的程序。应用程序的实例句柄就是标识这些运行中的程序的类似指针的变量。

窗口的创建
----------
创建一个完整的窗口需要经过4个操作步骤:
* 设计一个窗口类(对WNDCLASS结构体的成员变量赋值,指定各参数)
* 注册窗口类
* 创建窗口
* 显示和更新窗口

typedef struct _WNDCLASS {
UINT style; // 窗口类的类型(风格,Style)
WNDPROC lpfnWndProc; // 指向窗口过程函数的指针(回调函数)
int cbClsExtra; // 类的额外的数据空间(通常设为0)
int cbWndExtra; // 窗口的额外的数据空间(通常设为0)
HINSTANCE hInstance; // 当前应用程序实例号句柄(由操作系统传递给程序)
HICON hIcon; // 图标的句柄(用LoadIcon()加载)
HCURSOR hCursor; // 光标的句柄(用LoadCursor()加载)
HBRUSH hbrBackground; // 窗口背景画刷(用GetStockObject()加载)
LPCTSTR lpszMenuName; // 菜单
LPCTSTR lpszClassName; // 类名
} WNDCLASS;


Windows程序中的有些函数需要传递类型(或样式)参数,和现实中的开关接近,其设定方法如下:
类型(Style,样式)设定时利用“|”(逻辑或)运算符将各类型宏定义组合。
宏定义通常为:0x0001、0x0002、0x0004、0x0008、……,等。
(转换为二进制后,只有一位为1,其余为0。组合一起不会相互干扰,为1的位表示打开该类型,为0的位表示关闭该类型)
关闭一个类型用“~”(逻辑取反)运算符后再进行“&”(逻辑与)运算即可。
例如:

style = CS_VREDRAW | CS_HREDRAW; // 组合2个类型(Style)
style = style & ~CS_VREDRAW; // 关闭 CS_VREDRAW


窗口过程函数是一个回调函数。回调函数的原理:当应用程序收到给某一个窗口的消息时,就应该调用某一个函数来处理这条消息。这一调用过程不由应用程序自己来实施,而是由操作系统完成的。但是,回调函数本身的代码是由应用程序自己设计。
操作系统调用的是接受消息的窗口所属的类型中的lpfnWndProc成员所指定的函数。每一种不同类型的窗口都有自己专用的回调函数,这个函数就是通过lpfnWndProc成员指定的。

关于数据类型的强制转换:如果数据类型在本质上不同,则在编译时,强制类型转换可以通过,但在运行时出错。

将窗口类向操作系统注册:

RegisterClass(&wndclass);


创建窗口:

HWND hwnd;
hwnd = CreateWindow(...);


HWND CreateWindow(
LPCTSTR lpClassName, // 注册窗口类名(必须和窗口类中的lpszClassName相同,否则无法显示窗口)
LPCTSTR lpWindowName, // 窗口的名字(标题栏上显示的名字)
DWORD dwStyle, // 窗口的类型(通常用WS_OVERLAPPEDWINDOW)
int x, // 窗口的水平坐标(左上角)
int y, // 窗口的垂直坐标(左上角)
int nWidth, // 窗口的宽度
int nHeight, // 窗口的高度
HWND nWndParent, // 指示父窗口的句柄
HMENU hMenu, // 菜单的句柄
HINSTANCE hInstance, // 应用程序实例号句柄
LPVOID lpParam // WM_CREATE消息附加参数lpParam的指针(在多文档程序MDI中使用)
);


显示窗口:

ShowWindow(hwnd, SW_SHOWNORMAL);

BOOL ShowWindow(
HWND hwnd, // 要显示窗口的句柄
int nCmdShow // 如何显示(最大化、最小化等)
);


更新窗口:

UpdateWindow(hwnd);



消息循环:

MSG msg; // 消息结构体
while(GetMessage(&msg, NULL, 0, 0)) // 从调用线程的消息队列中取出消息
{
TranslateMessage(&msg); // 翻译消息,转换消息对(如将按键的WM_KEYDOWN和WM_KEYUP对转换为一个新的WM_CHAR消息)
DispatchMessage(&msg); // 分发消息,给窗口过程函数处理(分发给操作系统,让其调用回调函数)
}

BOOL GetMessage( // 有消息被取出时返回非0,否则返回0(取到WM_QUIT也返回0)
LPMSG lpMsg, // 消息结构体指针(将变量地址传递给函数,函数将变量赋值后返回)
HWND hWnd, // 指定要获取的窗口的消息(NULL表示属于调用线程的全部消息)
UINT uMsgFilterMin, // 消息的最小值(用于过滤消息,设为0表示不过滤)
UINT uMsgFilterMax // 消息的最大值(同上)
);



窗口过程函数:

回调时,操作系统将消息结构体的前4个参数传递给窗口过程函数。

LRESULT CALLBACK WinSunProc(
HWND hwnd, // 窗口句柄
UINT uMsg, // 消息
WPARAM wParam, // 附加参数1
LPARAM lParam // 附加参数2
)
{
HDC hdc;
PAINTSTRUCT ps;

switch(uMsg) // 判断具体是哪一个消息,分别处理响应
{
case WM_PAINT: // 当窗口需要重绘时产生WM_PAINT消息(通常在被遮盖的部分需要显示,或调整窗口大小后)
hdc = BeginPaint(hwnd, &ps); // 特殊的DC,填充PAINTSTRUCT结构体(系统维护,不需要关心)
TextOut(hdc, 0, 0, "北京维新科学技术培训中心", strlen("北京维新科学技术培训中心"));
EndPaint(hwnd, &ps); // 释放DC
break;
case WM_CHAR: // 用户按键时产生WM_CHAR消息
char szChar[20];
sprintf(szChar, "char is %d", wParam);
MessageBox(hwnd, szChar, "weixin", 0);
break;
case WM_LBUTTONDOWN: // 用户按下鼠标左键时产生WM_LBUTTONDOWN消息(注意:严格来说WM_LBUTTONDOWN不等于click,一对WM_LBUTTONDOWN和WM_LBUTTONUP才是一次click)
MessageBox(hwnd, "mouse click", "weixin", MB_OK);
HDC hDC; // 声明一个hDC变量,指向DC(设备上下文)的句柄
hDC = GetDC(hwnd); // 获取当前窗口的DC
TextOut(hDC, 0, 50, "计算机编程语言培训", strlen("计算机编程语言培训")); // 输出文字
ReleaseDC(hwnd, hDC); // 释放DC所占据的资源(否则产生内存泄漏!)
break;
case WM_CLOSE: // 当窗口将要被关闭时产生WM_CLOSE消息(例如用户点击窗口关闭按钮)
if (IDYES == MessageBox(hwnd, "你是否要退出程序?", "weixin", MB_YESNO)) // 提示用户,根据选择的按钮判断是否要退出程序(将常量放在判断语句左侧可以防止将“==”判断写成“=”赋值)
{
DestoryWindow(hwnd); // 销毁窗口并发送WM_DESTROY消息
}
break;
case WM_DESTROY: // 窗口已经销毁,退出程序
PostQuitMessage(0); // 投递(POST)WM_QUIT消息到消息队列(可以让GetMessage()函数返回0,结束消息循环)
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam); // 把其他所有消息交给DefWindowProc()(缺省窗口过程)处理
}

return 0;
}


CALLBACK定义为__stdcall,__stdcall和__cdecl是两种函数调用约定。__stdcall是标准调用约定(同PASCAL调用约定),__cdecl是C语言调用约定。
函数调用约定体现在参数传递顺序、堆栈清除等方面。VC默认__cdecl约定。

最小WIN32应用程序编写(略,见代码)。

  评论这张
 
阅读(143)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017