本文大部分内容都是摘自孙鑫老师,在下就是一个简单的总结,归纳,希望对大家有用!!!!
Windows操作系统是一种完全不同于传统的dos方式的程序设计方法,是事件驱动的方式(主要是基于消息的,当用户需要完成某种功能时,会调用操作系统的某种支持,然后操作系统将用户的某种需要,包装成消息,并投递到消息队列中去,最后应用程序从消息队列中取走消息,并进行响应。)
Windows应用程序,操作系统,计算机硬件之间的相互关系。
1,表示操作系统能操作输出设备,以执行特定的功能:让声卡发出声音,让显卡显示图像
2,表示操作系统能够感知输入设备状态的变化:鼠标移动,键盘按下,并且能够清楚的明白当前移动到了那个位置,键盘按下的是什么字符
(这就是操作系统和计算机硬件之间的交互关系)
应用程序的开发者通常不需要知道具体的实现细节,我们所关心的仅仅是应用程序和操作系统之间的交互
3,表示应用程序可以通知操作系统执行某个具体的动作,操作系统能够控制声卡发出声音,但它并不知道应该何时发出什么样的声音,需要应用程序告诉操作系统该发出什么声音
那么,应用程序是如何通知操作系统执行某个功能的呢???
有过编程经验的读者都应该知道,在应用程序中要完成某个功能,都是以函数调用的形式实现的,同样,应用程序也是以函数调用的方式来通知操作系统执行相应的功能的。操作系统所能够完成的每一个特殊功能通常都有一个函数与其对应,也就是说,操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用,,,这些函数的集合就是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称为Windows API。
如:CreateWindow就是一个API函数,应用程序中调用这个函数。操作系统就会按照该函数提供的参数信息产生一个相应的窗口。。
4,表示操作系统能够将输入设备的变化上传给应用程序。如:用户在某个程序活动中按下了一个按键,操作系统马上能够感知到这一事件,并且能够知道用户按下的是哪一个键,操作系统并不决定对这一事件如何做出反应,而是将这一事件转交给应用程序,由应用程序决定如何对这一事件做出反应。
蚊子叮了我们一口,我们的神经末梢(操作系统)马上感知到这一事件,并传递给了我们的大脑(应用程序),我们的大脑最终决定如何对这一事件做出反应。将蚊子赶走,或是拍死蚊子。
对事件做出反应的过程就是消息响应。。。。。
操作系统是如何将感知到的事件传递给应用程序的呢???
这是通过消息机制(Message)来实现的。操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用进程,
MSG结构定义如下:
typedef struct tagMSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;
句柄(HANDLE),资源的标示(和指针类似)
操作系统要管理和操作这些资源,都是通过句柄来找到对应的资源。按资源的类型,又可将句柄细分为图标句柄(HICON),光标句柄(HCURSOR),窗口句柄(HWND),应用程序实例句柄(HINSTANCE)等到各种类型的句柄。操作系统给每一个窗口指定的一个唯一的标识号即窗口句柄
其实:WPARAM,LPARAM本质上就是int类型,之所以会出现,只是为了更加直观的明白这些参数的意义
WORD:是一个16位的整数
DWORD:是一个32位的整数,表示了这个消息被投递于消息队列中的时间
POINT:是一个点的结构体(当消息被投递时,光标在屏幕上的位置)
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
//HINSTANCE:当同样的应用程序打开多次的时候,产生多个窗口
//hPrevInstance:标示上一次打开的窗口
//(for a win32-based application,this parameter is always NULL)
//LP:long point(长指针)
//LPSTR:是一个指向字符串首地址的指针
//lpCmdLine:是一个命令行的参数(dos中接收两个参数:argc是参数的个数,argv是一个指针数组,用来存放命令行的参数),
//同样的,windows中也可以接收命令行的参数
//nCmdShow:窗口显示的状态。(最大化,最小化,隐藏显示)
WinMain:是一个入口点函数,其实是由操作系统来调用的,不是由我们去调用的。
当操作系统启动我们的程序的时候,会给我们运行中的程序分配一个实例号,通过这个参数就传递进来了,如果我们传递了一个参数的话,那么操作系统就会将这个参数放在相应的参数中(lpCmdLine)
上面的这些参数,都是由操作系统来赋值呢,,,,
窗口的创建:
创建一个完整的窗口需要经过下面四个操作步骤:
1,设计一个窗口类
2,注册窗口类
3,创建窗口
4,显示及更新窗口
设计一个窗口类???
因为一个窗口具有很多特征,光标(形状(十字,箭头)),图标,背景
也就是说:windows已经为我们定义了一个窗口所需要的因素,我们只需要向里面填写相应的值即可
style:
可能会出现:CS_HREDRAW, CS_VREDRAW
CS:表示的是class style
程序中:我们可能会这么写
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE
//中间用的是(|)运算符,表示两者特性兼而有之
//水平重画,垂直重画,当我们窗口应用程序的水平,垂直坐标发生变化的时候,窗口此刻需要重画
窗口类的类型
在我们的程序中经常要用到一类变量,这个变量里的每一位(bit)都对应某一种特性。当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有该位所对应的特性。当变量中的某几位同时为1时,就表示同时具有几种特性的组合。一个变量中的哪一位代表那种意义,不容易记忆,所以我们经常根据特征的英语拼写的大写去定义一些宏,该宏所对应的数值中仅有与该特征相对应的那一位(bit)为1,其余的bit都为0.
CS_VREDRAW = 0X0001
CS_HREDRAW = 0X0002
CS_DBLCLKS = 0X0008
CS_NOCLOSE = 0X0200
全部都是只有一位为1,其余全是0,如果我们希望某一变量的数值既有CS_VREDRAW的特性,又有CS_HREDRAW,我们只需要使用二进制OR(|)运算符,将其进行组合,如上
如果我们希望在某一变量原有的几个特征上去掉其中一个特征,用取反(~)之后再进行与(&)运算,就可以实现,如在刚才的style基础上去掉CS_NOCLOSE特征,可以用style & ~CS_NOCLOSE
WNDPROC: lpfnWndProc
/*
窗口过程的一个类型,(long point function,接收一个函数指针),
指定了这一类型窗口的过程函数,也称为回调函数(当应用程序收到某一个
窗口的消息时,就应该调用某一函数来处理这条消息(消息通常都是与窗口
相关的)。这一调用不是应用程
序自己来实施,而是由操作系统来完成的,但是,回调函数本身的代码必须
由应用程序自己完成。对于一条消息,操作系统到底调用应用程序中的那个
函数(回调函数)来处理呢???操作系统调用的就是接受消息的窗口所属
的类型中的lpfnWndProc成员指定的函数。每一种不同类型的窗口都有自
己专用的回调函数,该函数就是通过lpfnWndProc成员指定的)
*/
HINSTANCE:hInstance
//代表当前应用程序的实例号
HICON: hIcon
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
HICON LoadIcon(
HINSTANCE hInstance,
LPCTSTR lpIconName
);
//hInstance:handle to application instance
//this parameter must be NULL when a standard icon is being loaded.
//lpIconName: name string or resource identifier
//图标的句柄
HCURSOR:hCursor
//光标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
HCURSOR LoadCursor(
HINSTANCE hInstance,
LPCTRSTR lpCursorName
);
//解释同上
HBRUSH:hbrBackground
//画刷的句柄
wndclass.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);
//the GetStockObject function retrieves a handle to one of the stock pens , brushes, fonts, or palettes(获取一个句柄,笔,画刷,字体,调色板)
HGDIOBJ GetStockObject(
int fnObject;
);
可以为:BLACK_BRUSH, DKGRAY_BRUSH, DC_BRUSH, WHITE_BRUSH, BLACK_PEN, DC_PEN, ANSI_FIXED_FONT
LPCSTR:lpszMenuName
//LPC: long point const
//菜单的名字
wndclass.lpszMenuName = NULL;//表面当前这个程序没有菜单
LPCTSTR:lpszClassName
//一个窗口类的名字
同样的,汽车的生产必须上交国家有关部门的批准。我们才能够生产汽车;类似的,当我们设计完一款新的窗口之后,需要对这个窗口进行注册,向操作系统进行注册,那么我们只有注册成功之后,我们才能创建基于当前类型的窗口
RegisterClass(&wndclass);
//注册一个窗口类
ATOM RegisterClass(
CONST WNDCLASS * lpWndClass;
);
//创建一个窗口,首先需要一个句柄:
HWND hwnd;
//我们利用这个句柄来作为新创建窗口的标示
//窗口创建完成之后,就应该将我们的这个窗口显示出来
ShowWindow(hwnd, SW_SHOWNORMAL);
BOOL ShowWind(
HWND hWnd, //句柄
int nCmdShow //窗口显示的状态(最大化,最小化)
);
//当然,我们还是需要更新的
UpdateWindow(hwnd);
//接下来,就到了我们程序当中关键的部分,消息循环
MSG msg;
while(GetMessage(&msg, NULL , 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//GetMessage:从消息队列中取出一条消息
the GetMessage function retrieves a message from the calling thread's message queue.
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
//每一个应用程序,操作系统就会给其建立一个消息队列,当我们的一个窗口有消息的时候,操作系统会将这个消息放到消息队列当中。我们的程序就会通过GetMessage从消息队列中取出一条一条具体的消息
lpMsg: [out](out表明,在传参的过程中,不需要对这个结构体内部的成员进行初始化,我们只需要定义一个结构体变量,将地址放在这个位置就可以了,通过这个函数调用,会帮我们自动的填充消息结构体内部的变量,这就是out的含义,当我们调用getmessage的时候,这个函数就会从消息队列中取出一条消息,然后利用消息结构体的变量返回)
hWnd:是一个句柄(我们要获取的是哪一个窗口的消息),如果我们将其设置为NULL,则说明,我们需要获取属于这个调用线程的任何窗口的消息,如上,我们将其设置为NULL
wMsgFilterMin:可以指示这个消息的最小的消息值,在我们的消息队列当中,有很多的消息。那么我们可以对这个消息进行一些选择(有的消息可能感兴趣,有的消息可能不感兴趣),那么我们还可以指定一些消息的范围。就可以利用当前的这个参数来设定消息的最小值。(use WM_KEYFIRST to specify the first keyboard message or WM_MOUSEFIRST to specify the first mouse message.)
wMsgFilterMax:指示这个消息的最大值(use WM_KEYLAST to specify the last keyboard message or WM_MOUSELAST to specify the last mouse message.)
如果我们将我们上面两个消息设置为NULL的话,那么getmessage可以返回所有可以利用的消息,没有范围的过滤。
当然,我们也可以用这两个参数做一个消息的过滤,在这个范围当中的我们进行获取,不在这个范围当中的,我们过滤
GetMessage(&msg, NULL , 0, 0);
此刻GetMessage的返回值为bool类型的,当getmessage从消息队列中取出一条消息的时候,返回为真,如果我们能够保证这个消息队列当中始终都有消息的话,那么返回值应该永远为真
//下面我们紧接着用到了TranslateMessage(&msg)
上面提到了,while(getmessage(&msg, NULL, 0, 0))
while(1)的话,那么上面就是一个死循环,这样也就能够保证程序不断的运行,那么什么时候为假呢,也就是什么时候退出呢???
当我们取出一条消息的时候,我们才用到了translatemessage函数,转换消息,翻译消息,这个函数到底什么意思呢???
是对取到的消息对进行转换(当我们按下键盘上的一个按键,系统将发出一个WM_KEYDOWN和WM_KEYUP这样的两个消息,并且参数当中提供了键盘上的,刚才按键中的虚拟的扫描码,但是有时候用户按下某个键,我们想要得到的是用户输入某一个字符的消息,也就是键盘上对应的某一个字母,那么在消息补充的附加参数当中提供我们按键字母的一个ascii码),这个translatemessage函数能够将WM_KEYDOWN和WM_KEYUP这样的两个消息转换为WM_CHAR消息,并且将转换后的消息投递到消息队列当中,(这个转换过程不会影响原来的消息只会产生一个新的消息,)也就是说,我们如果不用这样的消息的话,我们是不会受到WM_CHAR消息的。
当我们采用translatemessage消息的话,一旦我们按键,就能够将WM_KEYDOWN和WM_KEYUP这样的两个消息转换为WM_CHAR消息,并且将转换后的消息投递到消息队列当中,也就是这样的话,就能捕获到WM_CHAR
这样的消息了。
dispatchmessage函数呢??
是将我们收到的消息传到窗口的回调函数中去,也就是窗口过程函数当中去处理,也可以理解为:将消息给了操作系统,然后操作系统调用我们的窗口过程函数,
所以,windows系统消息循环的整个机制:
当一个应用程序建立的时候,操作系统会为这个应用程序分配一个消息队列,凡是跟这个应用程序相关的消息,都会被放入到这个消息队列当中,然后,我们的应用程序利用这个getmessage从消息队列当中取出具体的消息,利用translatemessage将WM_KEYDOWN和WM_KEYUP这样的两个消息转换为WM_CHAR消息,并且投放的消息队列当中,利用dispatchmessage将这个消息投递出去,分发出去(操作系统,操作系统利用我们设计窗口类的时候所指定的窗口回调函数(winsunproc),在这个函数里面,对不同的消息做了不同的处理 )
LRESULT CALLBACK WindowProc(
HWND hwnd, //handle to window
UINT uMsg, //message identifier
WPARAM wParam, //first message parameter
LPARAM lParam //second message parameter
);
函数名字可以改,参数的类型不能改,名字可以改。
四个参数和消息的四个参数很像。(也就是说:利用dispatchmessage的时候,操作系统会去调用这样的windowproc窗口过程函数,将这个消息参数结构体中的前四个参数传递给我们这个函数,对于消息的参数(有两个我们暂时不需要,一个是消息投递时的时间,另一个是光标在屏幕上的位置。))一旦有一个消息产生的时候,都会调用我们的窗口过程函数。因此,我们需要判断到底是哪一个消息。
MessageBox:
如下:我们的用法:
MessageBox(hwnd, szChar, "weixin", MB_OK);
int MessageBox(
HWND hWnd, //handle to owner window
LPCTSTR lpText, //text in message box
LPCTSTR lpCaption, //message box title
UINT uType //message box style
);
程序简单代码:
#include <windows.h>
#include <stdio.h>
LRESULT CALLBACK WinSunProc(
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(BLACK_BRUSH);
wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);
wndcls.hIcon=LoadIcon(NULL,IDI_ERROR);
wndcls.hInstance=hInstance;
wndcls.lpfnWndProc=WinSunProc;
wndcls.lpszClassName="2016 3+1";
wndcls.lpszMenuName=NULL;
wndcls.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls);
HWND hwnd;
hwnd=CreateWindow("2016 3+1","wince3+1",WS_OVERLAPPEDWINDOW,
0,0,600,400,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(
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,"weixin",0);
break;
case WM_LBUTTONDOWN:
MessageBox(hwnd,"mouse clicked","wince3+1",0);
HDC hdc;
hdc=GetDC(hwnd);
TextOut(hdc,0,50,"hello, world",strlen("hello, world"));
ReleaseDC(hwnd,hdc);
break;
case WM_PAINT:
HDC hDC;
PAINTSTRUCT ps;
hDC=BeginPaint(hwnd,&ps);
TextOut(hDC,0,0,"3+1",strlen("3+1"));
EndPaint(hwnd,&ps);
break;
case WM_CLOSE:
if(IDYES==MessageBox(hwnd,"是否真的结束?","weixin",MB_YESNO))
{
DestroyWindow(hwnd);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
return 0;
}
运行结果:
(点击左键)