VC点滴 之 WinMain(windows程序的运行原理以及VC++的实现过程)

类别:编程语言 点击:0 评论:0 推荐:

WinMain(windows程序的运行原理以及VC++的实现过程)

操作系统以消息机制把输入设备的变化传递给我们的应用程序,操作系统所扮演的角色是神经末梢
        APP
系统调用API 

         OS

 输出       输入

 HARDWARE


操作系统之所以成为操作系统是因为它能够控制硬件的设备,能够控制声卡发出声音,能够控制显卡画一个窗口,但它是如何实现这些功能就不是我们所关心的了,这些是写汇编的人员所要做的事情。操作系统又能够从输入设备获得信息,譬如说,操作系统能够感受到鼠标的移动,并且还能知道鼠标移动后的位置。再比如说操作系统能感受到键盘的按下,并且知道按下的是哪个键,这些都表明操作系统和输入输出设备有一个关系,至于操作系统怎样去控制输入设备以及怎样把输入设备的信息拿到手的,这些我们都不用关心,这些是写操作系统的人或者写驱动程序的人所要干的事。由于应用程序是运行在操作系统上的,我们所关心的是应用程序和操作系统之间有什么样的关系。就仿佛是机器人能够走路,但是机器人要走几步,向哪个方向走,这些是机器人不关心的,它只是说我有走路的能力,至于怎么走需要主人对它发号施令了,如果我们要求机器人向南走5步,机器人才能完成行走的操作。同样对于操作系统来说,操作系统能够控制声卡发出声音,但是具体发出什么样的声音,是美妙的还是悲伤的就由应用程序来告诉操作系统的,应用程序怎样告诉操作系统呢,我们在编写C语言的时候通过调用函数来让操作系统干某事,操作系统能够执行各种各样的功能,它把这些功能都以函数的形式提供给应用程序,应用程序通过调用这些函数来通知操作系统该干什么事情,操作系统的各种各样的功能都对应相应的函数,这些函数的集合叫做API。如控制机器人的函数集合我们叫robot API,像JAVA里也有操作我们的系统的函数,我们把它叫做JAVA API,这里的API与WINDOWS API是没有关系的。

每个应用程序都有一个队列,叫消息队列,什么叫队列呢?其实就是一个缓冲区(就是定义的一个数组,数组有多大,缓冲区就有多大),然后你可以往里面存数,别人就可以从里面取数。先取的是你最先存入的数。就好象寻呼台的寻呼系统,来了一个寻呼信息,就有寻呼小姐将该信息交给发射机发布出去,如果在一秒钟一次来了很多个寻呼请求,发射机就有可能忙不过来,为了不至于把信息丢失,我们就准备一个很大的缓冲区,有100个格子,来一个寻呼请求就往这个缓冲区里放一个,然后由发射机从里面取,总是先取最先放进去的,一个往里面放,一个从里面取,虽然说可能会有些延迟,但我们保证了寻呼的请求没有丢失,这点也体现在我们的操作系统忙的时候我们执行了关闭窗口的命令,可能窗口当时没有立即关闭,但过了一会还是关闭了窗口,这也是队列的应用,队列就好比食堂里的排队买饭,排在前面的先买到饭,第一个买到饭后就离开,而第二个此时就成了第一个,后面的依次往前移动,这就是队列先进先出的特点。我们这里的消息队列存放的是消息,描述这个消息就要用结构体(MSG),因为一个消息要包含一些辅助说明消息的信息。下面讲解MSG,每一个消息都是跟窗口相关联的(也就是一个消息必须由一个窗口接收),这是因为消息都有目标的,这样消息才能正确的到达目标窗口,且该目标窗口此时应具有焦点,如启动两个记事本程序,当按下键盘的时候,消息只能到达具有焦点的窗口上。第二个参数是标识消息的整数,第三四参数是对消息进行补充说明的,如有键盘消息,你到底按下的是哪个键,这些信息都包含在这两个参数中。第五个参数是发出消息的时间,最后一个参数是鼠标所在的位置。用GO TO Definition查看LPARAM等类型的原型。
讲解句柄,我们学习WINDOWS程序就要接触句柄(HANDLE)了。其实句柄就是一个void*,我们可以用GO TO Definition来查看HANDLE,在WINDOWS里面任何一个WINDOWS实体(如窗口、位图、刷子、菜单等)都要在内存中占一块空间,这块空间就应该有一个地址,为了区别于以前普通变量的地址,我们用HANDLE来标识该WINDOWS实体,HANDLE保存的是该WINDOWS实体的地址,以便后面对该WINDOWS实体进行操作,如以后我们要哪个窗口最大化,我必须告诉操作系统我所要操作的窗口,那么我递给操作系统的是一个句柄。我们看到的HWND、HMENU、HBURSH其实都是HANDLE。
我们编的程序是从消息队列里取消息,消息被取出时是以一个MSG结构体提供给用户的,然后对消息进行处理。在消息处理的时候我们又调用了WINDOWS的API函数,如关闭窗口退出程序,当我们点了关闭按钮就收到了WM_CLOSE,然后我们在这个消息里调用了WINDOWS的API函数去销毁窗口,当操作系统真的把窗口销毁后会又报告一个消息回来等候你的应答-WM_DESTROY,这个时候我们又可以干点什么事,你可以真的退出了应用程序,也可以干点别的事,这个过程像一个封闭的环路,不断的从消息队列里取消息,然后你的代码又根据消息调用API函数去响应,这么一个循环的过程,所以有人形容WINDOWS程序无头无尾。我们编写WINDOWS程序首先要懂C语言,然后我们要知道每个消息具体代表什么含义,你不能收到一个键盘按下消息后当做鼠标移动处理,如别人说脚痛,你不能当头痛处理。最后我们收到某个消息想干某件事时,就要知道哪个API函数能实现该功能。但是API函数有三千多个,我们只要知道20%就够了,因为这20%的函数能干80%的事。

下面我们开始编写WINDOWS程序:
首先我们要产生一个窗口,有了这个窗口用户才能进行一系列操作。
如同生产汽车一样,产生汽车前我们要干一些准备工作,其实生产汽车是很容易的事,难就难在设计汽车,如果设计好了汽车以后就可以成批量的生产了。设计汽车就要指定汽车的颜色、汽车的发动机、底盘、变速箱等。同样产生窗口我们也要先设计,如窗口的背景是什么、窗口的鼠标指针是什么、窗口的图标是什么、窗口的菜单是什么。设计好了后我们才能根据设计图纸下料批量生产了。这里窗口的设计通过一个结构体来完成:WNDCLASS,第一个参数是STYLE,这个参数对应的二进制只有一位为1(十六进制的一位对应二进制的四位)。这有什么好处呢?比如说第一位是最大化按钮,第二位是最小化按钮,第三位是关闭按钮,如果我们希望窗口有最大化和最小化按钮,我们就把第一位和第二位设置为1,而第三位为0,如果我们又想有关闭按钮了,我们就把STYLE参数与一个代表关闭按钮的整数相或就可以了。

STYLE里有两个参数是CS_HREDRAW和CS_VREDRAW,当我们把窗口的大小发生改变时(最小化、最大化、覆盖一样),显卡要重新给我们把窗口画一遍(窗口是绘出来的),这时候原来在窗口上画的东西就被冲掉了,如果不这么做的话,我们的窗口的大小无法发生改变,这里我们指定两个参数就是指定当窗口水平和垂直方向发生大小改变时需不需要显卡重新画窗口,画窗口的时候显卡会用你定义的窗口的背景颜色进行重画。这就好比我们每到春节的时候要给汽车重新喷漆一样,如果我们不指定这两个参数就是说我不重新喷漆了,只用水冲一下就可以。以在窗口上画线为例进行演示。
 cbClsExtra 、cbWndExtra 是两个补充说明的参数,是当窗口的参数不够的时候用。

hIcon是图标的句柄,可以通过API函数LoadIcon加载,MAKEINTRESOURCE是把一个整数转成字符串的宏。注意字符串就是一个地址,标识一个字符数组常量的首地址。WORD是一个unsigned short, DWORD是一个unsigned long。如果需要使用一个自定义的图标,第一个参数就应该是hInstance,第二个参数是MAKEINTRESOURCE(IDI_ICON1)。在VC中编辑图标,并保存,#include “resource.h“  将script1.rc增加到工程。

hCursor是鼠标指针的句柄。可以通过API函数LoadCursor加载。cursor里的IDC_HAND是定义在win2000的头文件中的,要装win2000 SDK.

hbrBackground是窗口背景刷子的句柄,NULL表示没有刷子。通过API函数GetStockObject得到。注意这里要接触一个近指针和远指针的概念,这些是16位操作系统的产物,是用来标识指针的管理范围的,在32位的操作系统上已经没有意义了,我们可以发现它们都定义成空了。这里GetStockObject的返回值要进行类型转换。(设置一个任意的颜色?)
hInstance是应用程序实例的句柄。我的一个应用程序起动了,应用程序就装在内存中了,成为一个实例(因为应用程序可以启动多个,我们把它们每个应用程序用实例句柄来管理和区分),以后操作系统想控制我的应用程序,我必须把应用程序的句柄传给操作系统。这个应用程序的句柄我们把它叫做实例句柄。我们任何一个窗口都应该属于一个应用程序实例,所以我们在这里要指定应用程序的实例句柄。这个实例句柄是由操作系统分配给我们的。应用程序都有一个入口函数,在DOS里是MAIN函数,在WINDOWS里是WINMAIN函数,我们发现应用程序的实例句柄是操作系统在WINMAIN函数里传给我们的。hPrevInstance是先前的应用程序的实例句柄。通过这个参数我们可以控制一个应用程序只能启动一份。当然我们在这里由于是WIN32的系统无法简单实现只程序启动一份。(WinMain函数是操作系统调用的,所以WinMain的参数是操作系统赋值的。)
lpszMenuName注意菜单不是窗口。用SPY++可以查看。
lpszClassName是窗口类型的名字。如富康988。

 

