文档库 最新最全的文档下载
当前位置:文档库 › Win32

Win32

百度首页 | 登录

新闻网页贴吧知道MP3图片视频百科文库

w i n32进入词条搜索词条

帮助设置

?

?

?

?

?

?

?

?

?

?

?

?

?

?世博

win32

目录

从单进程单线程到多进程多线程是操作系统发展的一种必然趋势,当年的DOS 系统属于单任务操作系统,最优秀的程序员也只能通过驻留内存的方式实现所谓的"多任务",而如今的Win32操作系统却可以一边听音乐,一边编程,一边打印文档。

理解多线程及其同步、互斥等通信方式是理解现代操作系统的关键一环,当我们精通了Win32多线程程序设计后,理解和学习其它操作系统的多任务控制也非常容易。许多程序员从来没有学习过嵌入式系统领域著名的操作系统VxWorks,但是立马就能在上面做开发,大概要归功于平时在Win32多线程上下的功夫。

因此,学习Win32多线程不仅对理解Win32本身有重要意义,而且对学习和领会其它操作系统也有触类旁通的作用。

编辑本段

进程与线程

先阐述一下进程和线程的概念和区别,这是一个许多大学老师也讲不清楚的问题。

概念

进程(Process)是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。程序只是一组指令的有序集合,它本身没有任何运行的含义,只是一个静态实体。而进程则不同,它是程序在某个数据集上的执行,是一个动态实体。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消,反映了一个程序在一定的数据集上运行的全部动态过程。

线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

二者关系

线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。

操作系统分类

根据进程与线程的设置,操作系统大致分为如下类型:

(1)单进程、单线程,MS-DOS大致是这种操作系统;

(2)多进程、单线程,多数UNIX(及类UNIX的LINUX)是这种操作系统;

(3)多进程、多线程,Win32(Windows NT/2000/XP等)、Solaris 2.x和O S/2都是这种操作系统;

(4)单进程、多线程,VxWorks是这种操作系统。

引入线程的好处

在操作系统中引入线程带来的主要好处是:

(1)在进程内创建、终止线程比创建、终止进程要快;

(2)同一进程内的线程间切换比进程间的切换要快,尤其是用户级线程间的切换。另外,线程的出现还因为以下几个原因:

(1)并发程序的并发执行,在多处理环境下更为有效。一个并发程序可以建立一个进程,而这个并发程序中的若干并发程序段就可以分别建立若干线程,使这些线程在不同的处理机上执行。

(2)每个进程具有独立的地址空间,而该进程内的所有线程共享该地址空间。这样可以解决父子进程模型中,子进程必须复制父进程地址空间的问题。

(3)线程对解决客户/服务器模型非常有效。

进程间通信(IPC)

Win32进程间通信的方式主要有:

(1)剪贴板(Clip Board);

(2)动态数据交换(Dynamic Data Exchange);

(3)部件对象模型(Component Object Model);

(4)文件映射(File Mapping);

(5)邮件槽(Mail Slots);

(6)管道(Pipes);

(7)Win32套接字(Socket);

(8)远程过程调用(Remote Procedure Call);

(9)WM_COPYDATA消息(WM_COPYDATA Message)。

获取进程信息

在WIN32中,可使用在PSAPI .DLL中提供的Process status Helper函数帮助我们获取进程信息。

(1)EnumProcesses()函数可以获取进程的ID,其原型为:

BOOL EnumProcesses(DWORD * lpidProcess, DWORD cb, DWORD*cbNeeded);

参数lpidProcess:一个足够大的DWORD类型的数组,用于存放进程的ID值;

参数cb:存放进程ID值的数组的最大长度,是一个DWORD类型的数据;

参数cbNeeded:指向一个DWORD类型数据的指针,用于返回进程的数目;

函数返回值:如果调用成功,返回TRUE,同时将所有进程的ID值存放在lpidP rocess参数所指向的数组中,进程个数存放在cbNeeded参数所指向的变量中;如果调用失败,返回FALSE。

