1、必须包含头文件windows.h
WINDOWS.H是一个最重要的头文件,它包含了其他Windows头文件,这些头文件的某些也包含了其他头文件。这些头文件中最重要的和最基本的是:
WINDEF.H 基本数据类型定义。
WINNT.H 支持Unicode的类型定义。
WINBASE.H Kernel(内核)函数。
WINUSER.H 用户界面函数。
WINGDI.H 图形设备接口函数。
这些头文件定义了Windows的所有资料型态、函数调用、资料结构和常数识别字,它们是Windows文件中的一个重要部分。
其实,远不止这些,我们可以打开该文件看一看,如果vc++6.0安装在C盘,那么我们可以在C:\Program Files\Microsoft Visual Studio\VC98\Include目录下打开该文件。当然WINDEF.H等文件也在这个目录下。这个目录下几乎包含了windows开发的所有的头文件。比如网络通信程序开发时所用的WINSOCK.H头文件等。
2、声明窗口过程函数
窗口过程函数声明如下:
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT 看上去很奇怪。
其实LRESULT 是在WINDEF.H中定义的,可以在VC++6.0用鼠标选择LRESULT 然后点鼠标右键,查看其定义,定义如下:
typedef LONG LRESULT;
L 的意思是LONG RESULT 的意思是结果、返回值。加在一起就是返回长整型的意思。
注意:typedef LONG LRESULT; 并不是创建了另一个数据类型,只是给 LONG类型定义了另一个别名。其实,我们会发觉在C或C++中也没有一种数据类型为LONG,只有long类型。如果我们继续用上面提到的方法查看LONG类型的定义,我们会发觉LONG在WINNT.H文件中定义为:
typedef long LONG;
所以,所谓的LRESULT 其实就是long类型。只是他还有另一个名字叫LRESULT。
Windows应用程序为我们定义了很多的这样的数据类型别名。
下面我们详细的介绍一下windows的常用数据类型。
我们查看WINDEF.H文件,可以看到windows为我们定义的一些常用的数据类型及常量如下:
typedef unsigned long DWORD;
typedef int BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef float FLOAT;
typedef FLOAT *PFLOAT;
typedef int INT;
typedef unsigned int UINT;
typedef unsigned int *PUINT;
typedef UINT WPARAM;
typedef LONG LPARAM;
typedef LONG LRESULT;
#define MAKEWORD(a, b) ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8))
#define MAKELONG(a, b) ((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16))
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
#define LOBYTE(w) ((BYTE)(w))
#define HIBYTE(w) ((BYTE)(((WORD)(w) >> 8) & 0xFF))
可以看到DWORD其实就是一个无符号的长整型的别名,所占内存为4个字节;
typedef int BOOL;
BOOL型其实就是int的别名。所占内存为4个字节。
typedef float FLOAT;
FLOAT 就是float类型的别名。请注意:BOOL 与 bool之间的区别,bool为布尔类型,所占内存只有一个字节。再看一个定义:
typedef FLOAT *PFLOAT;
有点奇怪吧!我们把它的写的格式变一下:
typedef FLOAT * PFLOAT;
我们发觉PFLOAT 就是FLOAT * 即 float * 也就是浮点数指针。
也就是凡是以字母”P“开头的表示他是一个指针类型。
再看两个定义:
typedef int near *PINT;
typedef int far *LPINT;
多了两个修饰符 near 段内 4G虚拟内存空间内寻址 32 位地址
far 段间 在整个64T虚拟内存空间寻址 32位地址
无论是PINT 还是 LPINT 都是一个4节的int * 类型,即是一个整型指针。再看一个定义:
#define MAKEWORD(a, b) ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8))
有点奇怪吧:其实MAKEWORD宏,从字面意义上来看就是”制造字“的意思,也就是用两个字节类型的数据来构成一个字。比如:
MAKEWORD(1,2)
该宏被替换后为:
((WORD)(((BYTE)(a))|((WORD)((BYTE)(b)))<<8))
将括号简化后该语句变为:
(WORD)(BYTE)(a) | (WORD)(BYTE)(b) << 8
语句执行顺序为:
- 将a转化为BYTE类型,然后再将前一步转换的结果转换为WORD类型,
- 将b转换为BYTE类型,然后再将前一步的结果转换为WORD类型,然后再左移8位。
- 再将第一步的结果和第二步的结果按位相或。
所以MAKEWORD(1,2)
的执行过程为:
1、 00000001 将1转化为BYTE类型 即8位二进制数
0000000000000001 将00000001转化为WORD类型
2、 00000010 将2转化为BYTE类型 即8位二进制数
0000000000000010 将00000010转化为WORD类型
0000001000000000 左移8位
3、 0000001000000001 按位相或
我们在编程过程中常常会用到这个宏,比如加载函数库时需要提供函数库的版本,版本通常分为主版本和次版本,版本会通过一个参数进行传递,此时就可以通过:
MAKEWORD(次版本号,主版本号)来获得一个版本参数。
我们在进行WIN SOCKET 套接字编程时就需要用到这个宏,来确定WIN SOCKET库的版本号。
再看两个定义:
#define LOWORD(l) ((WORD)(l))
该宏的功能为:获取长整型数的低十六位。
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
该宏的功能为:获取长整型数的高十六位。
这两个在后面的编程中我们也会用到,当我们点击鼠标时,鼠标的坐标会通过一个32位数传递给窗口过程函数的lparam参数。
要的到x坐标和y坐标就可以通过如下代码得到:、
int x = (int)LOWORD(lparam);
Int y = (int)HIWORD(lparam);
我们需要适应windows为我们定义的这些数据类型的别名,这样做的目的主要是看到数据类型名,就可知道该数据的功能。
函数的调用约定——CALLBACK 以及 WINAPI
接下来我们会看到一个叫CALLBACK 的奇怪的单词,还有接下来windows应用程序的入口函数定义时的WINAPI。在函数前面加上这两个东东是做什么的呢?
CALLBACK和WINAPI与函数的调用约定有关。由CALLBACK和WINAPI修饰的函数又被称之为“回调函数。一般来说在应用程序中编写的函数,即可以被应用程序调用,也可以被windows操作系统来调用。但是,如果一个函数在声明时用CALLBACK和WINAPI来修饰,则表明该函数是被操作系统调用。这些函数包括:窗口过程函数,线程函数等。应用程序一般不会调用”回调函数“。之所以把应用程序提供给操作系统调用的函数称之为”回调函数“,是与操作系统提供给应用程序调用的API函数相比较而言的。API是操作系统提供给应用程序调用的函数,而”回调函数“是应用程序提供给操作系统调用的函数。
有两种函数调用约定。一种是: __stdcall。另一种是_cdecl。
被普通应用调用的函数的函数的调用约定为_cdecl调用约定。、
而被操作系统调用的函数的调用约定为:__stdcall。
其中CALLBACK 和WINAPI 的含义是:规定该函数的调用约定为__stdcall。我们可以在Microsoft Visual C++ 6.0中选中“WINAPI ”,点右键:如图所示:
可以看到WINAPI 被定义为:#define WINAPI __stdcall
函数的默认调用约定为:_cdecl调用约定。
这两种调用约定有何区别呢?其实他们的区别很小。主要是函数形参内存的回收方式上有所区别。
下面我们创建一个名叫“调用约定的”控制台应用程序(创建过程就不再赘述)。
输入如下代码:
在“add(i,j);”语句前设置断点。然后调试运行。调试运行菜单操作如下图: