在windows下开发程序,首先要了解Windows环境下程序的运行机制。但在Windows内部,其运行机制是纷繁复杂的,没有必要了解所有的技术细节。而了解Windows程序运行的根本性概念是VC++程序员基本素质。
Windows应用程序,操作系统,计算机硬件之间的相互交互如下所示:
应用程序开发者仅需要关心应用程序与操作系统之间的交互关系即可。
向下的箭头3表示应用程序可以通知操作系统执行某个具体动作,如操作系统能够控制声卡发出声音,但它不知道应该何时发出何种声音,需要应用程序告诉操作系统该发出什么样的声音。这个关系好比有个机器人能够完成行走的功能,但是,如果人们不告诉它往哪个方向上走,机器人是不会主动行走的。这里的机器人就是操作系统,人们就是应用程序。
那么,应用程序是如何通知操作系统执行某个功能的呢?有过编程经验的读者都应该知道,在应用程序中要完成某个功能,都是以函数调用的形式实现的,同样,应用程序也是以函数调用的方式来通知操作系统执行相应的功能的。操作系统所能够完成的每一个特殊功能通常都有一个函数与其对应,也就是说,操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用,这些函数的集合就是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称Windows API。如CreateWindow就是一个API函数,应用程序中调用这个函数,操作系统就会按照该函数提供的参数信息产生一个相应的窗口。API已经成为被广泛使用的专业术语,某个系统或设备,提供给应用程序对其进行编程操作的函数、类、组件等的集合,那么就称作该系统的API。当购买一个语音卡时,会随带一个开发包,这个开发包就是一些函数的集合,这就可以称作语音卡的API。
向上的箭头④表示操作系统能够将输入设备的变化上传给应用程序。如用户在某个程序活动时按了一下键盘,操作系统马上能够感知到这一事件,并且能够知道用户按下的是哪一个键,操作系统并不决定对这一事件如何作出反应,而是将这一事件转交给应用程序,由应用程序决定如何对这一事件作出反应。好比有个蚊子叮了我们一口,我们的神经末梢(相当于操作系统)马上感知到这一事件,并传递给了我们的大脑(相当于应用程序),我们的大脑最终决定如何对这一事件作出反应,如将蚊子赶走,或是将蚊子拍死。对事件作出反应的过程就是消息响应。
操作系统是怎样将感知到的事件传递给应用程序的呢?这是通过消息机制(Message)来实现的。操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序,参看MSDN(Microsoft提供的帮助程序)。
MSG结构定义如下:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
消息结构体包含了与那个窗口相关(HWND hwnd)、这个消息是什么(UINT message)、消息的附加参数(WPARAM wparam、LPARAM lparam)、消息发生投递的时间(DWORD time)、消息投递时光标的位置(POINT pt)。
-
句柄(HANDLE),资源的标识。
操作系统要管理和操作这些资源,都是通过句柄来找到对应的资源。按资源的类型,又可将句柄细分成图标句柄(HICON),光标句柄(HCURSOR),窗口句柄(HWND),应用程序实例句柄(HINSTANCE)等等各种类型的句柄。操作系统给每一个窗口指定的一个唯一的标识号即窗口句柄。 -
从变量的类型区分变量的用途:
//x和y既可以用来表示坐标点,也可以用来表示宽度和高度,还可以用来表示身高和体重。
int x,y;
x=30;
y=30;
typedef int WIDTH
typedef int HEIGHT
WIDTH x;
HEIGHT y;
//好处:我们从变量的类型上就可以知道x和y是用来表示宽度和高度。
- 消息队列:操作系统为每一个应用程序建立一个消息队列,这个队列是一个先进先出的缓冲区,通常是某种变量类型的数组。消息队列中的每一个元素都是一条消息,操作系统按先后顺序将生成的消息放在消息队列里,应用程序总是取走队列里第一条消息。应用程序取得消息后便知道了用户的操作和程序状态的变化,利用应用程序在队列里取到了一个WM_CHAR消息,则一定是用户输入了一个字符,并且通过消息的附加参数可以知道输入的是哪一个字符,应用程序得到消息后便对消息进行处理,这便是所谓的消息响应。
Windows程序的入口函数:
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
);
- 创建一个完整的窗口需要经过下面四个操作步骤:
1.设计一个窗口类;
2.注册窗口类;
3.创建窗口;
4.显示及更新窗口。
- 设计窗口类
typedef struct _WNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS;
`
-
窗口类的类型:
在我们的程序中经常要用到一类变量,这个变量里的每一位(bit)都对应某一种特性。当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有该位所对应的特性。当变量中的某几位同时为1时,就表示同时具有几种特性的组合。一个变量中的哪一位代表哪种意义,不容易记忆,所以我们经常根据特征的英文拼写的大写去定义一些宏,该宏所对应的数值中仅有与该特征相对应的那一位(bit)为1,其余的bit都为0。我们使用goto definition就能发现CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS =0x0008,CS_NOCLOSE=0x0200。他们的共同点就是只有一位为1,其余位都为0。如果我们希望某一变量的数值既有CS_VREDRAW特性,又有CS_HREDRAW特性,我们只需使用二进制OR(|)操作符将他们进行或运算相组合,如style=CS_VREDRAW | CS_HREDRAW | CS_NOCLOSE。如果我们希望在某一变量原有的几个特征上去掉其中一个特征,用取反(~)之后再进行与(&)运算,就能够实现,如在刚才的style的基础上去掉CS_NOCLOSE特征,可以用style & ~CS_NOCLOSE实现。 -
窗口过程函数:
第二个成员变量lpfnWndProc指定了这一类型窗口的过程函数,也称回调函数。回调函数的原理是这样的,当应用程序收到给某一窗口的消息时(还记得前面讲过的消息通常与窗口相关的吗?),就应该调用某一函数来处理这条消息。这一调用过程不用应用程序自己来实施,而由操作系统来完成,但是,回调函数本身的代码必须由应用程序自己完成。对于一条消息,操作系统到底调用应用程序中的哪个函数(回调函数)来处理呢?操作系统调用的就是接受消息的窗口所属的类型中的lpfnWndProc成员指定的函数。每一种不同类型的窗口都有自己专用的回调函数,该函数就是通过lpfnWndProc成员指定的。
举例:汽车厂家生产汽车好比应用程序创建窗口,用户使用汽车好比操作系统管理窗口,某种汽车在销售前就指定好了修理站(类似回调函数),当用户的汽车出现故障后(类似窗口收到消息),汽车用户(类似操作系统)自己直接找到修理站去修理,不用厂家(类似应用程序)亲自将车送到修理站去修理,但修理站还得由厂家事先建造好。
示例代码如下:
#include <windows.h>
#include <stdio.h>
LRESULT CALLBACK WinSunProc(//窗口过程函数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, // command line
int nCmdShow // show state
)
{
WNDCLASS wndcls;//设计窗口类
wndcls.cbClsExtra=0;
wndcls.cbWndExtra=0;
wndcls.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);
wndcls.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wndcls.hInstance=hInstance;
wndcls.lpfnWndProc=WinSunProc;
wndcls.lpszClassName="window";
wndcls.lpszMenuName=NULL;
wndcls.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls);//注册窗口类
HWND hwnd;//窗口句柄
//创建窗口
hwnd=CreateWindow("window","这是一个Windows窗口学习程序",WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,SW_SHOWNORMAL);//窗口显示
UpdateWindow(hwnd);//更新窗口
MSG msg;//消息循环
while(GetMessage(&msg,NULL,0,0))//获取所有的消息
{
TranslateMessage(&msg);//转换消息
DispatchMessage(&msg);//分发消息
}
return 0;
}
LRESULT CALLBACK WinSunProc(//窗口过程函数WindowProc()
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch(uMsg)
{
case WM_CHAR:
char szChar[20];
sprintf(szChar,"char is %d",wParam);
MessageBox(hwnd,szChar,"window",MB_YESNO);
break;
case WM_LBUTTONDOWN:
MessageBox(hwnd,"mouse clicked","window",MB_OKCANCEL);
HDC hdc;
hdc=GetDC(hwnd);
TextOut(hdc,0,50,"这是一个学习windows窗口的程序",strlen("这是一个学习windows窗口的程序"));
ReleaseDC(hwnd,hdc);
break;
case WM_PAINT:
HDC hDC;
PAINTSTRUCT ps;
hDC=BeginPaint(hwnd,&ps);
TextOut(hDC,0,0,"这是一段用于在窗口上显示的文字",strlen("这是一段用于在窗口上显示的文字"));
EndPaint(hwnd,&ps);
break;
case WM_CLOSE:
if(IDYES==MessageBox(hwnd,"是否真的结束?","window",MB_YESNO))
{
DestroyWindow(hwnd);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
return 0;
}
致敬孙鑫老师。