C/C++运行期库
若要使多线程 C和C + +程序能够正确地运行,必须创建一个数据结构,并将它与使用C/C++运行期库函数的每个线程关联起来。
1、_beginthreadex
_beginthreadex函数的参数列表与CreateThread函数的参数列表是相同的,但是参数名和类型并不完全相同(C / C + +运行期函数不对Windows数据类型有任何依赖)。_beginthreadex函数只存在于C/C++运行期库的多线程版本中,多线程应用程序必须显式转换到多线程的C/C++运行期库。
(1)_beginthreadex的一些要点:
• 每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。(tiddata结构位于Mtdll.h文件中的Visual C++源代码中)。
• 传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。
• _beginthreadex从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。
• 当调用CreateThread时,它被告知通过调用_threadstartex而不是pfnStartAddr来启动执行
新线程。还有,传递给线程函数的参数是tiddata结构而不是pvParam的地址。
• 如果一切顺利,就会像CreateThread那样返回线程句柄。如果任何操作失败了,便返回NULL。
(2)_threadstartex的一些要点:
• 新线程开始从 BaseThreadStart函数(在kernel32.dll文件中)执行,然后转移到_threadstartex。
• 到达该新线程的tiddata块的地址作为其唯一参数被传递给_threadstartex。
• TlsSetValue是个操作系统函数,负责将一个值与调用线程联系起来。这称为线程本地存储器(T L S) , _threadstartex函数将tiddata块与线程联系起来。
• 一个S E H帧被放置在需要的线程函数周围。这个帧负责处理与运行期库相关的事情.如运行期错误(和 C/C++运行期库的signal函数。(用CreateThread函数创建线程,然后调用C/C++运行期库的signal函数,那么该函数就不能正确地运行。
• 调用必要的线程函数,传递必要的参数。(函数和参数的地址由 _ beginthreadex保存在tiddata块中。
• 必要的线程函数返回值被认为是线程的退出代码。
_threadstartex并不只是返回到BaseThreadStart。如果它准备这样做,那么线程就终止运行,它的退出代码将被正确地设置,但是线程的tiddata内存块不会被撤消。这将导致应用程序中出现一个漏洞。要防止这个漏洞,可以调用另一个C/C++运行期库函数_endthreadex ,并传递退出代码。
2、_endthreadex
C运行期库的_getptd函数内部调用操作系统的TlsGetValue函数,该函数负责检索调用线程的tiddata内存块的地址。然后该数据块被释放,而操作系统的ExitThread函数被调用,以便真正撤消该线程。当然,退出代码要正确地设置和传递。
ExitThread 函数将撤消调用函数,并且不允许它从当前执行的函数返回。由于该函数不能返回,所以创建的任何 C + +对象都不会被撤消。它会使得线程的t i d d a t a内存块无法释放,这样,应用程序将会始终占用内存(直到整个进程终止运行为止)
C / C + +运行期库的启动代码为应用程序的主线程分配了数据块,并且对数据块进行了初始化,这样,主线程就能安全地调用 C / C + +运行期函数中的任何函数。当主线程从它的进入点函数返回时,C / C + +运行期库就会释放相关的数据块。此外,启动代码设置了相应的结构化异常处理代码,以便主线程能够成功地调用C / C + +运行期库的s i g n a l函数。
3、_beginthread与_endthread
_beginthread无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程的I D值。_endthread函数不带参数,线程的退出代码必须硬编码为0。