Windows的目的是使那些熟悉系统基本知识的人能够坐下来,不必进行任何预训练,就能实际运行任何应用程序。为实现此目的,Windows向用户提供了一些始终不变的接口。理论上来说,如果用户能运行起Windows,那么也就能运行那些基于那种接口的所有程序。而我们作为程序员的职责就是用Windows提供的这些接口来开发基于Windows的应用程序。Windows提供的这些接口,我们通俗的称为Win32 SDK API。
为许多操作系统编写程序时,是你的程序使其与操作系统发生作用。比如在DoS程序中,是程序在要求做诸如是输入和输出这样的工作。不同的是,用传统的方法编写的程序调用操作系统,操作系统却不调用你的程序。不过在大多数情况下,windows以相反的方式进行工作,既Windows会来调用你的程序。其过程为:Windows一直处于等待状态,直到由Windows发送一条消息,该消息通过由Windows调用的特殊函数传送到用户的程序中。只要程序接收到一条消息,它就会产生相应的动作,这便是Windows应用程序的的驱动方式,我称它为消息驱动模型。
Windows编程基础
入口函数WinMain:
以前学过C的人都晓得,C中的入口就是main()函数,任何程序开始,都由main进入开始执行,在Windows的编程中,也一样有一个入口函数,本函数与C的入口函数差不多,不过是WinMain,所有的Windows程序都由Winmain入口开始往下执行。另外,WinMain有一个调用约定,必须制定为WINAPI约定。和C的main返回一样,返回整形。Windows的标准语言是C,所以这个说明是针对于C版本的,那么我们Delphi程序员呢?如果有人看过Delphi的工程文件dpr里面的内容的话,就应该晓得,Delphi的入口是由dpr文件的begin end之间入口开始往下执行。这个我们可以看着为Delphi的编译器的一个魔法,它将WinMain函数在Delphi的内部给我们已经制定好了,编译的时候自动进入Winmain,然后再Winmain中进入begin end之间开始我们的窗口过程,所以我们可以将begin end这个看做为WinMain的一个子部分,那么就可以简单的理解为这个就相当于是一个WinMain了,同时也不用我们自己去声明一个WinMain这样的入口函数,而只需在工程文件的begin .. end之间写我们的代码就可。
窗口过程
首先 窗口过程是由Windows调用的,而不是由我们程序自己调用的函数。 所有的Windows程序必须包括一些特殊的函数,他们不由我们程序自己调用,而是由Windows操作系统来调用。这个函数通常被称为窗口过程或者窗口函数。当Windows需要向程序中传递一条消息时,Windows将调用窗口函数来处理这个消息。窗口函数在它的参数内接受消息。所有的窗口函数的返回类型为LRESULT,调用约定为CALLBACK。LRESULT实际上就是一个整形,在Delphi中可以用LongInt来表示。调用约定CallBack表示此过程属于系统回调函数,在Delphi中用Stdcall约定就行。实际上凡是关乎操作系统的相关函数都指定为Stdcall调用约定。一个窗口函数过程内部,我们通常能够看到一个很大的Case end这样的结构。用来标记对不同的消息做不同的处理。Windows的消息有很多很多个,很多时候,我们不必为我们关心的消息提供消息处理过程,此时我们就可以使用Windows的默认处理过程为DefWindowProc函数,用这个函数就可以按照系统的默认方式进行处理。
窗口类
这里说的窗口类,是指窗口的类型和样式,而不是我们面向对象中所说的那个类了,这个需要分解清楚。Windows的应用程序都是由一个个的窗口组成的。而要生成这些窗口,我们必须先注册一个窗口类给系统,这样以后创建窗口的时候,就会根据你所给定的样式等信息进行创建了。
消息循环
在前面说了Windows是消息驱动程序运行的,在每个应用程序中,都有一个自己的消息队列,从应用程序的消息队列中不断的取回消息构建了消息循环运转。所以所有的应用程序都必须在内部建立一个消息循环,此循环从应用程序的消息队列中读取任何未处理的消息,然后将它送还给Windows,这样以该消息作为参数的就会被对应的窗口过程调用以对该消息进行处理。
通过前面几点的了解,应该大致的晓得了一个Windows应用程序所具备的几个要素和步骤了:
1、声明入口函数(这个在Delphi中没有)
2、定义一个窗口类
3、注册窗口类
4、创建窗口
5、显示窗口
6、开始消息循环。
通过这6个步骤,就基本上能够实现一个Windows的视窗应用程序了。下面我来给一个样本程序
program Project1;
uses
WIndows,
messages;
//窗口过程
function WndProc(hwnd: THandle;MsgId: Longint;wParam: WParam;lParam: LParam): LRESULT;stdcall;
var
dc: HDC;
r: TRect;
str: string;
hi: Word;
PaintStruct: TPaintStruct;
begin
Case msgId of
WM_DESTROY:
begin
PostQuitMessage(0);{释放的时候,发送退出消息循环,程序结束}
result := 1;
exit;
end;
result := DefWindowProc(Hwnd,MsgId,WParam,LParam);
exit;
end;
end;
procedure WinMain(HthisInstance: LongInt);
var
msg: Tmsg;
MainHwnd: THandle;
WndClass: TWNdClassex;
begin
WndClass.cbSize := Sizeof(WNdClass);//指定结构大小
WndClass.hInstance := HThisInstance;//指定宿主为当前应用程序的实例
WndClass.lpszClassName := 'DxWindow';//指定类名
WndClass.lpfnWndProc := @WndProc;//指定窗口过程
WndClass.style := 0;//CS_VREDRAW or CS_HREDRAW;//指定样式为普通样式
WndClass.hIcon := LoadIcon(0,IDI_Application);//普通图标32*32大小的
WndClass.hIconSm := LoadIcon(0,IDI_WINLogo);//指定小图标16*16的
WndClass.hCursor := LoadCursor(0,IDC_Arrow);//指定光标
WndClass.lpszMenuName := nil;//指定菜单
WndClass.cbClsExtra := 0;
WndClass.cbWndExtra := 0;
WndClass.hbrBackground := CreateSolidBrush(RGB(236,233,216));//GetStockObject(White_Brush);
if RegisterClassex(WndClass) <> 0 then
begin
MainHwnd := CreateWindowEx(0,WndClass.lpszClassName,'测试窗口标题',WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0,hTHisInstance,nil);
if MainHwnd <> 0 then
begin
ShowWindow(MainHwnd,sw_ShowNormal);
UpdateWindow(MainHwnd);
while GetMessage(msg,0,0,0) do
begin //这里开始消息循环
TranslateMessage(msg);
DIspatchMessage(msg);
end;
ExitCOde := Msg.wParam;//退出
end;
end;
end;
begin
WinMain(Hinstance);
end.
本程序,就是一个最基本的Windows程序,本程序不包含任何功能,仅仅是显示一个窗口。现在来分解一下这个程序。由于Delphi中不必用WinMain,所以我自己在内部构建了一个WinMain函数,传递一个参数HthisInstance,本参数表示当前应用程序的实例,实际上Delphi运行启动程序的时候,这个实例已经通过Delphi内部包装的WinMain函数给反馈回来了,这个实例句柄,我们也可以通过GetModuleHandle(0)来获得。那么这个参数的目的有什么作用呢?还是需要将WinMain的原型拿过来分析说明一下!WinMain的原型为:
int WINAPI WinMain(Hinstance hThisInst,Hinstance hPrevInst,LPSTR lpszArgs,int nWinMode);
hThisInst和hPrevInst都是句柄类型,hThisInst指程序的当前实例,因为Windows是个多任务操作系统,一次可以同时运行相同程序的多个实例,所以用这个实例句柄来标记到底属于哪个。hPrevInst这个我们可以不同管他,在我们的系统中,他始终为nil,他存在的唯一理由就是与Win3.1时代的程序兼容,意思是指前一个程序实例。lpszArgs指定为命令行参数,就想ping 127.0.0.1这个里面的127.0.0.1这样的就是参数,对应着Delphi的paramstr(1)等。nWinMode参数保存的值决定如何显示窗口。
定义窗口类。我这里用的是TWndClassEx,在Windows中声明如下
tagWNDCLASSEXA = packed record
cbSize: UINT; //指定本类结构体大小
style: UINT; //指定为窗口样式
lpfnWndProc: TFNWndProc; //指定窗口过程
cbClsExtra: Integer; //附加的信息
cbWndExtra: Integer;
hInstance: HINST;//所属的应用程序实例
hIcon: HICON; //图标32*32,大图标
hCursor: HCURSOR; //光标
hbrBackground: HBRUSH; //背景画布对象句柄
lpszMenuName: PAnsiChar;//菜单资源名
lpszClassName: PAnsiChar;//类名
hIconSm: HICON;//小图标16*16
end;
每个Windows应用程序都有两个与其相关的图标,一个是标准尺寸(32*32),另一个是小图标,当应用程序被最小化时,使用小图标。当应用程序快捷方式,以及在硬盘中显示时,显示标准图标。这里我通过了
LoadIcon这个函数来加载一个图标资源。原型为
HICON LoadIcon(Hinsance hinst,LPCSTR IconResName);
本函数将返回一个图标句柄。hInst指定包含图标的实例模块,我这里指定的是0,0表示调用系统的,如果指定为我们本应用程序的实例的话,这个返回是会失败的哦,因为我们应用程序内部并不包含对应的图标资源。第二个参数指定为图标资源名。系统默认的有
IDI_APPLICATION 缺省图标
IDI_ERROR 错误符号
IDI_INFORMATION 信息
IDI_QUESTION 问号
IDI_WARNING 感叹号
IDI_WINLOGO 窗口标志
读取鼠标光标,用LoadCursor,用法与LoadIcon差不多,一些系统的默认光标样式
IDC_ARROW 缺省箭头指针
IDC_CROSS 十字线
IDC_IBEAM 垂直工字型
IDC_WAIT 沙漏
窗口的背景,通过使用API函数来获得背景画刷的句柄。我这里用的是CreateSolidBrush,目的是创建一个画刷的GDI对象,CreateSolidBrush中的参数指定为画刷的颜色。另外,还有一种方式就是通过GetStockObject函数来获得一个系统内部的画刷对象。GetStockObject函数用于获得一些标准显示对象的句柄,包括画刷,画笔和字符字体等。原型为
HGDIOBJ GetStockObject(int Object);
汗水返回object所指定的对象的句柄(HGDIOBJ表示一个GDI句柄)
一些系统的内置画刷:
BLACK_BRUSH 黑色
DKGRAY_BRUSH 黑灰
HOLLOW_BRUSH
LIGRAY_BRUSH 浅灰
WHITE_BRUSH 白色
比如,可以尝试,将我上面的CreateSolidBrush换成GetStockObject(WHITE_BRUSH)这样的看一下效果。
一旦,当窗口类型,指定好了之后,我们就可以通过使用RegisterClassEx来注册一个窗口类了。
ATOM RegisterClassEx(wndclassex: TWNDCLASSEX)
ATOM表示一个原子类型,表示全系统唯一。
创建窗口
通过上面的步骤,注册成功了之后,就可以依据注册的窗口类来创建一个窗口了,创建窗口我用了CreateWindowEX,
function CreateWindowEx(dwExStyle: DWORD; lpClassName: PChar;
lpWindowName: PChar; dwStyle: DWORD; X, Y, nWidth, nHeight: Integer;
hWndParent: HWND; hMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND;
参数一指定了窗口的扩展样式,我这里啥扩展都没,所以指定为0,参数二指定为类名,这个名字必须要和我们注册的窗口类的名字一样,所以直接指定为WndClass.lpszClassName,参数三指定为窗口标题,参数4指定为窗口的样式,我这里指定的是层叠样式窗口,其常见类型为:
WS_ORERLAPPED 边框重叠窗口
WS_MAXIMIZEBOX 最大化
WS_MINIMIZEBOX 最小化
WS_SYSMENU 系统菜单
WS_HSCROLL 水平滚动
WS_VSCROLL 垂直滚动
尔后的参数就是指定了窗口的位置,宽度和高度,我这里都是指定了CW_USEDEFAULT采用了系统默认的。如果窗口创建成功,将会返回创建的窗口句柄,否则返回0。
创建了窗口之后,窗体并不会被显示,要显示窗口,我们需要嗲用ShowWindow函数,本函数参数一指定要显示的窗口句柄,参数2指定为显示方式,显示方式有下面几种形式:
SW_HIDE 隐藏
SW_MINIMIZE 最小化
SW_MAXIMIZE 最大化
SW_RESTORE 恢复为正常大小
ShowWindows函数,返回窗口的前一个显示状态,如果显示了窗口则返回非0值,如果没显示,则返回0
最后,调用了UpdateWindow函数,本函数的目的是告诉Windows向用户的应用程序发送一条消息,该消息是需要更新一下主窗口。
最后便是消息循环了。消息循环是所有Windows应用程序的组成部分,它的作用就是接受并处理Windows发送的消息。运行程序时,它不断的被系统发送消息。直到所有这些消息都被读取以及处理。否则就一直保存在应用程序的消息队列里。每次应用程序准备好去读取另一个消息时。就必须调用GetMessage。原型为
BOOL GETMessage(msg: tagMsg;hwnd: THandle;min,max: UINT);
msg指定消息结构体,这个在windows单元中有定义,在本结构体中有包含hwnd窗口句柄,用来标石采用哪个窗口过程处理。也包含有消息id,同时表示采用窗口过程的那个消息处理方式处理。其中的WPARAM和lparam是消息的附加信息。time指定了消息的发送时间,以毫秒为单位指定,pt包含鼠标的坐标。在应用程的消息队列中如果没有一条消息,则GetMessage调用会向Windows回传一个控制命令(这个以后再说)。
GetMessage的hwnd参数将指定所获得的消息传给哪个窗口。一个应用程序可能有很多个窗口,而用户也许只想接收某个具体窗口的消息,如果想接收指向所有应用程序的所有消息,则指定为0。其他两个参数指定了要接收的消息的范围,通常希望接收所有的消息,所以一般都指定为0.当用户程序结束时,getmssage返回0,此时消息循环接收。否则返回非0,如果发生错误,则返回-1(至于错误如何处理,请大家思考一下)。然后消息循环段内,有两个函数
TranslateMessage和DispatchMessage。这两个函数,第一个函数是将系统产生的虚拟键代码转成字符消息。可能并不是所有程序都需要调用它,但是打部分程序还是需要处理按键信息的。一旦读取并转换了消息,通过DispatchMessage就将消息再派遣回Windows,于是Windows保存该消息,直到能将其传递给程序的窗口uhanshu为止。一旦消息循环结束,WinMain也就结束,程序也随之结束。
最后就是窗口函数,我在上面定义的窗口函数为WndProc,可见我在这个函数中仅仅处理了一个唯一的消息就是窗口释放的时候,发送了一个PostQuitMessage的处理。该函数将发送一个WM_QUIT的消息给应用程序,然后GetMessage获得WM_QUIT的时候,就会为False,从而退出循环结束消息。其他的消息处理,我全部都是调用的默认处理过程DefWindowProc来对消息进行处理。
到现在为止一个基本的Delphi SDK编程框架就完成了。