《windows多线程编程.docx》由会员分享,可在线阅读,更多相关《windows多线程编程.docx(26页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Windows多线程编程总结关键字:多线程 线程同步 线程池 内核对象1 内核对象1 .1内核对象的概念内核对象是内核分配的一个内存块,这种内存块是一个数据结构,表示内核对象的各种特征。并且只能由内核来访问。应用程序若需要访问内核对象,需要通过操作系统提供的函数来进行,不能直接访问内核对象(Windows从安全性方面来考虑的)。内核对象通过Create*来创建,返回一个用于标识内核对象的句柄,这些句柄(而不是内核对象)可在创建进程范围内使用,不能够被传递到其他进程中被使用。1 .2内核对象使用的计数因为内核对象的所有者是内核,而不是进程,所以何时撤销内核对象由内核决定,而内核做这个决定的依据就
2、是该内核对象是否仍然被使用。那么如何判断内核对象是否被使用呢?可以通过内核对象的“使用计数”属性,一旦这个值变成0了,内核就可以释放该对象了。1 .3创建内核对象1 .3.1进程与句柄表每个进程在初始化的时候,将被分配一个句柄表,该句柄表中只存储内核对象的句柄,不存储用户对象的句柄。句柄表的详细结构微软没有公布,但是大致包含三个内容:内核对象句柄,内核对象地址,访问屏蔽标志。微软为何要将内核对象的句柄设置为进程相关的呢?理由有:l 不同的进程对内核对象的访问权限是不同的,有必要区分对待l 如果句柄是全局的,则一个进程可以控制另外一个进程的句柄,破坏另外一个进程的句柄。 1 .3.2创建内核对象
3、及操作系统内部机制利用CreateSomeObject的函数来创建内核对象。调用该函数的时候内核就为该对象分配一个内存块,并进行初始化,然后内核再扫描该进程的句柄表,初始化一条记录并放在句柄表中。1 .3.3进程中使用内核对象的内部机制假设函数F使用某个内核对象,其参数为Handle1,则该函数内部需要查找该进程的句柄表,找出参数句柄对应的记录,然后才能使用该内核对象。1 .4关闭内核对象无论进程怎样创建内核对象,在不使用该对象的时候都应当通过Bool CloseHandle(HANDLE hobj)来向操作系统声明结束对该对象的访问。为什么叫声明呢?是因为此时也许还有其他进程对该对象的访问,
4、操作系统可能并不立即释放该对象。操作系统需要做的是:从进程的句柄表中删除该内核对象的记录,另外再考察该内核对象的使用计数以决定是否需要释放该对象。1 .5内核对象的共享说到共享,与之孪生的就是共享权限。Windows内核对象的共享有三种方式:1 .5.1继承式共享(父子进程间)只有当进程是父子关系的时候,才能使用此种方式的共享。特别要注意的是继承的是内核对象的句柄,内核对象本身是不具备继承性。要达到这种继承的效果需要做以下几件事:l 在进程创建内核对象的时候,需要一个安全结构sa(SECURITY_ATTRIBUTES类型,以向OS声明对象的访问方式)作为参数。继承式共享需要将结构的成员sa.
5、bInheritHandle设置为TRUE。此时OS内部的处理式将进程的句柄表中的该对象的访问屏蔽字段设置成“可继承”。l 在创建子进程(CreateProcess函数)时,设置创建参数bInheritHandles为TRUE。表示被创建的子进程可以继承父进程中的所有可继承内核对象。OS内部的处理是:复制父进程句柄表中的记录到子进程的句柄表中,并使用相同的句柄值;为内核对象的使用计数器加1。特别说明:子进程能够继承的的内核对象仅局限于父进程创建它的时候所拥有的可继承内核对象。子进程诞生后,父进程再搞出什么可继承的东西,子进程是不能用的。这就需要在子进程中使用继承的内核对象的时候需要慎重,以确定
6、内核对象是否已被继承了。利用SetHandleinformation方法可以随时修改内核对象句柄的一些属性,目前公开的句柄属性有两种,一种是该句柄是否能被继承,另一种是该句柄是否能被关闭。1 .5.2同名共享同名共享,不需要共享进程之间存在父子关系。但局限于内核对象是否支持这种共享方式。创建内核对象的Create函数中是否包含pszName是该内核对象是否支持同名共享的标志。l 方法一:当Process1通过CreateObject(”someName”)创建了一个名字为someName的内核对象后,Process2也调用了CreateObject(”someName”),此时内核的动作是:在
7、全局中查询发现已经存在someName1的对象;为Process2的句柄表添加一条Ojbect的记录,使用的句柄不确定;为someName这个Object的引用计数器加1。l 方法二:Process2使用OpenObject(”someName”)的方式来获得对名someName的Object的句柄。用这种Open方法的时候,需要提供一个参数让OS鉴权,以判定是否能够以参数指定的方式来访问内核对象。1 .5.3复制内核对象的句柄的方式共享跨进程边界的内核对象共享的另外一个方法是通过DuplicateHandle来复制内核对象句柄。如果要将ProcessS中的对象拷贝到ProcessT中则调用D
8、uplicateHandle的进程一定要有对这两个进程的访问权,即句柄表中拥有这两个进程内核对象的句柄记录。2 线程的一般概念2 .1视图l 进程只是线程的容器,从来不执行任何东西l 线程总是在某个进程中被创建l 线程在进程的地址空间中执行代码l 线程们共享进程中的所有内核对象3 线程的创建HANDLE CreateThread( PSECURITY_ATTRIBUTES psa, DWORD cbStack, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadID);Wind
9、ows核心编程P124介绍说应当使用编译器提供的线程创建函数,而不应当直接使用CreateThread。3 .1CreateThread调用的内核行为调用CreateThread后,OS进行如下几个动作:l 生成一个线程内核对象l 在进程空间内为线程分配堆栈空间因为线程的环境同于其所在进程的环境,所以创建的线程可以访问进程中的所有资源,包括线程中所有的内核对象。4 线程销亡4 .1终止线程的方式:l 线程函数返回(最好使用这个方式,可以保证:线程种创建的C+对象正常析构;OS释放线程堆栈内存;OS将线程的退出码设置为线程函数的返回值;系统将递减该线程内核对象的的使用计数器【如果此时还有其他引用
10、,见下面说明】。)l 调用ExitThread(不能释放C+对象,所以最好不要使用这个方式。另外,如果非要调用也应当调用编译器推荐的,如_endThread【Windows核心编程P127】)l 同进程内的其他线程(包括主线程)调用TerminateThread(被撤销线程得不到通知,不能释放资源,尽量避免这种方式。另外这个函数是个异步函数,返回时,线程不保证已经被撤销,如果要观察线程是否被撤销,应当使用WaitForSingleObject)l 包含线程的进程终止(应当避免这种方式)4 .2线程退出时OS的行为l 线程内的所有用户对象被释放。l 线程的退出码从STILL_ACTIVE改为传递
11、给ExitThread或TerminateThread的代码l 线程内核对象的状态改为“已通知”l 如果线程为进程中的最后一个线程,则OS将进程当作已终止运行l 线程内核对象的引用计数器减1(一旦线程终止了,其他引用改线程内核对象将不能够处理改线程的句柄,但是可以通过调用GetExitcodeThread来检查hThread代表的线程是否已经终止运行了。)5 线程同步5 .1线程同步的起因以及解决之道5 .1.1共用资源型:多个线程需要访问同一个资源的时候,为了保证资源不被破坏,需要线程对资源的访问具有原子性。5 .1.2依赖型:一个线程等待另外一个线程某件事情完成后才能执行_可以通过手动事件
12、的方式互相通知。5 .2线程同步种类细分同步起因 同步种类 同步方法备注 共用资源 多个线程对共用变量做加减操作 互锁函数族之:InterlockedExchangeAdd 共用资源 多个线程对公共变量、指针做赋值操作 互锁函数族之:InterlockedExchange ,InterlockedExchangepoint 共用资源 多个线程需要根据对公共变量、指针的判断做操作选择 互锁函数族之:InterlockedCompareExchange InterlockedCompareExchangePointer 共用资源 复杂数据结构(非单值),不适合互锁函数族处理的 用“关键代码”的方式
13、,关键代码中要注意1、要尽量的快速处理完,以防止其他等待线程等待太长时间 2、线程等待过程中由用户模式切换到内核模式,耗费1000个CPU周期,时间比较长。3、只能对单个进程中的线程进行同步)InitializeCriticalSection;EnterCriticalSection;LeaveCriticalSection;DeleteCriticalSection; 处理线程同步的一种方法 对线程同步做的一个抽象,线程的同步本质上都是依赖于某个其他事件的发生,用软件的方法来对所依赖的事件做一个抽象,将有助与程序编写的简捷 CreateEventEvent的重要属性有一个是“自动”or“手动
14、”,如果是自动的,则在某个线程用Wait成功等待到事件的“通知”状态后,则事件状态立刻变成“未通知”状态,以保证同时对资源访问的线程只有一个。 原则上不算线程同步范畴,而属于对wait*的一种应用方式 一个可以作为定时器的内核对象,Waitable Timer CreateWaitableTimerSetWaitableTimer,CancelWaitableTimer 共用资源 一组线程对一组同样性质的资源的争用,则这组资源需要有所表示,以告知线程们是否有空闲的给以为他们服务,以信号量机制实现 CreateSemaphore,ReleaseSemaphore 共用资源 一组线程对一个单一的资
15、源的争用,需要有一种机制保证同一个事件只有一个线程能得到资源。以Mutex方式实现 CreateMutexReleaseMutex与关键代码的差别在于:1、 允许不同进程的线程之间同步2、 内核对象,用户模式和内核模式切换的时候需要更多的CPU开销 特别说明:WaitForSingleObject/WaitForMultipleObject是抑制线程本身的一种手法,配合以共用资源对象或所依赖的其他对象“通知状态”的原子性变化,以达到线程在争用资源、互相依赖时执行的顺序化,从而达到同步的目的。综上:其实Windows的线程同步机制是提供了一组不同情况下的资源争用处理办法而已。与此同时推出的Wai
16、t却可以带来很多其他好处,甚至部分缓解C+语言没有事件机制的缺憾,部分达到了JAVA,C#中事件机制的效果,为Oberserve模式的实现做了些贡献。Windows多线程编程日期:2006-07-17 来源: 作者: 字体:大 中 小 Windows多线程编程总结关键字:多线程 线程同步 线程池 内核对象1 内核对象1 .1内核对象的概念内核对象是内核分配的一个内存块,这种内存块是一个数据结构,表示内核对象的各种特征。并且只能由内核来访问。应用程序若需要访问内核对象,需要通过操作系统提供的函数来进行,不能直接访问内核对象(Windows从安全性方面来考虑的)。内核对象通过Create*来创建,
17、返回一个用于标识内核对象的句柄,这些句柄(而不是内核对象)可在创建进程范围内使用,不能够被传递到其他进程中被使用。1 .2内核对象使用的计数因为内核对象的所有者是内核,而不是进程,所以何时撤销内核对象由内核决定,而内核做这个决定的依据就是该内核对象是否仍然被使用。那么如何判断内核对象是否被使用呢?可以通过内核对象的“使用计数”属性,一旦这个值变成0了,内核就可以释放该对象了。1 .3创建内核对象1 .3.1进程与句柄表每个进程在初始化的时候,将被分配一个句柄表,该句柄表中只存储内核对象的句柄,不存储用户对象的句柄。句柄表的详细结构微软没有公布,但是大致包含三个内容:内核对象句柄,内核对象地址,
18、访问屏蔽标志。微软为何要将内核对象的句柄设置为进程相关的呢?理由有:l 不同的进程对内核对象的访问权限是不同的,有必要区分对待l 如果句柄是全局的,则一个进程可以控制另外一个进程的句柄,破坏另外一个进程的句柄。 1 .3.2创建内核对象及操作系统内部机制利用CreateSomeObject的函数来创建内核对象。调用该函数的时候内核就为该对象分配一个内存块,并进行初始化,然后内核再扫描该进程的句柄表,初始化一条记录并放在句柄表中。1 .3.3进程中使用内核对象的内部机制假设函数F使用某个内核对象,其参数为Handle1,则该函数内部需要查找该进程的句柄表,找出参数句柄对应的记录,然后才能使用该内
19、核对象。1 .4关闭内核对象无论进程怎样创建内核对象,在不使用该对象的时候都应当通过Bool CloseHandle(HANDLE hobj)来向操作系统声明结束对该对象的访问。为什么叫声明呢?是因为此时也许还有其他进程对该对象的访问,操作系统可能并不立即释放该对象。操作系统需要做的是:从进程的句柄表中删除该内核对象的记录,另外再考察该内核对象的使用计数以决定是否需要释放该对象。1 .5内核对象的共享说到共享,与之孪生的就是共享权限。Windows内核对象的共享有三种方式:1 .5.1继承式共享(父子进程间)只有当进程是父子关系的时候,才能使用此种方式的共享。特别要注意的是继承的是内核对象的句
20、柄,内核对象本身是不具备继承性。要达到这种继承的效果需要做以下几件事:l 在进程创建内核对象的时候,需要一个安全结构sa(SECURITY_ATTRIBUTES类型,以向OS声明对象的访问方式)作为参数。继承式共享需要将结构的成员sa.bInheritHandle设置为TRUE。此时OS内部的处理式将进程的句柄表中的该对象的访问屏蔽字段设置成“可继承”。l 在创建子进程(CreateProcess函数)时,设置创建参数bInheritHandles为TRUE。表示被创建的子进程可以继承父进程中的所有可继承内核对象。OS内部的处理是:复制父进程句柄表中的记录到子进程的句柄表中,并使用相同的句柄值
21、;为内核对象的使用计数器加1。特别说明:子进程能够继承的的内核对象仅局限于父进程创建它的时候所拥有的可继承内核对象。子进程诞生后,父进程再搞出什么可继承的东西,子进程是不能用的。这就需要在子进程中使用继承的内核对象的时候需要慎重,以确定内核对象是否已被继承了。利用SetHandleinformation方法可以随时修改内核对象句柄的一些属性,目前公开的句柄属性有两种,一种是该句柄是否能被继承,另一种是该句柄是否能被关闭。1 .5.2同名共享同名共享,不需要共享进程之间存在父子关系。但局限于内核对象是否支持这种共享方式。创建内核对象的Create函数中是否包含pszName是该内核对象是否支持同
22、名共享的标志。l 方法一:当Process1通过CreateObject(”someName”)创建了一个名字为someName的内核对象后,Process2也调用了CreateObject(”someName”),此时内核的动作是:在全局中查询发现已经存在someName1的对象;为Process2的句柄表添加一条Ojbect的记录,使用的句柄不确定;为someName这个Object的引用计数器加1。l 方法二:Process2使用OpenObject(”someName”)的方式来获得对名someName的Object的句柄。用这种Open方法的时候,需要提供一个参数让OS鉴权,以判定是
23、否能够以参数指定的方式来访问内核对象。1 .5.3复制内核对象的句柄的方式共享跨进程边界的内核对象共享的另外一个方法是通过DuplicateHandle来复制内核对象句柄。如果要将ProcessS中的对象拷贝到ProcessT中则调用DuplicateHandle的进程一定要有对这两个进程的访问权,即句柄表中拥有这两个进程内核对象的句柄记录。2 线程的一般概念2 .1视图l 进程只是线程的容器,从来不执行任何东西l 线程总是在某个进程中被创建l 线程在进程的地址空间中执行代码l 线程们共享进程中的所有内核对象3 线程的创建HANDLE CreateThread( PSECURITY_ATTRI
24、BUTES psa, DWORD cbStack, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadID);Windows核心编程P124介绍说应当使用编译器提供的线程创建函数,而不应当直接使用CreateThread。3 .1CreateThread调用的内核行为调用CreateThread后,OS进行如下几个动作:l 生成一个线程内核对象l 在进程空间内为线程分配堆栈空间因为线程的环境同于其所在进程的环境,所以创建的线程可以访问进程中的所有资源,包括线程中所有的内核对象。
25、4 线程销亡4 .1终止线程的方式:l 线程函数返回(最好使用这个方式,可以保证:线程种创建的C+对象正常析构;OS释放线程堆栈内存;OS将线程的退出码设置为线程函数的返回值;系统将递减该线程内核对象的的使用计数器【如果此时还有其他引用,见下面说明】。)l 调用ExitThread(不能释放C+对象,所以最好不要使用这个方式。另外,如果非要调用也应当调用编译器推荐的,如_endThread【Windows核心编程P127】)l 同进程内的其他线程(包括主线程)调用TerminateThread(被撤销线程得不到通知,不能释放资源,尽量避免这种方式。另外这个函数是个异步函数,返回时,线程不保证已
26、经被撤销,如果要观察线程是否被撤销,应当使用WaitForSingleObject)l 包含线程的进程终止(应当避免这种方式)4 .2线程退出时OS的行为l 线程内的所有用户对象被释放。l 线程的退出码从STILL_ACTIVE改为传递给ExitThread或TerminateThread的代码l 线程内核对象的状态改为“已通知”l 如果线程为进程中的最后一个线程,则OS将进程当作已终止运行l 线程内核对象的引用计数器减1(一旦线程终止了,其他引用改线程内核对象将不能够处理改线程的句柄,但是可以通过调用GetExitcodeThread来检查hThread代表的线程是否已经终止运行了。)5 线
27、程同步5 .1线程同步的起因以及解决之道5 .1.1共用资源型:多个线程需要访问同一个资源的时候,为了保证资源不被破坏,需要线程对资源的访问具有原子性。5 .1.2依赖型:一个线程等待另外一个线程某件事情完成后才能执行_可以通过手动事件的方式互相通知。5 .2线程同步种类细分同步起因同步种类同步方法备注共用资源多个线程对共用变量做加减操作互锁函数族之:interlockedexchangeadd共用资源多个线程对公共变量、指针做赋值操作互锁函数族之:interlockedexchange ,interlockedexchangepoint共用资源多个线程需要根据对公共变量、指针的判断做操作选择
28、互锁函数族之:interlockedcompareexchange interlockedcompareexchangepointer共用资源复杂数据结构(非单值),不适合互锁函数族处理的用“关键代码”的方式,关键代码中要注意1、要尽量的快速处理完,以防止其他等待线程等待太长时间 2、线程等待过程中由用户模式切换到内核模式,耗费1000个cpu周期,时间比较长。3、只能对单个进程中的线程进行同步)initializecriticalsection;entercriticalsection;leavecriticalsection;deletecriticalsection;处理线程同步的一种方
29、法对线程同步做的一个抽象,线程的同步本质上都是依赖于某个其他事件的发生,用软件的方法来对所依赖的事件做一个抽象,将有助与程序编写的简捷createeventevent的重要属性有一个是“自动”or“手动”,如果是自动的,则在某个线程用wait成功等待到事件的“通知”状态后,则事件状态立刻变成“未通知”状态,以保证同时对资源访问的线程只有一个。原则上不算线程同步范畴,而属于对wait*的一种应用方式一个可以作为定时器的内核对象,waitable timercreatewaitabletimersetwaitabletimer,cancelwaitabletimer共用资源一组线程对一组同样性质的
30、资源的争用,则这组资源需要有所表示,以告知线程们是否有空闲的给以为他们服务,以信号量机制实现createsemaphore,releasesemaphore共用资源一组线程对一个单一的资源的争用,需要有一种机制保证同一个事件只有一个线程能得到资源。以mutex方式实现createmutexreleasemutex与关键代码的差别在于:1、 允许不同进程的线程之间同步2、 内核对象,用户模式和内核模式切换的时候需要更多的cpu开销特别说明:WaitForSingleObject/WaitForMultipleObject是抑制线程本身的一种手法,配合以共用资源对象或所依赖的其他对象“通知状态”的
31、原子性变化,以达到线程在争用资源、互相依赖时执行的顺序化,从而达到同步的目的。综上:其实Windows的线程同步机制是提供了一组不同情况下的资源争用处理办法而已。与此同时推出的Wait却可以带来很多其他好处,甚至部分缓解C+语言没有事件机制的缺憾,部分达到了JAVA,C#中事件机制的效果,为Oberserve模式的实现做了些贡献。一般情况下多线程编程多采用MFC类库实现,那么如果不使用MFC 如何进行多线程程序设计呢?本文将就这个问题进行讨论:微软在Windows API中提供了建立新的线程的函数CreateThread,它的语法如下:hThread = CreateThread (&secu
32、rity_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ; 第一个参数是指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL。第二个参数是用于新线程的初始堆栈大小,默认值为0。在任何情况下,Windows根据需要动态延长堆栈的大小。CreateThread的第三个参数是指向线程函数的指标。函数名称没有限制,但是必须以下列形式声明:DWORD WINAPI ThreadProc (PVOID pParam) ; CreateThr
33、ead的第四个参数为传递给ThreadProc的参数。这样主线程和从属线程就可以共享数据。CreateThread的第五个参数通常为0,但当建立的线程不马上执行时为旗标CREATE_SUSPENDED。线程将暂停直到呼叫ResumeThread来恢复线程的执行为止。第六个参数是一个指标,指向接受执行绪ID值的变量。大多数Windows程序写作者喜欢用在PROCESS.H表头文件中声明的C执行时期链接库函数_beginthread。它的语法如下:hThread = _beginthread (ThreadProc, uiStackSize, pParam) ; 它更简单,对于大多数应用程序很完美
34、,这个线程函数的语法为:void _cdecl ThreadProc (void * pParam) ; 在建立多线程的Windows程序时,需要在Project Settings对话框中做一些修改。选择C/C+页面标签,然后在Category下拉式清单方块中选择Code Generation。在Use Run-Time Library下拉式清单方块中,可以看到用于Release设定的Single-Threaded和用于Debug设定的Debug Single-Threaded。将这些分别改为Multithreaded和Debug Multithreaded。这将把编译器旗标改为/MT,它是编
35、译器在编译多线程的应用程序所需要的。第一个demo./* deom1-四个线程同时写一个文件( 没有参数 ) */i nclude i nclude /* _beginthread, _endthread */i nclude i nclude using namespace std;ofstream out(out.txt);void ThreadFunc1(PVOID param) while(1)Sleep(10);outThis was draw by thread lendl;void ThreadFunc2(PVOID param) while(1)Sleep(10);outThi
36、s was draw by thread 2endl;void ThreadFunc3(PVOID param) while(1)Sleep(10);outThis was draw by thread 3endl;void ThreadFunc4(PVOID param) while(1)Sleep(10);outThis was draw by thread 4endl;int main()int i=0;_beginthread(ThreadFunc1,0,NULL);_beginthread(ThreadFunc2,0,NULL);_beginthread(ThreadFunc3,0,
37、NULL);_beginthread(ThreadFunc4,0,NULL);Sleep(3000);outend;return 0;/demo1 end-第二个demo./* deom2-四个线程同时写一个文件( 有参数 ) */i nclude i nclude /* _beginthread, _endthread */i nclude i nclude i nclude using namespace std;ofstream out(out.txt);void ThreadFunc1(PVOID param) while(1)char *p;p=(char *) param;Slee
38、p(10);outpThis was draw by thread lendl;void ThreadFunc2(PVOID param) while(1)Sleep(10);outThis was draw by thread 2endl;void ThreadFunc3(PVOID param) while(1)Sleep(10);outThis was draw by thread 3endl;void ThreadFunc4(PVOID param) while(1)Sleep(10);outThis was draw by thread 4endl;int main()char *p
39、str= 参数传递成功;_beginthread(ThreadFunc1,0,pstr);_beginthread(ThreadFunc2,0,NULL);_beginthread(ThreadFunc3,0,NULL);_beginthread(ThreadFunc4,0,NULL);Sleep(1000);outend;return 0;/ demo2 end -第三个demo( 一个win32 应用程序 ) /* deom3- 在屏幕上随机画出一系列矩形*/i nclude i nclude LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LP
40、ARAM) ;HWND hwnd ;int cxClient, cyClient ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)static TCHAR szAppName = TEXT (RndRctMT) ; MSG msg ;WNDCLASS wndclass ;wndclass.style = CS_HREDRAW | CS_VREDRAW ;wndclass.lpfnWndProc = WndProc ;wndclass.cbClsExtra
41、 = 0 ;wndclass.cbWndExtra = 0 ;wndclass.hInstance = hInstance ;wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;wndclass.lpszMenuName = NULL ;wndclass.lpszClassName = szAppName ;if (!Re
42、gisterClass (&wndclass)MessageBox (NULL, TEXT (This program requires Windows NT!),szAppName, MB_ICONERROR) ;return 0 ;hwnd = CreateWindow ( szAppName, TEXT (Random Rectangles),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL) ;ShowWindow (hwnd
43、, iCmdShow) ;UpdateWindow (hwnd) ;while (GetMessage (&msg, NULL, 0, 0)TranslateMessage (&msg) ;DispatchMessage (&msg) ;return msg.wParam ;VOID Thread (PVOID pvoid)HBRUSH hBrush ;HDC hdc ;int xLeft, xRight, yTop, yBottom, iRed, iGreen, iBlue ;while (TRUE)if (cxClient != 0 | cyClient != 0)xLeft = rand () % cxClient ;xRight = rand () % cxClient ;yTop = rand () % cyClient ;yBottom = ran