dll的函数返回值需要多线程返回值同步么

Windows群集开发:6)编写资源DLL
Windows群集开发:6)编写资源DLL
  编写资源DLL
  可以使用MS Visual C++开发系统或其它C/C++开发工具编写自定义资源DLL。本文中例子使用的是:
  Microsoft Visual C++ version 4.2b, 包含Unicode MFC库.
  MIDL编译器,版本3.00.44。(该MIDL编译器可在SDK中找到)
  活动模板库(ATL)版本2.0(扩展例子代码和资源类型生成向导产生的代码需要它)
  建立编译环境时,请参考平台SDK,特别是“Preparing a Build Environment”和“Developer Notes”章节。
  在平台SDK的例子中也可找到完整的资源DLL参考实现(参见SMBsmp)。
  创建新资源类型
  要创建新的资源类型,必须写一个资源DLL和一个群集管理器扩展DLL。简单的方法是运行资源类型生成向导来创建资源DLL。该向导将箭竹一个资源DLL框架和/或包含入口点定义,申明,导出的群集管理器扩展DLL。
  创建资源DLL的完整步骤,请参考SDK文档的“Creating a Custom Resource Type”,“Using the Resource Type AppWizard”和“Customizing a Resource DLL”等章节。
  生成的资源DLL框架仅包含最基本的故障转移和故障恢复功能。要使用群集环境的全部功能和允许DLL提供资源的特定信息,需要编写自己的代码。注意由向导生成的框架代码中包含TODO:和ADDPARAM:注释以指明哪里需要添加资源的特定信息。像下面所描述的那样,你需要使用资源API完成大部分的自定义功能。
  自定义资源DLL
  如前所述,资源API包含几个入口点函数,这些函数在资源DLL内实现。资源监视器使用这些入口点函数管理DLL提供的资源。另外,资源监视器实现少数几个回调函数,资源DLL使用这些回调函数向群集服务报告状态或为系统管理员记录事件日志。
  大部分入口点函数是所有资源必需的。两个特别的API入口点函数-Arbitrate和Release-仅在编写仲裁资源时需要。本文不讨论这两个函数。其余的入口点函数在下面列出,本文将讨论它们的细节。
&&&&&&&& Startup&&&&&&& Open&&&&&&& Online&&&&&&& LooksAlive&&&&&&& IsAlive&&&&&&& Offline&&&&&&& Close&&&&&&& Terminate&&&&&&& ResourceControl&&&&&&& ResourceTypeControl
  每个由群集软件支持的资源DLL应该遵从下面的指引:
  在某个例外情况下,对于给定的资源实例,资源DLL是不可重入的。该情况是Terminate入口点函数。Terminate应该在任何时候都能被调用,即使资源DLL中线程处于等待Online或Offline调用完成的阻塞状态。
  资源DLL对于其它资源ID是可重入的。如果资源DLL拥有超过1个资源ID,就必须为DLL内所有共享的全局数据进行同步。
  在资源DLL内,一个入口点函数完成操作所花费的时间不应该超过300毫秒。如果一个入口点函数-特别是Online, Offline, LooksAlive或isAlive-超出了这个限制,就应该派生线程来处理耗时的操作。(注意当前向导会为Online生成线程,未来版本也应该为Offline生成一个线程。)
  在资源DLL初始化期间,DLL的入口点函数(系统加载DLL后的标准的DLL入口)以DLL_PROCESS_ATTACH标志被调用。接着资源监视器开始调用资源API入口点函数。
  Startup例程
  当资源DLL被加载后,资源监视器就调用Startup例程。注意仅有Startup入口点函数被导出。所有在资源DLL内实现的其它入口点函数通过Startup返回的函数表来访问。
  下面是Startup例程的定义:
&&&&&&&& DWORD WINAPI Startup&&&&&&& (&&&&&&&& LPCWSTR ResourceType,&&&&&&&& DWORD MinVersionSupported,&&&&&&&& DWORD MaxVersionSupported,&&&&&&&& PSET_RESOURCE_STATUS_ROUTINE SetResourceStatus,&&&&&&&& PLOG_EVENT_ROUTINE LogEvent,&&&&&&&& PCLRES_FUNCTION_TABLE * FunctionTable&&&&&&& );
  ResourceType参数标识被启动的资源的类型。
  SetResourceStatus和LogEvent是由资源监视器实现的回调函数。(本文后面章节将讨论这些回调函数。)在Online或Offline入口点函数被调用后,如果它们要花费超过300毫秒,资源DLL应该调用SetResourceStatus向群集告知资源状态;资源DLL也应该使用LogEvent报告事件和错误。(SetResourceStatus应该仅由Online或Offline调用,并且仅在Online或Offline返回ERROR_IO_PENDING的情况。更多的信息,请参考Online和Offline的讨论。)
  FunctionTable结构包含了资源DLL其余入口点的函数地址。
  注意 Startup入口点函数资源DLL保存回调函数LogEvent和SetResourceStatus的仅有的地方。
  Startup返回以下值:
  如果请求成功,返回ERROR_SUCCESS。
  如果资源不支持在MinVersionSupported和MaxVersionSupported之间的版本,返回ERROR_REVISION_MISMATCH
  如果操作不成功,返回Win32?编程接口的错误值。
  出于对资源操作的优化,要确保实现的Startup能够在300毫秒内完成。
  Open例程
  一旦Startup成功返回,典型地,资源监视器会为由资源DLL管理的每个资源调用Open入口点函数。
  Open例程的下定义如下:
&&&&&&&& RESID WINAPI Open&&&&&&& (&&&&&&&& LPCWSTR ResourceName,&&&&&&&& HKEY ResourceKey,&&&&&&&& RESOURCE_HANDLE ResourceHandle&&&&&&& );
  ResourceName参数标识指定了被打开的资源(一个资源DLL能够提供给定类型的多个资源)。ResourceKey参数是关于资源的特定信息,是私有属性,并且,位于群集数据库中。该键值在Open返回时关闭;因此,如果该资源在其它入口点函数中访问一个键值,DLL应该调用ClusterRegOpenKey或ClusterRegCreateKey。ResourceHandle参数在SetResourceStatus和LogEvent回调函数中使用。
  Open使用群集API打开群集数据库并取得资源参数和私有属性。一个重要的事情是资源DLL应该在Open入口检查资源当前是否脱机(一个资源不能同时在超过一个节点上处于联机状态)。如果资源当前正联机,DLL应该尝试将其脱机。(注意 在这种情况下,online和offline指明了应用程序或服务的状态,而不是给定的群集节点-资源必须是真正脱机并且没有被节点所拥有。)另外,在Open例程中,资源DLL应该为资源创建专属的数据结构。
  注意 如果资源为仲裁资源,则群集API在Open例程中是不可用的。
  Open返回以下值:
  如果操作成功,返回资源标识(RESID)。
  如果操作不成功,返回NULL。应该调用SetLastError指明发生的错误。
  如果Open返回错误(返回NULL),资源将是不可管理的。因此,Open应该仅在相当稀有的情况返回错误(例如,不能为资源分配内存)。
  出于对资源的优化操作,确保实现的Open将在300毫秒内完成。
  Online例程
  当资源被打开,资源监视器调用Online入口点函数将资源联机。
  Online例程定义如下:
&&&&&&&& DWORD WINAPI Online&&&&&&& (&&&&&&&& RESID ResourceId,&&&&&&&& PHANDLE EventHandle&&&&&&& );
  ResourceId参数被传递给该入口函数,是资源的唯一标识(与Open入口函数返回的值是同一个)。资源DLL能够将EventHandle参数传回给资源监视器以便异步通知资源监视器自身状态。如果EventHandle参数不是能够被信号激发(singal) 的有效句柄,那么资源监视器将周期性的调用资源DLL的LooksAlive入口函数以检查资源状态。如果不想资源被这种方法烦扰,DLL应该在EventHandle参数中返回有效的句柄。通过返回有效的EventHandle,资源DLL可以向资源监视器通知任何状态改变。
  每个资源类型必须有自己的Online入口函数实现。这种不同实现是必须的,因为不同类型的资源有不同的需要。例如,将磁盘联机与将普通应用程序联机是完全不同的。将磁盘联机涉及到装载磁盘,校验磁盘签名,等等。而将应用程序联机只需简单的调用CreateProcess。
  Online返回以下值:
  如果操作成功并且资源现在联机,返回ERROR_SUCCESS。
  如果资源被某些其它系统独占,并且这些其它系统之一拥有独占权,返回ERROR_RESOURCE_NOT_AVAILABLE。
  如果请求正处于等待状态,并且一个线程已经被激活以处理该请求,则返回ERROR_IO_PENDING。
  如果操作不成功,Online返回Win32错误值。
  出于对资源操作的优化,确保Online例程的实现能够在300毫秒完成处理。如果做不到,应该在派生一个将资源联机的工作线程后立即返回ERROR_IO_PENDING给资源监视器,SetResourceStatus回调函数(该函数地址保存在Startup入口函数中)应该被周期性的调用,以指示资源状态。一旦资源联机,工作线程应该被跟中止或挂起以留待未来使用。
  如果出于任何原因资源在联机时失败了,资源DLL应该使用LogEvent回调函数记录事件日志,并且应该调用SetResourceStatus函数。SetResourceStatus使用RESOURCE_STATUS结构来指示资源的联机或脱机状态。群集资源可以是下述状态之一:
  联机―状态代码ClusterResourceOnline
  脱机―状态代码ClusterResourceOffline
  失败―状态代码ClusterResourceFailed
  等待联机―状态代码ClusterResourceOnlinePending
  等待脱机―状态代码ClusterResourceOfflinePending
  SDK.关于SetResourceStatus的更多信息,请参考平台SDK。
  如果,在3分钟后,资源仍没有联机,资源监视器将调用Terminate入口函数放弃操作。如果一个资源需要花费超过3分钟才能联机,使用群集API的群集管理器,Cluster.exe,或者其它管理工具可以使用ClusterResourceControl通过控制代码CLUSCTL_RESOUCE_SET_COMM_PROPERTIES修改PendingTimeout公用属性。(更多关于群集控制代码、公用属性,私有属性等的信息,请参考平台SDK文档。
  LooksAlive和IsAlive例程
  一旦资源联机,资源监视器将周期性检查资源状态。资源监视器使用LooksAlive和IsAlive入口函数来完成这个操作。资源监视器使用LooksAlive进行临时检查,IsAlive则做通透的检查。
  LooksAlive例程定义如下:
&&&&&&&& BOOL WINAPI LooksAlive&&&&&&& (&&&&&&&& RESID ResourceId&&&&&&& );
  IsAlive例程定义如下:
&&&&&&&& BOOL WINAPI IsAlive&&&&&&& (&&&&&&&& RESID ResourceId&&&&&&& );
  在这两个例程中,ResourceId参数唯一标识了被检查的资源实例。典型情况下,LooksAlive用于进行简单检查(例如检测进程是否仍在运行,文件共享是否仍存在,等等),并且资源管理器可以经常性的调用LooksAlive。如果不想资源DLL被其打断,可以在Online例程中返回有效EventHandle(如前面所述),然后使用这个句柄向资源监视器通知资源状态。
  IsAlive入口函数对资源状态进行更详细的计算,并且由资源监视器进行(不能被阻止)。资源DLL应该对资源做一个彻底的检查看看它是否工作正常。例如,数据库资源应该检查数据库是否能够写入到磁盘,执行查询和更新到磁盘,等等。
  LooksAlive返回以下值:
  如果资源可能联机并可用,返回TRUE。
  如果资源不能正常工作,LooksAlive返回FALSE。
  IsAlive返回以下值:
  如果资源联机并且工作正常,返回TRUE。
  如果资源不能正常工作,返回FALSE。
  出于优化目的,IsAlive入口函数应该在300毫秒完成。如前所述,应该创建一个分离的工作线程来完成对资源的检查工作。该工作线程其后将状态进行投递以便于IsAlive能够获取并返回给资源管理器。
  注意LooksAlive在任何情况下不能超出300毫秒。大多数情况下,应该不超过150毫秒。IsAlive稍长一些,不过即使它是个异步调用,资源监视器在同一线程中管理的其它资源入口函数将等待直接IsAlive返回。派生线程并不能改善这种状况。因此,IsAlive也应该在不超过300毫秒的时间内完成。
  Offline例程
  入口函数的讨论到目前为止都是关于从核心开始定制一个表现良好的资源DLL。下面入口函数为资源提供收尾和卸载机制。第一个是Offline函数。
  Offline函数定义如下:
&&&&&&&& DWORD WINAPI Offline&&&&&&& (&&&&&&&& RESID ResourceId&&&&&&& );
  ResourceId参数唯一标识了资源。在使资源脱机时,资源监视器调用这个入口函数。一旦脱机,对于群集客户端该资源就不再可用。
  Offline返回以下值:
  如果成功的完成了资源脱机请求,返回ERROR_SUCCESS。
  如果请求仍在等待并且一个线程已激活以处理脱机请求,则返回ERROR_IO_PENDING。
  如果由于其它原因,操作无法完成,应该返回Win32错误代码。
  资源DLL应该在300毫秒内优雅的关闭资源并从该入口返回。如果Offline例程的实现超出了这个限制,应该返回ERROR_IO_PENDING,并派生一个线程来完成脱机请求。该线程应该使用SetResourceStatus回调持续的向资源监视器更新资源状态,直到资源状态为ClusterResourceOffline。
  如果资源没有在PendingTimeout时间内优雅的关闭或Offline函数返回Win32错误代码,资源监视器将调用Terminate入口函数来强制终止资源。
  Close例程
  Close入口函数用于关闭资源,并且对于一个资源,只能调用一次。应该使用Close翻译由Open, Offline, ResourceControl或ResourceTypeControl入口函数分配的结构。如果要关闭的资源仍没有脱机,调用Terminate强制使之脱机。
  Close例程定义如下:
&&&&&&&& VOID WINAPI Close&&&&&&& (&&&&&&&& RESID ResourceId&&&&&&& );
  ResourcdId参数是被关闭资源的唯一标识。
  Close没有返回值。
  资源DLL应该在300毫秒内关闭资源。不过,如果超出了这个限制,群集服务将以正当的方式处理。
  Terminate入口函数立即终止一个在调用Offline时没有优雅关闭的进程。
  Terminate例程定义如下:
&&&&&&&& VOID WINAPI Terminate&&&&&&& (&&&&&&&& RESID ResourceId&&&&&&& );
  ResourceId是被强制脱机的资源的唯一标识。如果资源DLL有线程正等待资源脱机或优雅的将资源脱机,将放弃线程的脱机操作,并强制使资源脱机。
  Terminate没有返回值。
  ResourceControl和ResourceTypeControl例程
  ResourceControl和ResourceTypeControl入口函数是可选的。但是,微软建议实现这两个函数以支持群集资源控制代码。管理工具,如群集管理器和Cluster.Exe,以及群集可感知应用程序使用ClusterResourceControl和ClusterResourceTypeControl函数与资源进行独占的信息通信。例如,这些可用于设置属性(公用和私有的),请求操作,等等。当管理器或群集可感知应用程序调用任一ClusterResourceXXXXControl函数,资源管理器将分别调用ResourceControl或ResourceTypeControl,将正确的控制代码传入。实现了这两个例程的资源DLL将根据控制代码执行控制请求或设置资源属性。对于没有处理的控制代码,资源DLL应该向资源监视器返回正确的状态(ERROR_INVALID_FUNCTION状态),在这种情况下,如果有默认的处理动作,资源监视器将执行之。
  ResourceControl例程定义如下:
&&&&&&&& DWORD WINAPI ResourceControl&&&&&&& (&&&&&&&& RESID ResourceId,&&&&&&&& DWORD ControlCode,&&&&&&&& LPVOID InBuffer,&&&&&&&& DWORD InBufferSize,&&&&&&&& LPVOID OutBuffer,&&&&&&&& DWORD OutBufferSize,&&&&&&&& LPDWORD BytesReturned&&&&&&& );
  ResourceId参数是受影响资源的标识。ControlCode代表要执行的操作的控制代码,该参数的有效值列表,请参见平台SDK的“Control Codes for Resources”章节。
  InBuffer是该操作用到的传入数据的缓冲区指针,InBufferSize是它的大小,以字节为单位。OutBuffer是操作返回数据的缓冲区指针,OutBufferSize是它的大小,以字节为单位。注意如果操作不需要数据或不返回数据,InBuffer和OutBuffer可以为NULL。
  BytesReturned是OutBuffer中实际数据的字节数。
  ResourceControl返回以下值:
  如果ControlCode指定的操作成功完成,返回ERROR_SUCCESS(不过实际返回有赖于控制代码)
  如果资源DLL不支持ControlCode指示的操作,或者请求必须由资源监视器来处理,则返回ERROR_INVALID_FUNCTION。
  如果操作不成功,返回Win32错误代码。
  ResourceTypeControl例程定义如下:
&&&&&&&& DWORD WINAPI ResourceTypeControl&&&&&&& (&&&&&&&& LPCWSTR ResourceTypeName,&&&&&&&& DWORD ControlCode,&&&&&&&& LPVOID InBuffer,&&&&&&&& DWORD InBufferSize,&&&&&&&& LPVOID OutBuffer,&&&&&&&& DWORD OutBufferSize,&&&&&&&& LPDWORD BytesReturned&&&&&&& );
  ResourceTypeName是操作所影响的资源类型的标识。ControlCode为要执行的操作的控制代码。关于ControlCode参数有效值列表,请参考平台SDK的“Control Codes for Resources”。
  InBuffer是该操作用到的传入数据的缓冲区指针,InBufferSize是它的大小,以字节为单位。OutBuffer是操作返回数据的缓冲区指针,OutBufferSize是它的大小,以字节为单位。注意如果操作不需要数据或不返回数据,InBuffer和OutBuffer可以为NULL。
  BytesReturned是在OutBuffer缓冲区中返回的结果数据的实际尺寸。
  ResourceTypeControl返回以下值:
  如果由ControlCode指示的操作成功完成,ResourceTypeControl返回ERROR_SUCCESS(虽然实际的返回值有赖于控制代码)。
  如果资源DLL不支持ControlCode指定的操作,或者该请求必须由资源监视器来处理,则返回ERROR_INVALID_FUNCTION。
  如果操作不成功,ResourceTypeControl应该返回Win32错误代码。
H3C认证Java认证Oracle认证
基础英语软考英语项目管理英语职场英语
.NETPowerBuilderWeb开发游戏开发Perl
二级模拟试题一级模拟试题一级考试经验四级考试资料
软件测试软件外包系统分析与建模敏捷开发
法律法规历年试题软考英语网络管理员系统架构设计师信息系统监理师
高级通信工程师考试大纲设备环境综合能力
路由技术网络存储无线网络网络设备
CPMP考试prince2认证项目范围管理项目配置管理项目管理案例项目经理项目干系人管理
职称考试题目
招生信息考研政治
网络安全安全设置工具使用手机安全
生物识别传感器物联网传输层物联网前沿技术物联网案例分析
Java核心技术J2ME教程
Linux系统管理Linux编程Linux安全AIX教程
Windows系统管理Windows教程Windows网络管理Windows故障
数据库开发Sybase数据库Informix数据库
&&&&&&&&&&&&&&&
希赛网 版权所有 & &&注入后的DLL【以解决】|有问必答 - 风叶林-资源最多的免费辅助教程论坛 - Powered by phpwind
查看完整版本: [--
注入后的DLL【以解决】
DLL在注入游戏目标进程后,调用游戏CALL会使游戏崩溃。经查是由于线程安全问题。。。 有没有哪位高手解惑,DLL的内建线程,如何跟游戏的主线程同步啊?获取游戏窗口句柄和游戏主线程ID都不是问题。。。 用时钟和消息的就不要说了,辅助肯定有很多循环和逻辑,让游戏主线程执行不太现实。 DLL内部的多线程同步当然没有问题,但是哪怕DLL只建了一个线程,但这个线程和游戏的主线程并行,也变成多线程了,这个如何能同步呢?求C++方案
你设置窗体过程上去,SetWindowLong,改变WndProc,用游戏主线程就好了,复杂的遍历逻辑,可以不放在主线程中
:你设置窗体过程上去,SetWindowLong,改变WndProc,用游戏主线程就好了,复杂的遍历逻辑,可以不放在主线程中 ( 09:15) 嗯,谢谢了,就是子类化吧,这个我知道,我就是想问问还有没有别的更科学和效率的方法,如果你们老手也都是这么做的,我也就不折腾了
网上找到一个贴子,应该也是针对这种情况的,它用汇编讲述了一段原理,原文如下:在游戏地址空间找到一处空位置&&写入如下机器码&&&&&&58&&&&&&&&&&&&&&&& pop eax&&&&&&&&&&&&&&&&&&&&&&&&&& ;&&弹出返回地址004014DA&&&&&&A3 E8144000&&&&&&&&mov dword ptr ds:[4014E8],eax&&&&;&&保存返回地址004014DF&&&&&&58&&&&&&&&&&&&&&&& pop eax&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&;&&弹出选怪call地址&&&&&&FFD0&&&&&&&&&&&&&& call eax&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ; 在游戏空间内调用了选怪call&&&&- FF25 E8144000&&&&&&jmp dword ptr ds:[4014E8]&&&&&&&& ;&&返回调用处&&&&&&90&&&&&&&&&&&&&&&& nop&&&&&&&&&&&&&&&&&&;&&下面4个字节用来保存返回地址&&&&&&90&&&&&&&&&&&&&&&& nop004014EA&&&&&&90&&&&&&&&&&&&&&&& nop004014EB&&&&&&90&&&&&&&&&&&&&&&& nop004014EC&&&&&&90&&&&&&&&&&&&&&&& nop004014ED&&&&&&90&&&&&&&&&&&&&&&& nop004014EE&&&&&&90&&&&&&&&&&&&&&&& nop===========================下来我们看看 怎么调用他 ()&&&&&&&&&&&&90&&&&&&&&&&&&&&&& nop&&&&&&C2 0400&&&&&&&&&&&&ret 4&&&&&& ;&&比如这里是有一个参数的选怪call&&&&所以是 ret 4&&&&&&90&&&&&&&&&&&&&&&& nop===========================push 1&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ;压入选怪参数 push Spy4Win.&&&&&&&&&&&&&&&&&&&&; 压入选怪call地址&&比如这个地址是选怪callmov eax,Spy4Win.&&&&&&&&&&&&&&&& ;&&这里是我们在exe地址空间内找到的可用地址call eax这样 到选怪call以后&&调用选怪call的就是游戏自己了 只是用C++和内联汇编该如何写这段代码呢?比如DLL已注入到游戏中,那么申请一段空白内存byte Addr[256]={0};那么后面该如何jmp,具体怎么写呢?使用窗口子类化,和将CALL直接写成这种形式让游戏调用,两种方式哪种更科学更效率?
:网上找到一个贴子,应该也是针对这种情况的,它用汇编讲述了一段原理,原文如下:在游戏地址空间找到一处空位置  写入如下机器码      58             &n .. ( 18:46) 这种方法要修改代码,是笨办法,还有一种就非常复杂了,是修改消息派遣,从驱动回来之后的DispatchMessage的一个东西,那个里面又个_fnDWORD 是窗体消息过程总控地址函数,修改那个也可以,不过要拷贝自己的代码到游戏内存,然后做内存重定向才可以,可以无模块,并且主线程,非常麻烦,你就用窗体主线程就好了
:这种方法要修改代码,是笨办法,还有一种就非常复杂了,是修改消息派遣,从驱动回来之后的DispatchMessage的一个东西,那个里面又个_fnDWORD 是窗体消息过程总控地址函数,修改那个也可以,不过要拷贝自己的代码到游戏内存,然后做内存重定向才可以,可以无模块,并且主线程,非 .. ( 20:25) 修改汇编代码,也就意味着每一个CALL的内联汇编都需要改写,相当于超级HOOK的原理,这样理解对吧?那么SetWindowLong的办法,是否GetWindowLong只需要DLL初始化时调用一次得到回调函数地址即可,后面再调用任何CALL都直接SetWindowLong,不过我这有点不明白的是,有些CALL我需要它的返回值,除了用全局变量外,SetWindowLong能否获取回调函数返回值。。。另外还有种办法是线程切换,即GetThreadContext(),然后挂起主线程。不知和SetWindowLong相比,其效果又如何呢?或者这两方法实际都是将主线程挂起了,还是哪种方法会造成游戏窗口画面卡顿?感谢您答疑
:这种方法要修改代码,是笨办法,还有一种就非常复杂了,是修改消息派遣,从驱动回来之后的DispatchMessage的一个东西,那个里面又个_fnDWORD 是窗体消息过程总控地址函数,修改那个也可以,不过要拷贝自己的代码到游戏内存,然后做内存重定向才可以,可以无模块,并且主线程,非 .. ( 20:25) 另外求助版主,我的DLL是MFC工程,查资料得知MFC中更改窗体过程需用到SubClassWindow来替代SetWindowLong,但我使用SubClassWindow后游戏窗口立即卡死,即使紧接着调用UnSubClassWindow反子类化也恢复不了,您能说说跨线程子类化(因DLL已注入,所以非跨进程)的简单步骤吗?自定义消息部分已解决,且已测试在DLL本线程内可以正常PostMessage并执行回调函数
:这种方法要修改代码,是笨办法,还有一种就非常复杂了,是修改消息派遣,从驱动回来之后的DispatchMessage的一个东西,那个里面又个_fnDWORD 是窗体消息过程总控地址函数,修改那个也可以,不过要拷贝自己的代码到游戏内存,然后做内存重定向才可以,可以无模块,并且主线程,非 .. ( 20:25) 感谢版主,采纳你的意见,直接使用SetWindowsLong解决了,不想折腾,对比起来确实此方法简单实用。
好的,多支持论坛~~~
用户被禁言,该主题自动屏蔽!
查看完整版本: [--
Powered by
Gzip enabled[进程线程间通信]线程同步--线程间通信
· · · ·
您当前的位置: → [进程线程间通信]线程同步--线程间通信
[进程线程间通信]线程同步--线程间通信
篇一 : 线程同步--线程间通信一、线程同步线程的同步方法跟其他系统下类似,我们可以用原子操作,可以用 mutex,lock 等。iOS 的原子操作函数是以 OSAtomic 开头的,比如:OSAtomicAdd32, OSAtomicOr32 等等。这些函数可以直接使用,因为它 们是原子操作。iOS 中的 mutex 对应的是 NSLock,它遵循 NSLooking 协议,我们可以使用 lock, tryLock, lockBeforeData:来加锁,用 unLock 来解锁。使用示例:BOOL moreToDo = YES;NSLock *theLock = [[NSLock alloc] init];...while (moreToDo) {/* Do another increment of calculation */ /* until there&s no more to do. */if ([theLock tryLock]) {/* Update display used by all threads. */[theLock unlock]; }}我们可以使用指令 @synchronized 来简化 NSLock 的使用,这样我们就不必显示编写创建 NSLock,加锁并解锁相关代码。 - (void)myMethod:(id)anObj{@synchronized(anObj) {// Everything between the braces is protected by the @synchronized directive. }}还有其他的一些锁对象,比如:循环锁 NSRecursiveLock,条件锁 NSConditionLock,分布式锁 NSDistributedLock 等等,在 这里就不一一介绍了,大家去看官方文档吧。用 NSCodition 同步执行的顺序NSCodition 是一种特殊类型的锁,我们可以用它来同步操作执行的顺序。它与 mutex 的区别在于更加精准,等待某个 NSCondtion 的线程一直被 lock,直到其他线程给那个 condition 发送了信号。下面我们来看使用示例:某个线程等待着事情去做,而有没有事情做是由其他线程通知它的。[cocoaCondition lock]; while (timeToDoWork &= 0)[cocoaCondition wait];timeToDoWork--;// Do real work here. [cocoaCondition unlock];其他线程发送信号通知上面的线程可以做事情了:[cocoaCondition lock]; timeToDoWork++; [cocoaCondition signal]; [cocoaCondition unlock];二、线程间通信线程在运行过程中,可能需要与其它线程进行通信。我们可以使用 NSObject 中的一些方法: 在应用程序主线程中做事情:performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes:在指定线程中做事情:performSelector:onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes:在当前线程中做事情:performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:inModes:取消发送给当前线程的某个消息cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget:selector:object:如在我们在某个线程中下载数据,下载完成之后要通知主线程中更新界面等等,可以使用如下接口:- (void)myThreadMainMethod{NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];// to do something in your thread job...[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO]; [pool release];}RunLoop说到 NSThread 就不能不说起与之关系相当紧密的 NSRunLoop。Run loop 相当于 win32 里面的消息循环机制,它可以让你 根据事件/消息(鼠标消息,键盘消息,计时器消息等)来调度线程是忙碌还是闲置。系统会自动为应用程序的主线程生成一个与之对应的 run loop 来处理其消息循环。在触摸 UIView 时之所以能够激发 touchesBegan/touchesMoved 等等函数被调用,就是因为应用程序的主线程在 UIApplicationMain 里面有这样一个 run loop 在分发 input 或 timer 事件。篇二 : 38线程间的通信应用程序被装载到内存之后就形成了进程,这是上一章重点讨论的话题。但是程序在内存中是如何执行的呢?这就涉及到了代码的执行单元——线程。本章就线程的创建、多线程处理展开介绍。本章首先介绍创建线程的方法和线程内核对象,接着详细分析产生线程同步问题的根本原因,并提出一些解决办法。为了扩展多线程的应用和为读者提供更多的实际机会,本章还重点讨论了线程局部存储和CWinThread类的设计,这也是设计框架程序的一个前奏。本书今后讨论的程序实例很多都是基于多线程的,在实际的应用过程中,大部分程序也都会涉及到多线程,所以读者应该深入掌握本章的内容。3.1 多线程CreateProcess函数创建了进程,同时也创建了进程的主线程。这也就是说,系统中的每个进程都至少有一个线程,这个线程从入口地址main处开始执行,直到return语句返回,主线程结束,该进程也就从内存中卸载了。主线程在运行过程中还可以创建新的线程,即所谓的多线程。在同一进程中运行不同的线程的好处是这些线程可以共享进程的资源,如全局变量、句柄等。当然各个线程也可以有自己的私有堆栈用于保存私有数据。本节具体介绍线程的创建和线程内核对象对程序的影响。3.1.1 线程的创建线程描述了进程内代码的执行路径。进程中同时可以有多个线程在执行,为了使它们能够“同时”运行,操作系统为每个线程轮流分配CPU时间片。为了充分地利用CPU,提高软件产品的性能,一般情况下,应用程序使用主线程接受用户的输入,显示运行结果,而创建新的线程(称为辅助线程)来处理长时间的操作,比如读写文件、访问网络等。这样,即便是在程序忙于繁重的工作时也可以由专门的线程响应用户命令。每个线程必须拥有一个进入点函数,线程从这个进入点开始运行。主线程的进入点是函数main,如果想在进程中创建一个辅助线程,则必须为该辅助线程指定一个进入点函数,这个函数称为线程函数。线程函数的定义如下:DWORD WINAPI ThreadProc(LPVOID lpParam); // 线程函数名称ThreadProc可以是任意的WINAPI是一个宏名,在windef.h文件中有如下的声明:#define WINAPI ____stdcall是新标准C/C++函数的调用方法。从底层上说,使用这种调用方法参数的进栈顺序和标准C调用(_cdecl方法)是一样的,都是从右到左,但是__stdcall采用自动清栈的方式,而_cdecl采用的是手工清栈方式。Windows规定,凡是由它来负责调用的函数都必须定义为__stdcall类型。ThreadProc是一个回调函数,即由Windows系统来负责调用的函数,所以此函数应定义为__stdcall类型。注意,如果没有显式说明的话,函数的调用方法是_cdecl。可以看到这个函数有一个参数lpParam,它的值是由下面要讲述的CreateTread函数的第四个参数lpParameter指定的。创建新线程的函数是CreateThread,由这个函数创建的线程将在调用者的虚拟地址空间内执行。函数的用法如下:HANDLE CreateThread (LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性DWORD dwStackSize, // 指定线程堆栈的大小LPTHREAD_START_ROUTINE lpStartAddress, // 线程函数的起始地址LPVOID lpParameter, // 传递给线程函数的参数DWORD dwCreationFlags, // 指定创线程建后是否立即启动DWORD* lpThreadId // 用于取得内核给新生成的线程分配的线程ID 号);此函数执行成功后,将返回新建线程的线程句柄。lpStartAddress 参数指定了线程函数的地址,新建线程将从此地址开始执行,直到return语句返回,线程运行结束,把控制权交给操作系统。下面是一个简单的例子(03ThreadDemo工程下)。在这个的例子中,主线程首先创建了一个辅助线程,打印出辅助线程的ID号,然后等待辅助线程运行结束;辅助线程仅打印出几行字符串,以模拟真正的工作。程序代码如下:#include &stdio.h&#include &windows.h&// 线程函数DWORD WINAPI ThreadProc(LPVOID lpParam){ int i = 0;while(i & 20){ printf(& I am from a thread, count = %d \n&, i++); }return 0;}int main(int argc, char* argv[]){ HANDLE hTDWORD dwThreadId;// 创建一个线程hThread = ::CreateThread (NULL, // 默认安全属性NULL, // 默认堆栈大小ThreadProc, // 线程入口地址(执行线程的函数)NULL, // 传给函数的参数0, // 指定线程立即运行&dwThreadId); // 返回线程的ID号printf(& Now another thread has been created. ID = %d \n&, dwThreadId);// 等待新线程运行结束::WaitForSingleObject (hThread, INFINITE);::CloseHandle (hThread);return 0;}程序执行后,CreateThread函数会创建一个新的线程,此线程的入口地址为 ThreadProc。最后的输出结果如图3.1所示。图3.1 新线程的运行结果上面的例子使用CreateThread 函数创建了一个新线程:CreateThread ( NULL, NULL, ThreadProc, NULL,0, NULL);创建新线程后CreateThread函数返回,新线程从ThreadProc函数的第一行执行。主线程继续运行,打印出新线程的一些信息后,调用WaitForSingleObject函数等待新线程运行结束。// 等待新线程运行结束::WaitForSingleObject (hThread, // hHandle 要等待的对象的句柄INFINITE ); // dwMilliseconds 要等待的时间(以毫秒为单位)WaitForSingleObject函数用于等待指定的对象(hHandle)变成受信状态。参数dwMilliseconds给出了以毫秒为单位的要等待的时间,其值指定为INFINITE表示要等待无限长的时间。当有下列一种情况发生时函数就会返回:(1)要等待的对象变成受信(signaled)状态。(2)参数dwMilliseconds指定的时间已过去。一个可执行对象有两种状态,未受信(nonsignaled)和受信(signaled)状态。线程对象只有当线程运行结束时才达到受信状态,此时“WaitForSingleObject(hThread, INFINITE)”语句才会返回。CreateThread函数的lpThreadAttributes和dwCreationFlags参数的作用在本节的例子中没有体现出来,下面详细说明一下。lpThreadAttributes——一个指向SECURITY_ATTRIBUTES结构的指针,如果需要默认的安全属性,传递NULL就行了。如果希望此线程对象句柄可以被子进程继承的话,必须设定一个SECURITY_ATTRIBUTES结构,将它的bInheritHandle成员初始化为TRUE,如下面的代码所示:SECURITY_ATTRIBUTESsa.nLength = sizeof(sa);sa.lpSecurityDescriptor = NULL;sa.bInheritHandle = TRUE ; // 使CreateThread返回的句柄可以被继承// 句柄h可以被子进程继承HANDLE h = ::CreateThread (&sa, ...... );当创建新的线程时,如果传递NULL做为lpThreadAttributes参数的值,那么返回的句柄是不可继承的;如果定义一个SECURITY_ATTRIBUTES类型的变量sa,并像上面一样初始化sa变量的各成员,最后传递sa变量的地址做为lpThreadAttributes参数的值,那么CreateThread函数返回的句柄就是可继承的。这里的继承是相对于子进程来说的。当创建子进程时,如果为CreateProcess函数的bInheritHandles参数传递TRUE,那么子进程就可以继承父进程的可继承句柄。dwCreationFlags——创建标志。如果是0,表示线程被创建后立即开始运行;如果指定为CREATE_SUSPENDED标志,表示线程被创建以后处于挂起(暂停)状态,直到使用ResumeThread函数(见下一小节)显式地启动线程为止。3.1.2 线程内核对象线程内核对象就是一个包含了线程状态信息的数据结构。每一次对CreateThread函数的成功调用,系统都会在内部为新的线程分配一个内核对象。系统提供的管理线程的函数其实就是依靠访问线程内核对象来实现管理的。下面列出了这个结构的基本成员:线程内核对象(Thread Kernel Object)CONTEXT (上下文,即寄存器的状态)EAXEBX其他CPU寄存器Usage Count 使用计数(2)Suspend Count 暂停次数(1)Exit Code 退出代码(STILL_ACTIVE)Signaled 是否受信(FALSE)………………创建线程内核对象的时候,系统要对它的各个成员进行初始化,上表中每一项括号里面的值就是该成员的初始值。本节主要讨论内核对象各成员的作用,以及系统如何管理这些成员。1.线程上下文CONTEXT每个线程都有它自己的一组CPU寄存器,称为线程的上下文。这组寄存器的值保存在一个CONTEXT结构里,反映了该线程上次运行时CPU寄存器的状态。2.使用计数Usage CountUsage Count成员记录了线程内核对象的使用计数,这个计数说明了此内核对象被打开的次数。线程内核对象的存在与Usage Count的值息息相关,当这个值是0的时候,系统就认为已经没有任何进程在引用此内核对象了,于是线程内核对象就要从内存中撤销。只要线程没有结束运行,Usage Count的值就至少为1。在创建一个新的线程时,CreateThread函数返回线程内核对象的句柄,相当于打开一次新创建的内核对象,这也会促使Usage Count的值加1。所以创建一个新的线程后,初始状态下Usage Count的值是2。之后,只要有进程打开此内核对象,就会使Usage Count的值加1。比如当有一个进程调用OpenThread函数打开这个线程内核对象后,Usage Count的值会再次加1。HANDLE OpenThread(DWORD dwDesiredAccess, // 想要的访问权限,可以为THREAD_ALL_ACCESS等 BOOL bInheritHandle, // 指定此函数返回的句柄是否可以被子进程继承 DWORD dwThreadId // 目标线程ID号); // 注意,OpenThread函数是Windows 2000及其以上产品的新特性,Windows 98并不支持它。由于对这个函数的调用会使Usage Count的值加1,所以在使用完它们返回的句柄后一定要调用CloseHandle函数进行关闭。关闭内核对象句柄的操作就会使Usage Count的值减1。还有一些函数仅仅返回内核对象的伪句柄,并不会创建新的句柄,当然也就不会影响Usage Count的值。如果对这些伪句柄调用CloseHandle函数,那么CloseHandle就会忽略对自己的调用并返回FALSE。对进程和线程来说,这些函数有:HANDLE GetCurrentProcess (); // 返回当前进程句柄HANDLE GetCurrentThread (); // 返回当前线程句柄前面提到,新创建的线程在初始状态下Usage Count的值是2。此时如果立即调用CloseHandle函数来关闭CreateThread返回的句柄的话,Usage Count的值将减为1,但新创建的线程是不会被终止的。在上一小节那个简单的例子中,Usage Count值的变化情况是这样的:调用CreateThread函数后,系统创建一个新的线程,返回其句柄,并将Usage Count的值初始化为2。线程函数一旦返回,线程的生命周期也就到此为止了,系统会使Usage Count的值由2减为1。接下来调用CloseHandle函数又会使Usage Count减1。这个时候系统检查到Usage Count的值已经为0,就会撤销此内核对象,释放它占用的内存。如果不关闭句柄的话,Usage Count的值将永远不会是0,系统将永远不会撤销它占用的内存,这就会造成内存泄漏(当然,线程所在的进程结束后,该进程占用的所有资源都要释放)。3.暂停次数Suspend Count线程内核对象中的Suspend Count用于指明线程的暂停计数。当调用CreateProcess(创建进程的主线程)或CreateThread函数时,线程的内核对象就被创建了,它暂停计数被初始化为1(即处于暂停状态),这可以阻止新创建的线程被调度到CPU中。因为线程的初始化需要时间,当线程完全初始化好了之后,CreateProcess或CreateThread检查是否传递了CREATE_SUSPENDED 标志。如果传递了这个标志,那么这些函数就返回,同时新线程处于暂停状态。如果尚未传递该标志,那么线程的暂停计数将被递减为0。当线程的暂停计数是0的时候,该线程就处于可调度状态。创建线程的时候指定CREATE_SUSPENDED标志,就可以在线程有机会在执行任何代码之前改变线程的运行环境(如下面讨论的优先级等)。一旦达到了目的,必须使线程处于可调度状态。进行这项操作,可以使用ResumeThread函数。DWORD ResumeThread (HANDLE hThread); // 唤醒一个挂起的线程该函数减少线程的暂停计数,当计数值减到0的时候,线程被恢复运行。如果调用成功,ResumeThread函数返回线程的前一个暂停计数,否则返回0xFFFFFFFF(-1)。单个线程可以被暂停若干次。如果一个线程被暂停了3次,它必须被唤醒3次才可以分配给一个CPU。暂停一个线程的运行可以用SuspendThread函数。DWORD SuspendThread (HANDLE hThread); // 挂起一个线程任何线程都可以调用该函数来暂停另一个线程的运行。和ResumeThread相反,SuspendThread函数会增加线程的暂停计数。大约每经20ms,Windows查看一次当前存在的所有线程内核对象。在这些对象中,只有一少部分是可调度的(没有处于暂停状态),Windows选择其中的一个内核对象,将它的CONTEXT(上下文)装入CPU的寄存器,这一过程称为上下文转换。但是这样做的前提是,所有的线程具有相同的优先级。在现实环境中,线程被赋予许多不同的优先级,这会影响到调度程序将哪个线程取出来做为下一个要运行的线程。4.退出代码Exit Code成员Exit Code指定了线程的退出代码,也可以说是线程函数的返回值。在线程运行期间,线程函数还没有返回,Exit Code的值是STILL_ACTIVE。线程运行结束后,系统自动将ExitCode设为线程函数的返回值。可以用GetExitCodeThread函数得到线程的退出代码。 ……DWORD dwExitC38线程间的通信_线程间通信if(::GetExitCodeThread(hThread, &dwExitCode)){ if(dwExitCode == STILL_ACTIVE){ } // 目标线程还在运行else{ } // 目标线程已经中止,退出代码为dwExitCode}……5.是否受信Signaled成员Signaled指示了线程对象是否为“受信”状态。线程在运行期间,Signaled的值永远是FALSE,即“未受信”,只有当线程结束以后,系统才把Signaled的值置为TRUE。此时,针对此对象的等待函数就会返回,如上一小节中的WaitForSingleObject函数。3.1.3 线程的终止当线程正常终止时,会发生下列事件:l 在线程函数中创建的所有C++对象将通过它们各自的析构函数被正确地销毁。l 该线程使用的堆栈将被释放。l 系统将线程内核对象中Exit Code(退出代码)的值由STILL_ACTIVE设置为线程函数的返回值。l 系统将递减线程内核对象中Usage Code(使用计数)的值。线程结束后的退出代码可以被其他线程用GetExitCodeThread函数检测到,所以可以当做自定义的返回值来表示线程的执行结果。终止线程的执行有4种方法。(1)线程函数自然退出。当函数执行到return语句返回时,Windows将终止线程的执行。建议使用这种方法终止线程的执行。(2)使用ExitThread函数来终止线程,原型如下:void ExitThread( DWORD dwExitCode); // 线程的退出代码ExitThread函数会中止当前线程的运行,促使系统释放掉所有此线程使用的资源。但是,C/C++资源却不能得到正确地清除。例如,在下面一段代码中,theObject对象的析构函数就不会被调用。class CMyClass{public:CMyClass() { printf(& Constructor\n&); }~CMyClass() { printf(& Destructor\n&); }};void main(){ CMyClass theO::ExitThread(0); // ExitThread函数使线程立刻中止,theObject对象的析构函数得不到机会被调用// 在函数的结尾,编译器会自动添加一些必要的代码,来调用theObject的析构函数}运行上面的代码,将会看到程序的输出。Constructor一个对象被创建,但是永远也看不到Destructor这个单词出现。theObject这个C++对象没有被正确地销毁,原因是ExitThread函数强制该线程立刻终止,C/C++运行期没有机会执行清除代码。所以结束线程最好的方法是让线程函数自然返回。如果在上面的代码中删除了对ExitThread的调用,再次运行程序产生的输出结果如下:ConstructorDestructor(3)使用TerminateThread函数在一个线程中强制终止另一个线程的执行,原型如下:BOOL TerminateThread(HANDLE hThread, // 目标线程句柄DWORD dwExitCode // 目标线程的退出代码);这是一个被强烈建议避免使用的函数,因为一旦执行这个函数,程序无法预测目标线程会在何处被终止,其结果就是目标线程可能根本没有机会来做清除工作,比如,线程中打开的文件和申请的内存都不会被释放。另外,使用TerminateThread函数终止线程的时候,系统不会释放线程使用的堆栈。所以,建议读者在编程的时候尽量让线程自己退出。如果主线程要求某个线程结束,可以通过各种方法通知线程,线程收到通知后自行退出。只有在迫不得已的情况下,才使用TerminateThread函数终止线程。(4)使用ExitProcess函数结束进程,这时系统会自动结束进程中所有线程的运行。用这种方法相当于对每个线程使用TerminateThread函数,所以也应当避免这种情况。总之,始终应该让线程正常退出,即由它的线程函数返回。通知线程退出的方法很多,如使用事件对象、设置全局变量等,这是下一节的话题。3.1.4 线程的优先级每个线程都要被赋予一个优先级号,取值为0(最低)到31(最高)。当系统确定哪个线程需要分配CPU时,它先检查优先级为31的线程,然后以循环的方式对他们进行调度。如果有一个优先级为31的线程可调度,它就会被分配到一个CPU上运行。在该线程的时间片结束时,系统查看是否还有另一个优先级为31的线程,如果有,就安排这个线程到CPU上运行。Windows调度线程的原则就是这样的,只要优先级为31的线程是可调度的,就绝对不会将优先级为0~30的线程分配给CPU。大家可能以为,在这样的系统中,低优先级的线程永远得不到机会运行。事实上,在任何一段时间内,系统中的线程大多是不可调度的,即处于暂停状态。比如3.1.1小节的例子中,调用WaitForSingleObject函数就会导致主线程处于不可调度状态,还有在第4章要讨论的GetMessage函数,也会使线程暂停运行。Windows支持6个优先级类:idle、below normal、normal、above normal、high和real-time。从字面上也可以看出,normal是被绝大多数应用程序采用的优先级类。其实,进程也是有优先级的,只是在实际的开发过程中很少使用而已。进程属于一个优先级类,还可以为进程中的线程赋予一个相对线程优先级。但是,一般情况下并不改变进程的优先级(默认是nomal),所以可以认为,线程的相对优先级就是它的真实优先级,与其所在的进程的优先级类无关。线程刚被创建时,他的相对优先级总是被设置为normal。若要改变线程的优先级,必须使用下面这个函数:BOOL SetThreadPriority(HANDLE hThread,int nPriority );hThread参数是目标线程的句柄,nPriority参数定义了线程的优先级,取值如下所示:l THREAD_PRIORITY_TIME_CRITICAL Time-critical(实时)l THREAD_PRIORITY_HIGHESTHighest(最高)l THREAD_PRIORITY_ABOVE_NORMAL Above normal(高于正常,Windows 98不支持)l THREAD_PRIORITY_NORMAL Normal(正常)l THREAD_PRIORITY_BELOW_NORMAL Below normal(低于正常,Windows 98不支持)l THREAD_PRIORITY_LOWEST Lowest(最低)l THREAD_PRIORITY_IDLE Idle(空闲)下面的小例子说明了优先级的不同给线程带来的影响。它同时创建了两个线程,一个线程的优先级是“空闲”,运行的时候不断打印出“Idle Thread is running”;另一个线程的优先级为“正常”,运行的时候不断打印出“Normal Thread is running”字符串。源程序代码如下:DWORD WINAPI ThreadIdle(LPVOID lpParam) // 03PriorityDemo工程下{ int i = 0;while(i++&10)printf(&Idle Thread is running \n&);return 0;}DWORD WINAPI ThreadNormal(LPVOID lpParam){ int i = 0;while(i++&10)printf(& Normal Thread is running \n&);return 0;}int main(int argc, char* argv[]){ DWORD dwThreadID;HANDLE h[2];// 创建一个优先级为Idle的线程h[0] = ::CreateThread(NULL, 0, ThreadIdle, NULL,CREATE_SUSPENDED, &dwThreadID);::SetThreadPriority(h[0], THREAD_PRIORITY_IDLE);::ResumeThread(h[0]);// 创建一个优先级为Normal的线程h[1] = ::CreateThread(NULL, 0, ThreadNormal, NULL,0,&dwThreadID);// 等待两个线程内核对象都变成受信状态::WaitForMultipleObjects(2, // DWORD nCount 要等待的内核对象的数量h, // CONST HANDLE *lpHandles 句柄数组 TRUE, // BOOL bWaitAll 指定是否等待所有内核对象变成受信状态INFINITE); // DWORD dwMilliseconds 要等待的时间 ::CloseHandle(h[0]);::CloseHandle(h[1]);return 0;}程序运行结果如图3.2所示。可以看到,只要有优先级高的线程处于可调度状态,Windows是不允许优先级相对低的线程占用CPU的。3.2 两个优先级不同的线程创建第一个线程时,将CREATE_SUSPENDED标记传给了CreateThread函数,这可以使新线程处于暂停状态。在将它的优先级设为THREAD_PRIORITY_IDLE后,再调用ResumeThread函数恢复线程运行。这种改变线程优先级的方法在实际编程过程中经常用到。WaitForMultipleObjects函数用于等待多个内核对象,前两个参数分别为要等待的内核对象的个数和句柄数组指针。如果将第三个参数bWaitAll的值设为TRUE,等待的内核对象全部变成受信状态以后此函数才返回。否则,bWaitAll为0的话,只要等待的内核对象中有一个变成了受信状态,WaitForMultipleObjects就返回,返回值指明了是哪一个内核对象变成了受信状态。下面的代码说明了函数返回值的作用:HANDLE h[2];h[0] = hThread1;h[1] = hThread2;DWORD dw = ::WaitForMultipleObjects(2, h, FALSE, 5000);switch(dw){ case WAIT_FAILED:// 调用WaitForMultipleObjects函数失败(句柄无效?)case WAIT_TIMEOUT:// 在5秒内没有一个内核对象受信case WAIT_OBJECT_0 + 0:// 句柄h[0]对应的内核对象受信case WAIT_OBJECT_0 + 1:// 句柄h[1]对应的内核对象受信}参数bWaitAll为FALSE的时候,WaitForMultipleObjects函数从索引0开始扫描整个句柄数组,第一个受信的内核对象将终止函数的等待,使函数返回。有的时候使用高优先级的线程是非常必要的。比如,Windows Explorer进程中的线程就是在高优先级下运行的。大部分时间里,Explorer的线程都处于暂停状态,等待接受用户的输入。当Explorer的线程被挂起的时候,系统不给它们安排CPU时间片,使其他低优先级的线程占用CPU。但是,一旦用户按下一个键或组合键,例如Ctrl+Esc,系统就唤醒Explorer的线程(用户按Ctrl+Esc时,开始菜单将出现)。如果该时刻有其他优先级低的线程正在运行的话,系统会立刻挂起这些线程,允许Explorer的线程运行。这就是抢占式优先操作系统。3.1.5 C/C++运行期库在实际的开发过程中,一般不直接使用Windows系统提供的CreateThread函数创建线程,而是使用C/C++运行期函数_beginthreadex。本小节主要来分析一下_beginthreadex函数的内部实现。事实上,C/C++运行期库提供另一个版本的CreateThread是为了多线程同步的需要。在标准运行库里面有许多全局变量,如errno、strerror等,它们可以用来表示线程当前的状态。但是在多线程程序设计中,每个线程必须有惟一的状态,否则这些变量记录的信息就不会准确了。比如,全局变量errno用于表示调用运行期函数失败后的错误代码。如果所有线程共享一个errno的话,在一个线程产生的错误代码就会影响到另一个线程。为了解决这个问题,每个线程都需要有自己的errno变量。要想使运行期为每个线程都设置状态变量,必须在创建线程的时候调用运行期提供的_beginthreadex,让运行期设置了相关变量后再去调用Windows系统提供的CreateThread函数。_beginthreadex的参数与CreateThread函数是对应的,只是参数名和类型不完全相同,使用的时候需要强制转化。unsigned long _beginthreadex(void *security,unsigned stack_size,unsigned ( __stdcall *start_address )( void * ),void *arglist,unsigned initflag,unsigned *thrdaddr);VC++默认的C/C++运行期库并不支持_beginthreadex函数。这是因为标准C运行期库是在1970年左右问世的,那个时候还没有多线程这个概念,也就没有考虑到将C运行期库用于多线程应用程序所出现的问题。要想使用_beginthreadex函数,必须对VC进行设置,更换它默认使用的运行期库。选择菜单命令“Project/Settings…”,打开标题为“Project Settings”的对话框,如图3.3所示。选中C/C++选项卡,在Category对应的组合框中选择Code Generation类别。从Use run-time library组合框中选定6个选项中的一个。默认的选择是第一个,即Single-Threaded,此选项对应着单线程应用程序的静态链接库。为了使用多线程,选中Multithreaded DLL就可以了。38线程间的通信_线程间通信后两节的例子就使用_beginthreadex函数来创建线程。图3.3 选择支持多线程的运行期库相应地,C/C++运行期库也提供了另一个版本的结束当前线程运行的函数,用于取代ExitThread函数。void _endthreadex(unsigned retval ); // 指定退出代码这个函数会释放_beginthreadex为保持线程同步而申请的内存空间,然后再调用ExitThread函数来终止线程。同样,笔者还是建议让线程自然退出,而不要使用_endthreadex函数。
上一篇文章:
下一篇文章:
本文标题:[进程线程间通信]线程同步--线程间通信&版权说明
文章标题: 文章地址:
1、《[进程线程间通信]线程同步--线程间通信》一文由262阅读网()网友提供,版权归原作者本人所有,转载请注明出处!
2、转载或引用本网内容必须是以新闻性或资料性公共免费信息为使用目的的合理、善意引用,不得对本网内容原意进行曲解、修改,同时必须保留本网注明的"稿件来源",并自负版权等法律责任。
3、对于不当转载或引用本网内容而引起的民事纷争、行政处理或其他损失,本网不承担责任。

我要回帖

更多关于 java线程返回值 的文章

 

随机推荐