新打开键盘为止,一个程序陷入死循环,也没有其他程序可以把它终止掉。Dos下的编程思路是“单任务”的,你只要认为你的程序会按照你的流程一步步的执行下去,不必考虑先后问题(当然程序可能会被中断打断,但你可以认为它们会把环境恢复,如果中断程序没有把环境恢复,那是他们的错)。

内存管理方式上的不同

在内存管理方式上,Dos汇编和Win32汇编也有很多的不同:Dos工作在实模式下,我们可以寻址1M的内存,寻址时通过段寄存器来制定段的初始地址,每个段的大小为64K,超过1M的部分,就只能把他作为XMS使用,也就是说,只能用作数据存放使用而无法在其中执行程序。

而Windows在保护模式下执行,这里所有的资源对应用程序来说都是被“保护”的:程序在执行中有级别之分,只有操作系统工作在最高级--0级中,所有应用程序都工作在3级中(Ring3),在Ring3中,你无法直接访问IO端口,无法访问其他程序运行的内存,连向程序自己的代码段写入数据都是非法的,会在Windows的屏幕上冒出一个熟悉的蓝屏幕来。只有对Ring0的程序来说,系统才是全开放的。

内存的不同

在内存方面,Windows使用了处理器的分页机制,使得对应用程序来说,所有的内存都是“平坦”的,你不必用一个段寄存器去指定段的地址,因为在保护模式下,段寄存器的含义是不同的(可以参见80386手册方面的书籍),你可以直接指定一个32位的地址来寻址4GB的内存。

程序结构方面的不同

在程序结构方面,Windows程序也有很大的不同,它是“基于消息”的,你可以想象这样一个常见的Windows窗口,上面有几个按钮,如果你用Dos编程的思路去考虑,你会发现实现它很困难:鼠标移动到窗口边缘时拖动会改变窗口大小,鼠标点击按钮时再做要做的事,你会发现,你的程序自开始执行后就在等待,你不知道鼠标先会点什么地方,实际上你是在等待所有可能的事情的发生。而在Dos下,你可以只顾自己先执行,需要用户输入时,再停下来,你不输入我就不再执行,而且,我让你输入数据A你就不能输入数据B。

好了,言归正传,因为以上是Win32编程的基础,无论对Win32汇编还是VC+ +,它们都是一样的,下面我们来看看有关Win32汇编的内容。

编辑本段

Win32ASM编译器

却不带Import库。看来使用哪一种编译器还是比较难选择的,但Steve Hutchesson 给了我们一个答案,他为Masm建立了一个很全的Import库,基本上包括了Windo ws绝大部分的Api函数,这些库、include文件和其他工具还有Masm6.14版本一起做成了一个Masm32编译器-- Masm32V5。这样一来,我们用汇编编程就象用C 一样方便。

因为有了Masm32V5,所以就我个人而言,我推荐使用Masm作为Win32ASM 的编译工具,但Masm和Tasm的宏语法有很多的不同,我的这个教程是以Masm 格式写的。

编辑本段

Masm32的环境设置

在Win32编程中,由于Windows有很多的数据结构和定义,这些都放在includ e文件中,还有连接时要用到Import库(通俗的讲就是Windows提供的DLL文件中的函数列表,也就是告诉程序到哪里去调用API函数),这些都放在include 和lib 目录中。我们在编译时要指定以下的系统环境:

set include=\Masm32v5\Include

set lib=\Masmv5\lib

set path=\Masmv5\Bin

这样编译器就会到正确的路径中去找include 文件和lib 文件。你可以自己在autoexec.bat 文件中加上以上语句,为了产生Windows的PE格式的执行文件,在编译和连接中要指定相应的参数:

编译:Ml /c /coff 文件名.asm

连接:Link /SUBSYSTEM:WINDOWS OBJ文件名.obj 资源文件名.res

为了不在每次编译时都要打这么多的参数,我们可以用nmake 文件来代为执行,nmake 是代码维护程序,他会检查.asm .obj .exe .res 等文件的时间,如果你更新了源程序,他会自动执行编译程序或连接程序产生相应的文件。你可以在文件名为makefile 的文件中指定使用的编译器和连接程序以及相应的参数,下面是一个makefile 文件的例子:

NAME = Clock

OBJS = $(NAME).obj

RES = $(NAME).res

