《windows sdk编程系列文章12 ---- 进程、多线程.doc》由会员分享,可在线阅读,更多相关《windows sdk编程系列文章12 ---- 进程、多线程.doc(19页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、windows sdk编程系列文章 - 进程2008-04-13 20:30本课中我们将学习:什么是进程?如何产生和终止一个进程?理论:进程是什么?下面是我从核心编程中节选的解释: “一个进程是一个正在执行的应用程序,它包含有:私有的虚拟地址空间、代码、数据和其它的操作系统资源,譬如进程可以存取的管道、文件和同步对象等等。”从上面的定义中您可以看到,一个进程拥有几个对象:地址空间、执行模块和其它该执行程序打开或创建的任何对象或资源。至少,一个进程必须包含可执行模块、私有的地址空间和一个以上的线程。什么是线程呢?一个线程实际上是一个执行单元。当WINDOWS产生一个进程时,它自动为该进程产生一个
2、主线程。该线程通常从模块的第一条指令处开始执行。如果进程需要更多的线程,它可以随后显式地产生。当WINDWOS 接收到产生进程的消息时,它会为进程生成私有内存地址空间,接着把可执行文件映射到该空间。在WIN32下为进程产生了主进程后,您还可以调用函数CreateProcess来为您的进程产生更多的线程。CreateProcess的原型如下:BOOL CreateProcess( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIB
3、UTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation);不要被这么多的参数吓倒,其实您可以忽略其中的大多数的参数(让它们有缺省值)。lpApplicationName - 可执行文件的名称(含或不含路径)。如果该参数为NULL,那必须在参数lpCommandLine中传
4、递文件名称。lpCommandLine - 传递给欲执行的文件的命令行参数。如果lpApplicationName为NULL,那必须在该参数中指定,譬如:notepad.exe readme.txt 。lpProcessAttributes 和 lpthreadAttributes - 指定进程和主线程的安全属性。您可以把它们都设成为NULL,这样就设置了缺省的安全属性。bInheritHandles - 标志位。用来设置新进程是否继承创建进程所有的打开句柄。dwCreationFlags - 有几个标志可以在此处设置以决定欲创建进程的行为,譬如:您可能想创建进程后并不想让它立刻运行,这样在它
5、真正运行前可以作一些检查和修改工作。您还可以在此处设置新进程中的所有线程的优先级,通常我们把它设置为NORMAL_PRIORITY_CLASS。lpEnvironment - 指向环境块的指针,一般地环境块包含几个环境字符串。如果该参数为NULL,那么新进程继承创建进程的环境块。lpCurrentDirectory - 指向当前目录以及为子进程设置的“当前目录”的路径。如果为NULL, 则继承创建进程的“当前目录”路径。lpStartupInfo - 指向新进程的启动结构体STARTUPINFO的指针。STARTUPINFO告诉WINDOWS如何显示新进程的外观。该参数有许多的成员变量,如果您
6、不想新进程有什么的特别之处,可以调用GetStartupInfo函数来用创建进程的启动参数来填充STARTUPINFO结构体变量。lpProcessInformation - 指向结构体PROCESS_INFORMATION的指针,该结构体变量包含了一些标识该进程唯一性的一些成员变量:typedef struct _PROCESS_INFORMATION HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; PROCESS_INFORMATION进程句柄和进程ID是两个不同的概念。进程ID好似一个唯一值,而进
7、程句柄是调用相关的WINDOWS API 后得到的一个返回值。不能用进程句柄来标识一个进程的唯一性,因为这个值并不唯一。在调用CreateProcess产生新进程后,该进程就被创建,而且CerateProcess函数立即返回。您可以调用函数GetExitCodeProcess来检验进程是否结束。该函数的原型如下:BOOL GetExitCodeProcess( HANDLE hProcess, LPDWORD lpExitCode );如果调用成功,lpExitCode中包含了所查询进程的状态码。如果等于STILL_ACTIVE就表明该进程依旧存在。 您可以调用函数TerminateProce
8、ss来强制终止一个进程。该函数的原型如下:BOOL TerminateProcess( HANDLE hProcess, UINT uExitCode );您可以指定任意一个退出值。用该函数结束一个进程并不好,因为该进程加载的动态连接库并不会得到进程正退出的消息。例子:见光盘FirstWindow12在下面的例子中,当用户选择菜单项“crate process”时我们创建一个新进程。它会去执行“msgbox.exe”。如果用户想要终止新进程,可以选择菜单项“terminate process”。这时,应用程序检查欲终止的进程是否仍存在,若存在则调用TerminateProcess函数来终止它。
9、#include Windows.h#include tchar.hTCHAR ClassName = _T(Win32ProcessClass);TCHAR AppName = _T(Win32 File Process Example);TCHAR MenuName = _T(FirstMenu);TCHAR programname = _T(helleworld.exe);HINSTANCE g_hInstance;PROCESS_INFORMATION processInfo;#define IDM_CREATE_PROCESS 1#define IDM_TERMINATE 2#def
10、ine IDM_EXIT 3HMENU hMenu;DWORD ExitCode;INT_PTR CALLBACK ProcWinMain( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam ) STARTUPINFO startInfo; switch(Msg) case WM_INITMENUPOPUP: if(GetExitCodeProcess(processInfo.hProcess,&ExitCode) if(ExitCode = STILL_ACTIVE) EnableMenuItem(hMenu,IDM_CREATE_PROCE
11、SS,MF_GRAYED); EnableMenuItem(hMenu,IDM_TERMINATE,MF_ENABLED); else EnableMenuItem(hMenu,IDM_CREATE_PROCESS,MF_ENABLED); EnableMenuItem(hMenu,IDM_TERMINATE,MF_GRAYED); else EnableMenuItem(hMenu,IDM_CREATE_PROCESS,MF_ENABLED); EnableMenuItem(hMenu,IDM_TERMINATE,MF_GRAYED); break; case WM_DESTROY: Pos
12、tQuitMessage(0); break; case WM_COMMAND: if(lParam = 0) if(LOWORD(wParam) = IDM_CREATE_PROCESS) if(processInfo.hProcess != 0) CloseHandle(processInfo.hProcess); processInfo.hProcess = 0; GetStartupInfo(&startInfo); CreateProcess(programname,NULL,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,NULL,NULL,&start
13、Info,&processInfo); CloseHandle(processInfo.hThread); else if(LOWORD(wParam) = IDM_TERMINATE) GetExitCodeProcess(processInfo.hProcess,&ExitCode); if(ExitCode = STILL_ACTIVE) TerminateProcess(processInfo.hProcess,0); CloseHandle(processInfo.hProcess); processInfo.hProcess = 0; else DestroyWindow(hWnd
14、); break; default: return DefWindowProc(hWnd,Msg,wParam,lParam); return 0;int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) WNDCLASSEX wc; MSG msg; HWND hWnd; g_hInstance = hInstance; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; w
15、c.lpfnWndProc = ProcWinMain; wc.cbClsExtra = NULL; wc.cbWndExtra = NULL; wc.hInstance = hInstance; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = MenuName; wc.lpszClassName = ClassName; wc.hIcon = wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION); wc.hCursor = LoadCursor(NULL,IDC_ARROW);
16、 RegisterClassEx(&wc); hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,300,200,NULL,NULL,hInstance,NULL); ShowWindow(hWnd,SW_SHOWNORMAL); UpdateWindow(hWnd); hMenu = GetMenu(hWnd); while(GetMessage(&msg,NULL,0,0) TranslateMessage(&msg); Dispa
17、tchMessage(&msg); return msg.wParam;分析:应用程序创建主窗口,保存菜单句柄以备后用。当用户在主菜单中选择了“Process”菜单项后,消息处理过程中接收到WM_INITMENUPOPUP消息,我们在此处修改弹出式菜单中的菜单项的“使能”和“非使能”,以便同一菜单有不同的显示。 case WM_INITMENUPOPUP: if(GetExitCodeProcess(processInfo.hProcess,&ExitCode) if(ExitCode = STILL_ACTIVE) EnableMenuItem(hMenu,IDM_CREATE_PROCES
18、S,MF_GRAYED); EnableMenuItem(hMenu,IDM_TERMINATE,MF_ENABLED); else EnableMenuItem(hMenu,IDM_CREATE_PROCESS,MF_ENABLED); EnableMenuItem(hMenu,IDM_TERMINATE,MF_GRAYED); else EnableMenuItem(hMenu,IDM_CREATE_PROCESS,MF_ENABLED); EnableMenuItem(hMenu,IDM_TERMINATE,MF_GRAYED); break;我们之所以处理该消息的目的就是让菜单显示时有
19、不同的外观以方便用户的使用。譬如;新进程尚未运行时,我们就变亮(使能)“菜单项“start process”,而变灰(非使能)菜单项“terminate process”。当新进程运行起来后,菜单的外观就应该是相反的。首先我们调用GetExitCodeProcess函数,其中传入由CreateProcess返回的句柄。如果GetExitCodeProcess返回FALSE,则表示进程尚未运行,我们就让菜单项“terminate process”变灰;如果返回TRUE,表示新进程已经启动了,我们再检测是否正在运行,这通过比较ExitCode是否等于STILL_ACTIVE 来完成,如果相等,表示
20、进程仍在运行,我们就让菜单项“start process”变灰,因为在我们的简单的应用程序中不提供同时运行多个进程的能力。 if(LOWORD(wParam) = IDM_CREATE_PROCESS) if(processInfo.hProcess != 0) CloseHandle(processInfo.hProcess); processInfo.hProcess = 0; GetStartupInfo(&startInfo); CreateProcess(programname,NULL,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,NULL,NULL,
21、&startInfo,&processInfo); CloseHandle(processInfo.hThread); 当用户选择了菜单项“start process”时,我们先检测结构体PROCESS_INFORMATION中的成员变量hPRocess是否已经关闭。如果是第一次启动应用程序,那该变量为0,全局变量定义结构体时已经初始化该值为0。如果该值不为0,则表明新进程已经结束,但是我们尚未关闭该进程的句柄(以减少该进程的引用记数),我们在此处完成该动作。我们调用GetStartupInfo函数来填充启动信息的结构体变量,而该变量将被传递到CreateProcess函数中去。调用Creat
22、eProcess生成新进程,我们不检查该函数的返回值为的是让问题简化一些,在实际应用中,必须做该项工作。在调用CreateProcess后,我们立即关闭在进程信息结构体参数中返回的主线程句柄,关闭线程句柄为的是减少该内核对象的引用记数,否则即使该线程退出后,其内核对象仍惨存在内核中得不到释放,这会引起资源泄露。进程其实也是一样,之所以我们不在该处关闭进程的句柄是因为稍后我们还要用该句柄去得到一些和进程相关的信息,至于线程,我们的应用程序不需要其相关信息。 else if(LOWORD(wParam) = IDM_TERMINATE) GetExitCodeProcess(processInfo
23、.hProcess,&ExitCode); if(ExitCode = STILL_ACTIVE) TerminateProcess(processInfo.hProcess,0); CloseHandle(processInfo.hProcess); processInfo.hProcess = 0; 当用户选择了菜单项“terminate process”后,我们调用函数GetExitCodeProcess来检查新进程是否还存在,如果还存在我们就调用函数TerminateProcess来结束它。另外我们把它的句柄关闭掉,因为我们再也不用它了。技术成就梦想windows sdk编程系列文章
24、- 多线程编程2008-04-13 21:48本课中,我们将学习如何进行多线程编程。另外我们还将学习如何在不同的线程间进行通信。 理论:前一课中,我们学习了进程,其中讲到每一个进程至少要有一个主线程。这个线程其实是进程执行的一条线索,除此主线程外您还可以给进程增加其它的线程,也即增加其它的执行线索,由此在某种程度上可以看成是给一个应用程序增加了多任务功能。当程序运行后,您可以根据各种条件挂起或运行这些线程,尤其在多CPU的环境中,这些线程是并发运行的。这些是在Win32下才有的概念,在WIN16下并没有等同的概念。在同一进程中运行不同的线程的好处是这些线程可以共享进程的资源,如全局变量、资源等
25、。当然各个线程也可以有自己的私有栈用于保存私有数据。另外每个线程需要保存其运行上下文以便在线程切换时能够记住或恢复其上下文,当然这是由操作系统来完成的,对于用户是透明的。我们大体上可以把线程分成两大类: 1. 处理用户界面的线程:该类线程产生自己的窗口并负责处理相关的窗口消息。用户界面线程遵守WIN16下的互斥原则,每一刻仅有一个用户界面线程使用USER和GDI库中的内核函数,也就是说当一个用户界面程序在进入GDI或USER中时,内核不允许重入。由此我们可以推论出WIN95的该部分内核的代码是遵守16位模式的。而WINOWS NT是纯的32位操作系统,所以不存在这个问题。 2. 工作者线程:该
26、类线程不用处理窗口界面,当然也就不用处理消息了。它一般都运行在后台干一些计算之类的粗活,这大概也是把它叫做工作者线程的原因吧。 运用Win32的多线程模式来编程,我们可以遵循某种策略:即让主线程仅来做用户界面的工作,而其它繁重的工作则交由工作者线程在后台完成。这就好比我们日常生活中的许多例子。譬如:政府管理者好比是用户界面线程,它负责听取民意,给职能部门分配工作,然后把工作成果汇报给公众。而具体的职能部门就是工作者线程,它负责完成下达的具体工作。如果让政府管理这来具体地做每一件事,它必须作一件事后再做另一项,那它就不能及时来听取和反馈民意。这样就无法管理好一个国家了。当然即使采用多线程机制,政
27、府管理部门也不一定就能管理好国家,但是程序却可以采用多线程机制来管理好她自己的工作。我们可以调用CreateThread函数来生成新线程。该函数的语法如下:HANDLE CreateThread( SEC_ATTRS SecurityAttributes, ULONG StackSize, SEC_THREAD_START StartFunction, PVOID ThreadParameter, ULONG CreationFlags, PULONG ThreadId);生成一个线程的函数和生成一个进程基本相同。SecurityAttributes -如果您想要线程有缺省的安全属性,可以置该
28、值为NULL。 StackSize - 指定线程的堆栈大小。如果为0,那线程的大小和进程相同。 StartFunction- 线程函数的起始地址。注意该函数仅接收一个32位的参数和返回一个32位的值。(该参数可以是一个指针,而且进程的线程可以直接存取进程定义全局变量,所以您大可不必担心不能如何把大量的参数传递给线程)。 ThreadParameter - 传递给线程的上下文。 CreationFlags -如果是0的话则表示创线程建后立即启动,相反的是标志位CREATE_SUSPENDED,这样您需要稍后显示地让该线程运行。ThreadId - 内核给新生成的线程分配的线程ID。如果生成线程成
29、功的话,CreateThread函数就返回新线程的句柄。否则返回NULL。如果没有给参数CreationFlags指定CREATE_SUSPENDED的话,该线程就会立即运行。如果不这样,我们上面说了,需要显示地启动该线程,要这样做您需要调用ResumeThread函数。在线程返回后(线程的执行类似于执行一个函数,如果它调用了最后一条指令后,那么该线程就结束了,除非您让它进入一个循环,譬如我们讲的用户界面线程就是如此,只不过它不退出的原因是进入的循环是在while ( GetMessage(.)).中,如果您没有给它传递一个值为0的消息,那它可不会退出,系统会自动调用ExitThread函数透
30、明地处理线程一些退出时的清理工作。当然您可以自己调用该函数,但似乎没有什么意义。要得到退出时的退出码,您可以调用GetExitCodeThread函数。 如果您想结束一个程序,可以调用TerminateThread函数,不过使用该函数要小心行事,因为该函数一旦被调用线程就会退出,这样它就没有机会来做清理自己的工作了。现在我们来看看线程间的通讯机制。 总的说来一共有三种方法: 使用全局变量 使用Windows消息传递机制 使用事件 上面我们说了线程会共享进程的资源,其中全局变量也包括在内,所以线程可以通过使用全局变量来通讯。但是这种办法的明显的缺点是在有多个线程存取同一个全局变量时,必须考虑同步
31、的问题。譬如:有一个有十个成员变量的结构体,其中一个线程在对起赋值时,假设只更新了五个成员变量的值,这时内核的调度线程剥夺其运行权给另一个线程,这样接下来的线程如果想要用该全局结构体变量,它的值就显然不对了。另外多线程的程序也很难调试,尤其这些错误很隐蔽和很难复现时。如果两个线程都是用户界面线程时,用WINDOWS的消息机制来进行线程间的通讯是比较方便的. 您所要做的只是自定义一些windows消息(注意不要和windows的预定义的消息冲突),然后在线程之间传递可以了。您可以这样来定义消息,把WM_USER(它的值等于0x0400)当作基数,然后顺序地去加序号,譬如: #define WM_
32、FINISH WM_USER+0x100小于WM_USER 的值是Windows系统的保留值,大于该值留给用户来使用。如果其中有一个线程是工作者线程的话,那就不能用该种方法来进行通讯了,这是因为工作者线程没有消息队列。您应当用下面这种策略来进行工作者线程和用户界面线程之间的通讯: User interface Thread - global variable(s)- Worker thread Worker Thread - custom window message(s) - User interface Thread稍后我们的例子中将讲解这种通讯办法。最后的办法是事件对象。您可以把事件对象
33、看作是一种标志。如果事件对象的状态是无信号的话,说明该线程正在睡眠或挂起,在该种状态下系统是不会给该线程分配CPU时间片的。当一个线程的状态转成有信号时,WINDOWS就会唤醒该线程并且让它正常运行。例子:见光盘FirstWindow13#include Windows.h#include tchar.hTCHAR ClassName = _T(Win32ThreadClass);TCHAR AppName = _T(Win32 MultiThreading Example);TCHAR MenuName = _T(FirstMenu);HINSTANCE g_hInstance;#defin
34、e IDM_CREATE_THREAD 1#define IDM_EXIT 2#define WM_FINISH WM_USER+0x100HMENU hMenu;DWORD ExitCode;DWORD ThreadID;HWND g_hwnd;DWORD WINAPI ThreadProc(LPVOID lpParameter) Sleep(1000); return SendMessage(g_hwnd,WM_FINISH,NULL,NULL); INT_PTR CALLBACK ProcWinMain( HWND hWnd, UINT Msg, WPARAM wParam, LPARA
35、M lParam ) switch(Msg) case WM_FINISH: MessageBox(NULL,AppName,AppName,MB_OK); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_COMMAND: if(lParam = 0) if(LOWORD(wParam) = IDM_CREATE_THREAD) HANDLE hThread = CreateThread(NULL,NULL,ThreadProc, NULL,NORMAL_PRIORITY_CLASS,&ThreadID); CloseHan
36、dle(hThread); else DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd,Msg,wParam,lParam); return 0;int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) WNDCLASSEX wc; MSG msg; HWND hWnd; g_hInstance = hInstance; wc.cbSize = sizeof(WNDCLASSEX);
37、wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = ProcWinMain; wc.cbClsExtra = NULL; wc.cbWndExtra = NULL; wc.hInstance = hInstance; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = MenuName; wc.lpszClassName = ClassName; wc.hIcon = wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION); wc.
38、hCursor = LoadCursor(NULL,IDC_ARROW); RegisterClassEx(&wc); hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,300,200,NULL,NULL,hInstance,NULL); g_hwnd = hWnd; ShowWindow(hWnd,SW_SHOWNORMAL); UpdateWindow(hWnd); hMenu = GetMenu(hWnd); while(Get
39、Message(&msg,NULL,0,0) TranslateMessage(&msg); DispatchMessage(&msg); return msg.wParam;分析:主程序的主线程是一个用户界面线程,它有一个普通窗口。用户选择菜单项Create Thread,程序就会产生一个线程: if(LOWORD(wParam) = IDM_CREATE_THREAD) HANDLE hThread = CreateThread(NULL,NULL,ThreadProc, NULL,NORMAL_PRIORITY_CLASS,&ThreadID); CloseHandle(hThread); 上面的代码段产生一个线程,线程的主体代码是函数ThreadProc,该函数和主线程并行运行。在调用成功后,CreateThread函数立即返回,ThreadProc也开始运行。因为我们不再用线程句柄,我们立即关闭它以避免内存泄漏。我们前面讲过关闭句柄不会终止线程的执行,而只是减少起引用计数。DWORD WINAPI ThreadProc(LPVOID lpParameter) Sleep(1000); return SendMessage(g_hwnd,WM_FINISH,NULL,NULL); 我们看到上面的线程的代码仅仅Sleep了1秒,当结束后它会向主线程发送WM_FINI