设计完窗口后我们要注册窗口类RegisterClass,注册完了才能生产这一款窗口,否则操作系统不知道你要产生什么样的窗口。
 随后我们调用CreateWindow去产生窗口,这里有个窗口的样式参数,WS_OVERLAPPEDWINDOW是一个包装样式,如果想去掉某个功能就将去掉的样式取反,然后将结果与包装样式相与。WM_DISABLE是窗口不可用了。
如果我们没有ShowWindow则应用程序启动后看不见窗口,这时我们在代码里添加一个空格,然后删除空格,编译器发现代码发生改变会帮我们重新编译,这时报告一个fatal error,因为刚才的应用程序启动后没有关闭,新的就无法覆盖原来的,注意刚才没有窗口并不表示程序没有运行,窗口是窗口,程序是程序。窗口只是程序产生的一个小的部件。所以创建了窗口后我们要把内存中的窗口显示出来,不过如果在style属性里指定了ws_visible就不用ShowWindow了。书上经常有UpdateWindow,但我们这里可以不用。

有了窗口用户就可以干任何事情了,而我们就要对用户的这些操作进行处理,调用GetMessage来取消息。这里我们定义一个结构体:MSG来保存这些消息。GetMessage的第一个参数是一个MSG地址,如果不是地址的话无法返回值,我们就取不到消息。第二个参数是一个窗口的句柄,由于消息是跟一个窗口相关联的。如果我们把这个参数填为NULL,表示要取属于本应用程序的所有消息,包括该应用程序内所有窗口的消息。第三、四个参数是要取的消息的范围(消息是一个整数),这个参数可以帮助你过滤某些消息。我们处理完消息后又要来取消息又要处理,这样一个循环什么时候停止呢?当GetMessage取到WM_QUIT消息时就要会返回0,也只有这个消息能让GetMessage返回0,这样程序就能退出了。这里要注意的是:如果GetMessage的第二个参数填的是本窗口的句柄,则收不到WM_QUIT消息,这样程序就退不出去了,因为WM_QUIT是个特殊的消息,该消息不跟任何窗口相关联,属于应用程序的。所以我们第二个参数要填为NULL。
TranslateMessage是将WM_KEYDOWN和WM_KEYUP转换为WM_CHAR的函数,因为有时我们想对用户按下哪个键进行反应,所以我们只需要WM_CHAR来判断比较简单。当这三个消息同时存在的时候,先取WM_CHAR,再取WM_KEYDOWN,最后是WM_KEYUP。
DispatchMessage是分派、转发消息的函数。

至此我们的程序就差一个处理消息的函数了。lpfnWndProc需要填一个函数的指针,这个函数是回调函数:只要窗口收到消息了就去找这个规定好的回调函数去处理。就好象我们买了汽车后,在汽车的说明书上就指定了汽车的维修地点,当你的车坏了就去这些指定的地点去修。
介绍sprintf和MessageBox函数。
CALLBACK表示调用方式用Pascal(_stdcall如Delphi)的调用方式进行调用,否则就是按照C语言的调用方式(_cdecl)进行调用。这主要是解决一个程序通用的问题。
__stdcall与__cdecl是两种不同的函数调用习惯,定义了参数的传递顺序、堆栈清除等。关于它们的详细信息请参看msdn。由于除了那些可变参数的API函数外,其余的API函数都是__stdcall习惯。由于VC++程序默认的编译选项是__cdecl,所以在VC++中调用这些__stdcall习惯的API函数,必须在声明这些函数的原型时加上__stdcall修饰符,以便对该函数的调用使用__stdcall习惯。我们曾有这样的经验,在Delphi(默认的编译选项是__stdcall)中编写的dll中的函数,在VC++中被调用时,总是造成程序崩溃,在函数的原型声明中加上__stdcall修饰符,便解决了这个问题。回调函数也必须是__stdcall调用习惯,在这里是用CALLBACK来标识的,否则,在NT4.0环境,程序将崩溃,但win98和win2000却没有这种现象。
演示代码,包括响应画图和加载图标、默认的WINDOWS消息的处理。
在16位的系统中系统中只有一个消息队列,所以系统必须等待当前任务处理消息后才可以发送下一消息到相应程序,如果一个程序陷入死循环或是耗时操作时系统就会得不到控制权。这种多任务系统也就称为协同式的多任务系统。Windows3.X就是这种系统。

  而32位的系统中每一运行的程序都会有一个消息队列,所以系统可以在多个消息队列中转换而不必等待当前程序完成消息处理就可以得到控制权。这种多任务系统就称为抢先式的多任务系统。Windows95/NT就是这种系统