$(NAME).exe: $(OBJS) $(RES)

Link /DEBUG /SUBSYSTEM:WINDOWS $(OBJS) $(RES)

$(RES): $(NAME).rc

Rc $(NAME).rc

.asm.obj:

Ml /c /coff $(NAME).asm

文件告诉nmake程序,程序名为clock,产生 clock.exe 文件需要clock.obj

和clock.res 文件,而产生clock.res 文件需要clock.rc 文件,产生clock.obj 文件要用到clock.asm 文件,至于是否需要执行ml, link 和rc,程序会根据文件的时间自动判断。

编辑本段

Win32ASM程序的结构和语法

让我们先来看看一个最简单的Win32汇编程序:

.386

.model flat, stdcall

option casemap :none ; case sensitive

include windows.inc

include kernel32.inc

includelib kernel32.lib

.data

szCaption db 'Win32汇编例子',0

szText db 'Win32汇编,Simple and powerful!',0

.code

start:

invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK

invoke ExitProcess,NULL

end start

这就是一个能执行的最简单的Win32汇编程序,下面我简单地介绍一下各部分的作用:

386

这条语句和Dos下汇编是一样的,是告诉编译器我们要用到80386的指令集,因为32位汇编程序要用到32位的寄存器如eax,ebx等,所以这一句是必须的,当然,你也可以用.486,.586等,当用到特权指令时,还可以用.386p,.486p等等。

model flat stdcall

.model告诉编译器程序的模式,编过Dos汇编的人可能知道在Dos程序的模式有tiny,small,...huge 等,它指定了程序内存寻址模式,在huge等模式下,内存寻址和子程序调用将用Far的格式,但在Win32汇编中,你只能使用一个模式即flat 模式,因为对Win32程序来说,内存是连续的一个4GB的段,无所谓小或大的模式。而stdcall 告诉编译器参数的传递方式,在调用子程序时,参数是通过堆栈传递的,参数的传递方式有三种,stdcall,c 和pascal,stdcall 指定了参数是从右到左压入堆栈的,比如说对一个Windows API 如MessageBox,在手册中是如此定义的:

int MessageBox(

HWND hWnd, // handle of owner window

LPCTSTR lpText, // address of text in message box

LPCTSTR lpCaption, // address of title of message box

UINT uType // style of message box

);

那么在汇编中我们就可以这样调用它:

push uType

push lpCaption

push lpText

push hWnd

call MessageBox

大家要注意最右面的参数是最后一个进堆栈的,当然,我们不必这样麻烦的调用一个API,因为Masm中的一个宏语句不但帮助我们完成了所有的压栈操作,还帮我们检查参数的个数是否正确,那就是invoke 语句,我们可以把上面的语句换成i nvoke MessageBox,hWnd,lpText,lpCaption,uType 就行了。如本程序中代入实际参数就成了invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK。

include 语句

include 语句包含了一些系统的定义和API函说明,其中所有的Windows 数据结构定义和常量定义包含在windows.inc 中,而其他API函数的说明包含在xxx.i nc 中,如查Microsoft Win32 Programmer's Reference 知道ExitProcess包含在kernel32.dll 中,那么我们就要在程序中包括include kernel32.inc 和includeli b kernel32.lib语句,否则在编译时会出现API 函数未定义的错误。而MessageBo x 在user32.dll 中,那么我们就要在程序中包括include user32.inc 和includelib user32.lib语句

data 或data

指明了接下来是数据段,.data 定义了预定义的变量,.data?定义了未初始化的变量,两者的不同之处是.data? 定义的变量并不占用.exe 文件的大小,而是在程序执行时动态分配,所以开始是不指定初始值的数据可以放在.data? 段中,如一个1K大小的缓冲区,放在.data?中,程序将不会增加一个字节。

code

指明了接下来是代码段,我们的所有代码都放在这里。最后的一句start 语句指定了程序开始执行的语句。程序中的ExitProcess 是一个标准的Win32 API,对应Dos汇编中的int 20h 或mov ah,4ch/int 21h,也就是程序退出。而MessageB ox 也是一个标准的API,功能是在屏幕上显示一个消息框,具体的参数上面已经解

