Win32是指Microsoft Windows操作系统的32位环境,与Win64 都为Windows常见环境。如今的Win32操作系统可以一边听音乐,一边编程,一边打印文档。Win32操作系统是一个典型的多线程操作系统
Win32 是指 Microsoft Windows 操作系统的 32 位环境,与 Win64 都为 Windows 常见环境。如今的 Win32 操作系统可以一边听音乐,一边编程,一边打印文档。Win32 操作系统是一个典型的多线程操作系统
从单线程到多进程是操作系统发展的一种必然趋势,当年的 DOS 系统属于单任务操作系统,最优秀的程序员也只能通过驻留内存的方式实现所谓的”多任务”,而如今的 Win32 操作系统却可以一边听音乐,一边编程,一边打印文档。
理解多线程及其同步、互斥等通信方式是理解现代操作系统的关键一环,当我们精通了 Win32 多线程程序设计后,理解和学习其它操作系统的多任务控制也非常容易。许多程序员从来没有学习过嵌入式系统领域著名的操作系统 VxWorks,但是立马就能在上面做开发,大概要归功于平时在 Win32 多线程上下的功夫。
因此,学习 Win32 多线程不仅对理解 Win32 本身有重要意义,而且对学习和领会其它操作系统也有触类旁通的作用。
先阐述一下进程和线程的概念和区别,这是一个许多大学老师也讲不清楚的问题。
进程(Process)是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。程序只是一组指令的有序集合,它本身没有任何运行的含义,只是一个静态实体。而线程则不同,它是程序在某个数据集上的执行,是一个动态实体。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消,反映了一个程序在一定的数据集上运行的全部动态过程。
线程(Thread)是进程的一个实体,是 CPU 调度和分派的基本单位。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
进程间通信(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)。
Dos 汇编的特点
在 Dos 下编汇编程序,我们可以管理系统的所有资源,我们可以改动系统中所有的内存,如自己改动内存控制块来分配内存,自己修改中断向量表来截获中断等,对其他操作也是如此,如我们对键盘端口直接操作就可以把键盘屏蔽掉,可以这样来描述 Dos 系统:系统只有一个特权级别,在编程上讲,任何程序和操作系统都是同级的,所以在 Dos 下,一个编得不好的程序会影响其他所有的程序,如一个程序把键盘口中断关掉了,所有程序就都不能从键盘获得键入的数据,直到任何一个程序重新打开键盘为止,一个程序陷入死循环,也没有其他程序可以把它终止掉。Dos 下的编程思路是“单任务”的,你只要认为你的程序会按照你的流程一步步的执行下去,不必考虑先后问题(当然程序可能会被中断打断,但你可以认为它们会把环境恢复,如果中断程序没有把环境恢复,那是他们的错)。
内存管理方式上的不同
在内存管理方式上,Dos 汇编和 Win32 汇编也有很多的不同:Dos 工作在实模式下,我们可以寻址 1M 的内存,寻址时通过段寄存器来制定段的初始地址,每个段的大小为 64K,超过 1M 的部分,就只能把他作为 XMS 使用,也就是说,只能用作数据存放使用而无法在其中执行程序。
而 Windows 在保护模式下执行,这里所有的资源对应用程序来说都是被“保护”的:程序在执行中有级别之分,只有操作系统工作在最高级–0 级中,所有应用程序都工作在 3 级中(Ring3), 在 Ring3 中,你无法直接访问 IO 端口,无法访问其他程序运行的内存,连向程序自己的代码段写入数据都是非法的,会在 Windows 的屏幕上冒出一个熟悉的蓝屏幕来。只有对 Ring0 的程序来说,系统才是全开放的。
编译器
Win32ASM 的编译器最常用的有两种:Borland 公司的 Tasm5.0 和 Microsoft 的 Masm6.11 以上版本,两种编译器各有自己的优缺点,Tasm 带了一个不大不小的 Import 库,而 Masm 没有带,但 Masm 在代码的优化上面好象比 Tasm 做得好,但它却不带 Import 库。看来使用哪一种编译器还是比较难选择的,但 Steve Hutchesson 给了我们一个答案,他为 Masm 建立了一个很全的 Import 库,基本上包括了 Windows 绝大部分的 Api 函数,这些库、include 文件和其他工具还有 Masm6.14 版本一起做成了一个 Masm32 编译器 — Masm32V5。这样一来,我们用汇编编程就象用 C 一样方便。
因为有了 Masm32V5,所以就我个人而言,我推荐使用 Masm 作为 Win32ASM 的编译工具,但 Masm 和 Tasm 的宏语法有很多的不同,我的这个教程是以 Masm 格式写的。
在 Win32 编程中,由于 Windows 有很多的数据结构和定义,这些都放在 include 文件中,还有连接时要用到 Import 库(通俗的讲就是 Windows 提供的 DLL 文件中的函数列表,也就是告诉程序到哪里去调用 API 函数),这些都放在 include 和 lib 目录中。我们在编译时要指定以下的系统环境:
set include=Masm32v5Include
set lib=Masmv5lib
set path=Masmv5Bin
这样编译器就会到正确的路径中去找 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,程序会根据文件的时间自动判断。
概念
窗口是屏幕上的矩形区域。一个窗口可以从键盘或者鼠标接受用户的输入,并在其内部显示图形输出。一个应用程序窗口通常包含程序的标题条、菜单、边框,滚动条。其中,对话框也是一种窗口。不同的是,对话框表面通常包含几个其它窗口,称之为“子窗口”。这些子窗口的形式有压入按钮、单选按钮、复选框、文本输入区域、列表框和滚动条等。 用户将这些窗口看成屏幕上的对象,可以通过按下一个按钮或者滚动一个滚动条与这些对象直接交互。
通讯方式
窗口以“消息”的形式接收窗口的输入,窗口也用消息与其它窗口通讯。比如在程序窗口的大小改变时,字处理器会重新格式化其中的文本。窗口大小改变的细节是由操作系统处理的,但程序能够响应这个系统功能。当用户改变窗口的大小时,Windows 给程序发送一条消息指出新窗口的大小。然后,程序就可以调整窗口中的内容,以响应大小的变化。程序创建的每一个窗口都有相关的窗口过程。也就是给这个窗口指定一个子程序(窗口过程),Windows 通过调用它来给窗口发送消息。窗口过程再根据此消息进行处理,然后将控制返回给 Windows。
创建基础
窗口在“窗口类”的基础上创建的。Windows 定义了缺省的窗口过程,如果你对所有的消息都让 Windows 自己处理,那么你就能得到一个标准的窗口,同样,你也可以选择处理自己感兴趣的消息,这样,相当于产生了不同的子类,也就形成了不同的应用程序。同样,子窗口也是基于同一个窗口类,并且使用同一个窗口过程。例如,所有 Windows 程序中的所有按钮都基于同一窗口类。这个窗口类有一个处理所有按钮消息的窗口过程,但是,如果你按自己的设想设计一个按钮,如想把按钮的表面换成位图,你就可以自己处理按钮窗口的 WM_PAINT 消息,当 Windows 需要画按钮表面的时候,你就可以随自己的意思去画。
Windows 程序开始执行后,Windows 为该程序创建一个“消息队列”。这个消息队列用来存放该程序可能创建的各种不同窗口的消息。程序中有一段代码,叫做“消息循环”, 它用来从队列中取出消息,并且将它们发送给相应的窗口过程。在没有消息发生的时候,你的程序实际上就在消息循环中转圈子。