注意:不要用API Reference里的WinMain函数的定义,因为LPWSTR lpCmdLine里的W表示宽字节,即两个字节,本来是没问题的,但由于我们用的是盗版,这里就出问题了。

16位操作系统和32位操作系统,它们的根本区别就是,16位操作系统一次只读出两个格子
的数据,32位操作系统则可读出4个格子也就是4个字节。因为每个字节为8位二进制数,4
个字节就是32位,所以叫做32位操作系统。从WIN98开始,所有的WINDOWS操作系统都完全
是32位的了(windows95不是纯32位的操作系统,因为了兼有令人满意的速度和与旧的16位程序的良好兼容性,其内核本身就是一个16位与32位的混合体)。而且从386开始,所有的CPU都是32位,这就是说CPU一次处理4个字节的数据。因此,在程序里你最好尽量把变量定义为长整形,因为32位的变量不管寻址还是计算都是最快的。除非你在使用286计算机,或者DOS操作系统。

#include <windows.h>
#include "resource.h"
#include <stdio.h>
int x,y;
LRESULT CALLBACK myproc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
 switch(uMsg)
 {
 case WM_CLOSE:
  if(IDOK==MessageBox(hwnd,"你是真的要退出吗?","系统提示",MB_OKCANCEL|MB_ICONINFORMATION))
  {
   DestroyWindow(hwnd);
  }
  break;
 case WM_DESTROY:
  PostQuitMessage(0);
  break;
 default:
  return DefWindowProc(hwnd,uMsg,wParam,lParam);
 case WM_CHAR:
  {
  char buf[100];
  sprintf(buf,"%d",wParam);
  MessageBox(hwnd,buf,"show",MB_OK);
  break;
  }
 case WM_MOUSEMOVE:
  {//这里和上面的情况用大括号一是为了防止buf的重复定义,二是为解决类似int x=LOWORD(lParam);的定义编译器糊涂报错。
  HDC hdc=GetDC(hwnd);
  char buf[100];
  sprintf(buf,"x=%d,y=%d",x,y);  
  SetTextColor(hdc,RGB(255,255,255));
  TextOut(hdc,0,0,buf,strlen(buf));
  //InvalidateRect(hwnd,NULL,true);
  //PostMessage(hwnd,WM_PAINT,0,0);
  //SendMessage(hwnd,WM_PAINT,0,0);
  x=LOWORD(lParam);
  y=HIWORD(lParam);  
  memset(buf,0,100);
  sprintf(buf,"x=%d,y=%d",x,y);
  SetTextColor(hdc,RGB(0,0,0));
  TextOut(hdc,0,0,buf,strlen(buf));
  ReleaseDC(hwnd,hdc);
  break;
  }
 }
 return 1;
}
int WINAPI WinMain(
  HINSTANCE hInstance,      // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance
  LPSTR lpCmdLine,          // command line
  int nCmdShow              // show state
)
{
 WNDCLASS wndclass;
 wndclass.cbClsExtra=NULL;
 wndclass.cbWndExtra=NULL;

 wndclass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);
 
 //wndclass.hCursor=LoadCursor(NULL,IDC_HAND);
 wndclass.hCursor=LoadCursor(hInstance,MAKEINTRESOURCE(IDC_POINTER));

 //wndclass.hIcon=LoadIcon(NULL,IDI_ERROR);
 wndclass.hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(IDI_ICON1));

 wndclass.hInstance=hInstance;
 wndclass.lpfnWndProc=myproc;
 wndclass.lpszClassName="mywnd";
 wndclass.lpszMenuName=NULL;
 wndclass.style=CS_HREDRAW;
 RegisterClass(&wndclass);
 HWND hwnd=CreateWindow("mywnd","window test",WS_OVERLAPPEDWINDOW,100,100,400,400,NULL,NULL,hInstance,NULL);
 ShowWindow(hwnd,SW_SHOW);
 MSG msg;
 while(GetMessage(&msg,NULL,NULL,NULL))
 {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }
 return 1;
}

本文地址:http://com.8s8s.com/it/it24224.htm