释过了还有要注意的是invoke MessageBox,NULL,addr szText,addr szCaption,M B_OK 语句中,MB_OK 和NULL 已经预定义在Windows.inc 中。

Windows 的资源文件

不管在Dos下编程还是在Windows下编程,我们总是要用到除了可执行文件外的很多其他数据,如声音数据,图形数据,文本等等,在Dos下编程,我们可以自己定义这些文件的格式,但这样一来就造成了很多资源共享的问题,大家可能还记的Dos下的很多游戏,它们的图形都是按自己的格式存放的,你无法用标准的看图软件来看。也无法把它另存为其他格式。虽然在Win32编程中,我们仍然可以这样做,但Win32编程给了我们一个方案---- 就是格式统一的资源文件,把字符串、图形、对话框包括上面的按钮,文本等定义到一个资源文件中,就可以方便的在不同的文件中使用它,最重要的是,如果我们用自己的文件格式,使用时就要涉及到这些文件的读写操作,比较复杂,但使用资源文件时,Windows提供了一系列的API来装入资源。非常方便。现在,让我们来看一个很简单的资源文件的源文件,它的扩展名是. rc,当它用资源编译器编译以后产生.res 文件就可以在link的时候连入.exe 文件中:

#include

#define DLG_MAIN 1

DLG_MAIN DIALOGEX 0, 0, 236, 185

STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "对话框模板"

FONT 9, "宋体"

BEGIN

DEFPUSHBUTTON "退出",IDOK,177,163,50,14

CONTROL "",-1,"Static",SS_ETCHEDHORZ,7,155,222,1

END

现在我简单解释一下.rc文件的语法:

#include -- resource.h文件包括资源文件的一些常量定义,如下面的WS_POPUP,WS_VISIBLE 等窗口的风格等等

#define DLG_MAIN 1 -- 类似于.asm 文件的equ 语句,和汇编源程序一样,这些定义是为了程序的可读性。

DLG_MAIN DIALOGEX 0,0,236,185

Windows的.rc文件可以定义BITMAP(位图),CURSOR(光标),ICON(图标),ACC ELERATORS(加速键),DIALOG(对话框),MENU(菜单),STRINGTABLE(字符串表),RC DATA(自定义资源)等8种资源,详细的描述可以参考有关MFC的书籍,在Win32A SM中的资源编译器的语法中,一般格式是这些资源的定义方法是:

位图定义:nameID BITMAP [load-mem] filename

光标定义:nameID CURSOR [load-mem] filename

图标定义:nameID ICON [load-mem] filename

加速键定义:

acctablename ACCELERATORS [optional-statements]

BEGIN event, idvalue, [type] [options]

. . .

END

等等,具体的定义和参数可以参考Masm32v5 中的Rc.hlp 帮助文件。(可以在编程工具中下载),我们可以用资源编辑器来所见即所得地编辑资源,也可以在文本编辑器中用上面这些语句自己定义资源。

在程序中使用资源

在程序中,要使用资源之前必须先装如内存,Windows定义了一系列的API来装入资源,如LoadMenu,LoadString,LoadBitmap 等等,如LoadBitmap 的定义:

HBITMAP LoadBitmap(

HINSTANCE hInstance, // handle of application instance

LPCTSTR lpBitmapName // address of bitmap resource name

);

这些Load函数的返回值是一个句柄,调用参数中一般至少为两项:hInstance 和ResouceName,这个ResouceName(如BitmapName,MenuName)就是在资源文件中的#define 指定的值,如果你用#define MY_ICON 10/ MY_ICON ICON "M ain.ico" 定义了一个图标,那么在程序中要使用Main.ico 图标就可以用LoadIcon(h Instance,10) 来装入已经定义为10号的图标文件。另一个参数hInstance 是执行文件的句柄,它对应资源所在的文件名,你可以在程序开始执行时用invoke GetMod uleHandle,NULL 获得hInstance。另外一些资源并不是显式地装入的,如对话框资源,它是在建立对话框的函数中由Windows自己装入的,如下面例子中的invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0 ,是在屏幕上显示一个资源文件中已经定义好了的对话框,就并不存在LoadDialogBox 之类的A PI来先装入对话框。

编辑本段

显示一个对话框

源程序

介绍了这么多相关的东西,现在让我们来看看如何显示一个对话框,源程序如下:.386

.model flat, stdcall

option casemap :none ; case sensitive

include windows.inc

include user32.inc

include kernel32.inc

include comctl32.inc

include comdlg32.inc

includelib user32.lib

includelib kernel32.lib

includelib comctl32.lib

includelib comdlg32.lib

DLG_MAIN equ 1

.data?

hInstance dd ?

szBuffer db 256 dup (?)

_ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data

.code

;********************************************************************

_ProcDlgMain proc uses ebx edi esi, \

hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD mov eax,wMsg

.if eax == WM_CLOSE

invoke EndDialog,hWnd,NULL

.elseif eax == WM_INITDIALOG

.elseif eax == WM_COMMAND

mov eax,wParam

.if eax == IDOK

invoke EndDialog,hWnd,NULL

.elseif eax == IDCANCEL

invoke EndDialog,hWnd,NULL

.endif

.else

mov eax,FALSE

ret

.endif

mov eax,TRUE

ret

_ProcDlgMain endp

;********************************************************************

start:

invoke InitCommonControls

invoke GetModuleHandle,NULL

mov hInstance,eax

invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0 invoke ExitProcess,NULL

end start

解析

_ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

PROTO 语句类似于C语言中的函数定义,在Win32汇编中,如果子程序的定义在引用以后,你就必须先定义,当然,这个定义是针对invoke 语句和其他带参数的调用的,如果你的子程序没有参数,你就可以用call 指令去调用它而不是用宏指令invoke,这时候你就不必声明这个函数。

_ProcDlgMain proc uses ebx edi esi, \

hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD 这个定义proc 的语句应该是不陌生的,要重复讲解一下的是uses 和下面的参数,uses 下的寄存器表示要编译器自动插入保存及恢复这些寄存器的指令,\ 是在Masm32 中接下一行的符号,表示下一行是本行的继续内容,以避免一行中的内容过长。下面的hWnd:DWORD 等语句定义了调用这个子程序的参数,如果有以下定义MyProc proc dwPara1:DWORD,dwPara2:DWORD,dwPara3:DWORD,然后你用invoke MyProc 1,2,3 来调用它,那么,1,2,3 将分别被赋值给dwPara1,d wPara2,dwPara3,你可以在子程序中使用这些传递过来的参数。如果参数的类型是双字,那么:DWORD 可以省略。

.if/.else/.elseif/.endif

这些语句是宏指令,实际上不说你也知道它们的意思,有了这些宏指令,我们就可以把汇编编得象C一样结构清晰,而不必老是看到jmp 指令了,当然,这只不过编译器帮你做了这些事情而已,如果你去反汇编一下,你开始会看到一大堆jmp 指令,.if 的格式如下

.if eax == 1 如果eax等于1

.if eax != 1 如果eax不等于1

.if eax != 1 && ebx != 2 如果eax不等于1且ebx不等于2

.if eax == 1 || ebx == 2 如果eax等于1或者ebx等于2

其他的宏指令还有.while/.endw .break 等等,可以参考Masm32V5 的帮助文件Masm32.hlp

最后要讲到的就是DialogBoxParam 这个API了,在Windows中,所有的窗口都要指定一个子程序,当Windows检测到鼠标、定时器等和这个窗口有关的动作时,它回调用这个子程序,这就是Windows基于消息的体系的最基本的概念,换句话说,在Dos下,我们通过INT指令调用系统,而在Windows 下,有很多时候是你指定子

程序地址让Windows来调用你。invoke DialogBoxParam,hInstance,DLG_MAIN, NULL,offset _ProcDlgMain,0中的offset _ProcDlgMain 就指定了如果有消息发生,Windows就来执行这个子程序,参数中的DLG_MAIN 就是在资源文件中定义的对话框模板编号。hInstance 是对话框所在的资源文件的句柄。

另外,在_ProcDlgMain 子程序中,Windows传给我们4个参数hWnd,wMsg,w Param,lParam,其中,hWnd是对话框的窗口句柄,wMsg表示现在发生的消息事件,如这个对话框初始化时Windows会以WM_INITDIALOG为消息调用,关闭时为WM _CLOSE,按下对话框上的按钮时为WM_COMMAND等,wParam和lParam是附加的参数,对应不同的消息对应不同定义,具体可以参考Win32 Programmer's ref erence。

编辑本段

有关窗口的基本知识

概念

窗口是屏幕上的矩形区域。一个窗口可以从键盘或者鼠标接受用户的输入,并在其内部显示图形输出。一个应用程序窗口通常包含程序的标题条、菜单、边框,滚动条。其中,对话框也是一种窗口。不同的是,对话框表面通常包含几个其它窗口,称之为“子窗口”。这些子窗口的形式有压入按钮、单选按钮、复选框、文本输入区域、列表框和滚动条等。用户将这些窗口看成屏幕上的对象,可以通过按下一个按钮或者滚动一个滚动条与这些对象直接交互。

通讯方式

窗口以“消息”的形式接收窗口的输入,窗口也用消息与其它窗口通讯。比如在程序窗口的大小改变时,字处理器会重新格式化其中的文本。窗口大小改变的细节是由操作系统处理的,但程序能够响应这个系统功能。当用户改变窗口的大小时,Windo ws给程序发送一条消息指出新窗口的大小。然后,程序就可以调整窗口中的内容,以响应大小的变化。程序创建的每一个窗口都有相关的窗口过程。也就是给这个窗口指定一个子程序(窗口过程),Windows通过调用它来给窗口发送消息。窗口过程再根据此消息进行处理,然后将控制返回给Windows。

创建基础

窗口在“窗口类”的基础上创建的。Windows定义了确省的窗口过程,如果你对所有的消息都让Windows自己处理,那么你就能得到一个标准的窗口,同样,你也可以选择处理自己感兴趣的消息,这样,相当于产生了不同的子类,也就形成了不同的应用程序。同样,子窗口也是基于同一个窗口类,并且使用同一个窗口过程。例如,所有Windows 程序中的所有按钮都基于同一窗口类。这个窗口类有一个处理所有按钮消息的窗口过程,但是,如果你按自己的设想设计一个按钮,如想把按钮的表面换

成位图,你就可以自己处理按钮窗口的WM_PAINT 消息,当Windows 需要画按钮表面的时候,你就可以随自己的意思去画。

Windows程序开始执行后,Windows为该程序创建一个“消息队列”。这个消息队列用来存放该程序可能创建的各种不同窗口的消息。程序中有一段代码,叫做“消息循环”,它用来从队列中取出消息,并且将它们发送给相应的窗口过程。在没有消息发生的时候,你的程序实际上就在消息循环中转圈子。

编辑本段

创建一个窗口

过程

创建一个窗口的过程如下:

取得程序的实例句柄(hInstance)

注册窗口类,实际上就是为你的窗口指定处理消息的过程,定义光标,窗口风格,颜色等参数

创建窗口

显示窗口

然后进入消息循环,也就是不停地检测有无消息,并把它发送给窗口进程去处理。

编程要点

创建一个窗口的代码在不同的程序中实际上是几乎一模一样的,所以你编一个新的程序时可以把这一段拷来拷去,稍微修改一下就行,程序的大部分代码实际上是用在窗口过程中,因为这才是不同程序的不同之处。窗口过程的编程要点如下:从Windows传给窗口过程的参数uMsg 得到消息类型,并转到不同的分枝去处理。

对自己已经处理的消息,返回Windows 时必须在eax 中返回0。

自己不处理的消息,必须调用DefWindowProc 处理,并把返回值传回Window s,否则,Windows会无法显示。

uMsg 参数中指定的消息有280多种,实际上我们需要处理的只有重要的几种,如Windows在创建的时候会发送WM_CREATE 消息,我们就可以在这时候初始化,分配内存等等,而退出时会发送WM_CLOSE,我们就可以进行释放内存等清除工作,当Windows上的菜单或按钮被按下时发送WM_COMMAND 消息等等,具体可以参考Win32 Programmer's Reference。下面,我们来看一个创建窗口的简单程序。

一个创建窗口的程序

.386

.model flat, stdcall

option casemap :none ; case sensitive

include windows.inc

include user32.inc

include kernel32.inc

include comctl32.inc

include comdlg32.inc

include gdi32.inc

includelib user32.lib

includelib kernel32.lib

includelib comctl32.lib

includelib comdlg32.lib

includelib gdi32.lib

IDI_MAIN equ 1000 ;icon

IDM_MAIN equ 4000 ;menu

IDM_EXIT equ 4001

.data?

hInstance dd ?

hWinMain dd ?

hMenu dd ?

szBuffer db 256 dup (?)

.data

szClassName db "Windows Template",0

szCaptionMain db '窗口模板',0

.code

start:

call _WinMain

invoke ExitProcess,NULL

_WinMain proc

local @stWcMain:WNDCLASSEX

local @stMsg:MSG

invoke InitCommonControls

invoke GetModuleHandle,NULL

mov hInstance,eax

invoke LoadIcon,hInstance,IDI_MAIN

mov hIcon,eax

invoke LoadMenu,hInstance,IDM_MAIN

mov hMenu,eax

;*************** 注册窗口类***************************************** invoke LoadCursor,0,IDC_ARROW

mov @stWcMain.hCursor,eax

mov @stWcMain.cbSize,sizeof WNDCLASSEX

mov @stWcMain.hIconSm,0

mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW

mov @stWcMain.lpfnWndProc,offset WndMainProc

mov @stWcMain.cbClsExtra,0

mov @stWcMain.cbWndExtra,0

mov eax,hInstance

mov @stWcMain.hInstance,eax

mov @stWcMain.hIcon,0

mov @stWcMain.hbrBackground,COLOR_WINDOW + 1

mov @stWcMain.lpszClassName,offset szClassName

mov @stWcMain.lpszMenuName,0

invoke RegisterClassEx,addr @stWcMain

;*************** 建立输出窗口***************************************

invoke CreateWindowEx,WS_EX_CLIENTEDGE,\

offset szClassName,offset szCaptionMain,\

WS_OVERLAPPEDWINDOW OR WS_VSCROLL OR WS_HSCROLL,\

0,0,550,300,\

NULL,hMenu,hInstance,NULL

invoke ShowWindow,hWinMain,SW_SHOWNORMAL

invoke UpdateWindow,hWinMain

;*************** 消息循环*******************************************

.while TRUE

invoke GetMessage,addr @stMsg,NULL,0,0

.break .if eax == 0

invoke TranslateMessage,addr @stMsg

invoke DispatchMessage,addr @stMsg

.endw

ret

_WinMain endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> >>>>>>>

WndMainProc proc uses ebx edi esi, \

hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

mov eax,uMsg

.if eax == WM_CREATE

mov eax,hWnd

mov hWinMain,eax

call _Init

;********************************************************************

.elseif eax == WM_COMMAND

.if lParam == 0

mov eax,wParam

.if ax == IDM_EXIT

call _Quit

.endif

.endif

;********************************************************************

.elseif eax == WM_CLOSE

call _Quit

;********************************************************************

.else

invoke DefWindowProc,hWnd,uMsg,wParam,lParam

ret

.endif

xor eax,eax

ret

WndMainProc endp

_Init proc

invoke SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon

ret

_Init endp

;********************************************************************

_Quit proc

invoke DestroyWindow,hWinMain

invoke PostQuitMessage,NULL

ret

_Quit endp

;********************************************************************

end start

窗口程序的分析

让我们来简单分析一下这个程序,首先程序调用_WinMain,在_WinMain 中定义了两个局部变量@stMsg 和@stWinMain,数据类型分别是MSG 和WNDCLA SSEX结构,在参考手册中,可以看到WNDCLASSEX定义了一个窗口的所有参数,如使用的菜单、光标、颜色、窗口过程等,接下来的一大堆mov 指令实际上就是在

填写这个数据结构,填写完成后,最重要的两句是mov @stWcMain.lpfnWndProc, offset WndMainProc 定义了处理消息的窗口过程,mov @stWcMain.lpszClassNa me,offset szClassName 定义了你要创建的类的名称,然后就是使用RegisterClas sEx 注册这个窗口类,注意,这时候窗口并没有创建,你只不过是定义好了一个子类,接下去你要用你定义的类去创建一个窗口。也就是使用CreateWindowEx 函数去创建它。在手册中,CreateWindowEx 是这样定义的:

HWND CreateWindowEx(

DWORD dwExStyle, // extended window style

LPCTSTR lpClassName, // pointer to registered class name

LPCTSTR lpWindowName, // pointer to window name

DWORD dwStyle, // window style

int x, // horizontal position of window

int y, // vertical position of window

int nWidth, // window width

int nHeight, // window height

HWND hWndParent, // handle to parent or owner window

HMENU hMenu, // handle to menu, or child-window identifier

HINSTANCE hInstance, // handle to application instance

LPVOID lpParam // pointer to window-creation data );

其中的参数dwExStyle 是窗口的风格,lpClassName 就是我们自己定义的类的名字。如果大家要创建一个已经定义好的类,如RichEdit 类等等,只要把lpClass Name 指向"RichEdit32" 字符串就行了,当然这时就不用RegisterClass 以及编写自己的窗口过程了。执行CreateWindowEx 后,得到一个返回值就是窗口句柄,这个值在以后是要经常用到了,所以要先保存下来。这时窗口并没有在屏幕上显示出来,而是处于隐藏状态,我们要用ShowWindow 来显示出窗口并用UpdateWindow 来绘窗口的内容。

窗口显示出来后,程序就进入一个循环----消息循环,前面我已经说过,作用是不停地接收Windows 消息并发送给窗口过程去处理。GetMessage 从消息队列中取出一条消息,如果取得的消息不是WM_QUIT,那么GetMessage 返回一个非零值,否则返回零,这时候循环结束,程序执行ExitProcess退回操作系统。TranslateMe ssage 将消息进行一些键盘转换,用于处理一些快捷键,DispatchMessage 将处理后的消息发回Windows,由Windows调用窗口进程进行处理,当窗口进程处理完返回后,程序才从DispatchMessage 返回,从而开始下一个GetMessage 调用。这些函的参数可以参考手册。

窗口过程的分析

窗口过程有四个参数,hWnd 是本窗口的句柄,和创建窗口时返回的值相同,u Msg 是本次调用的消息类型,wParam 和lParam是消息的参数,其含义和数值根据消息的不同而不同。在本程序中,我们处理WM_CREATE,WM_COMMAND 和W

M_QUIT 消息,然后返回0,对不处理的消息,使用invoke DefWindowProc,hWnd, uMsg,wParam,lParam 来处理并直接用ret 将返回值传回Windows。在响应WM_ CLOSE 消息时,我们用DestroyWindow 清除窗口并用PostQuitMessage 产生一条WM_QUIT 消息,从而使程序在消息循环调用GetMessage 时返回0,以结束消息循环并结束程序。

编辑本段

常见应用

如何隐藏/显示任务栏

shell db "Shell_TrayWnd",0 ; 任务栏的类名

invoke FindWindow,addr shell,NULL ; 先获得句柄,之后隐藏.

.if eax != 0

invoke ShowWindow,eax,SW_HIDE ; 用SW_SHOW显示

.endif

--------------------------------------------------------------------------------

- 如何禁止/允许/显示/隐藏开始按钮?

.data?

buffer db 127 dup(?)

.data

shell db "Shell_TrayWnd",0

sbar db "BUTTON",0

child dd ?

slen dd ?

.code

invoke FindWindow,addr shell,NULL ; 获得状态栏句柄

mov tray, eax

invoke GetWindow,tray, GW_CHILD ; 获得状态栏的子窗口(如果有的话)

mov child, eax

.if child != 0

invoke GetClassName,child,offset buffer, sizeof buffer ;获得子窗口类名

.if eax > 0

invoke lstrlen, offset buffer ;获得类名长度

mov slen,eax

invoke CharUpperBuff,offset buffer,slen ;转为大写

invoke lstrcmp,addr buffer, addr sbar ;将类名与'BUTTON'比较

.if eax == 0

invoke ShowWindow,child,SW_HIDE ; 隐藏开始按钮

相关文档