WINDOWS的多任务就是多进程;
一个进程可以有多个线程呀。

解决方案 »

  1.   

    第4章 进程
    1. 如何取得正在运行的程序的ANSI/Unicode全路径和名字?
    使用C/C++运行期全局变量_pgmptr和_wpgmptr(Stdlib.h)。并不是进程名,而是可执行文件名。
    2. 如何取得程序运行时的命令行?
    (1) 使用WinMain函数的第三个参数取得命令行的参数部分。
    (2) 使用PTSTR GetCommandLine(  );函数取得完整命令行。
    当程序是被CreateProcess起动时,由于CreateProcess调用方法不当,简单的使用以上方法不能正确取得命令行的参数。
    3. 如何设置进程的错误模式?
    调用SetErrorMode函数。
    标志 说明
    SEM_FAILCRITICALERRORS 系统不显示关键错误句柄消息框,并将错误返回给调用进程
    SEM_NOGOFAULTERRORBOX 系统不显示一般保护故障消息框。本标志只应该由采用异常情况处理程序来处理一般保护(G P )故障的调试应用程序来设定
    SEM_NOOPENFILEERRORBOX  当系统找不到文件时,它不显示消息框。
    SEM_NOALIGNMENTFAULTEXCEPT 系统自动排除内存没有对齐的故障,并使应用程序看不到这些故障。本标志对x 8 6 处理器不起作用
    默认情况下,子进程继承父进程的错误模式标志。
    4. 如何取得系统的版本?
    调用GetVersionEx函数。
    5. 如何正确使用CreateProcess函数?
    (1) LPCTSTR lpApplicationName
    想运行的可执行文件的名字的字符串(应含扩展名)。如果找不到该文件,CreateProcess运行失败。应该设为NULL。
    (2) LPTSTR lpCommandLine
    传递给新进程的命令行字符串,应当为非常量字符串的地址。可以设定一个完整的命令行,如果第一个标记没有扩展名,CreateProcess将其假设为.exe。如果找不到该文件,CreateProcess按环境设置目录搜索运行。
    (3) LPSECURITY_ATTRIBUTES 
    设定进程对象的安全性。可以为这些参数传递NULL,在这种情况下,系统为这些对象赋予默认安全性描述符。 (不明白)
    (4) LPSECURITY_ATTRIBUTES lpThreadAttributes
    设定线程对象的安全性。可以为这些参数传递NULL,在这种情况下,系统为这些对象赋予默认安全性描述符。 (不明白)
    (5) BOOL bInheritHandles
    决定子进程对父进程继承性,一般设为FALSE。
    (6) DWORD dwCreationFlags
    用于标识标志,以便用于规定如何来创建新进程。
    标志 说明
    EBUG_PROCESS 父进程想要调试子进程和子进程将来生成的任何进程。当任何子进程(被调试进程)中发生某些事件时,将情况通知父进程。 (不明白)
    DEBUG_ONLY_THIS_PROCESS 与DEBUG_PROCESS标志相类似,调试程序只被告知紧靠父进程的子进程中发生的特定事件。 (不明白)
    CREATE_SUSPENDED 新进程被创建,但是,它的主线程则被挂起。
    DETACHED_PROCESS 阻止基于CUI的进程对它的父进程的控制台窗口的访问,并告诉系统将它的输出发送到新的控制台窗口。
    CREATE_NEW_CONSOLE 为新进程创建一个新控制台窗口。如果同时设定CREATE_NEW_CONSOLE和DETACHED_PROCESS标志,就会产生一个错误。
    CREATE_NO_WINDOW 不为应用程序创建任何控制台窗口。
    CREATE_NEW_PROCESS_GROUP 修改用户在按下Ctrl+C或Ctrl+Break键时得到通知的进程列表。
    CREATE_DEFAULT_ERROR_MODE 不继承父进程使用的错误模式。
    CREATE_SEPARATE_WOW_VDM 只能当你在Windows2000上运行16位Windows应用程序时使用。告诉系统创建一个单独的DOS虚拟机(VDM),并且在该VDM中运行16位Windows应用程序。 (不明白)
    CREATE_SHARED_WOW_VDM 只能当你在Windows2000上运行16位Windows应用程序时使用。在系统的共享VDM中运行16位Windows应用程序。 (不明白)
    CREATE_UNICODE_ENVIRONMENT 告诉系统,子进程的环境块应该包含Unicode字符。按照默认设置,进程的环境块包含的是ANSI字符串。
    CREATE_FORCEDOS 强制系统运行嵌入16位OS/2应用程序的MOS-DOS应用程序。
    CREATE_BREAKAWAY_FROM_JOB 使作业中的进程生成一个与作业相关联的新进程 (不明白)。
    IDLE_PRIORITY_CLASSBELOW_NORMAL_PRIORITY_CLASSNORMAL_PRIORITY_CLASSABOVE_NORMAL_PRIORITY_CLASSHIGH_PRIORITY_CLASSREALTIME_PRIORITY_CLASS 空闲低于正常(Windows2000)正常高于正常(Windows2000)高实时
    对于大多数应用程序来说不应该设定优先级类。
    (7) LPVOID lpEnvironment
    指向包含新进程将要使用的环境字符串的内存块。在大多数情况下,为该参数传递NULL,使子进程能够继承它的父进程正在使用的一组环境字符串。也可以使用GetEnvironmentStrings函数当不再需要该内存块时,应该调用FreeEnvironmentStrings函数将内存块释放。
    (8) LPCTSTR lpCurrentDirectory
    设置子进程的当前驱动器和目录。如果本参数是NULL,则新进程的工作目录将与生成新进程的应用程序的目录相同。如果本参数不是NULL,那么必须指向包含需要的工作驱动器和工作目录的以0 结尾的字符串。注意,必须设定路径中的驱动器名。
    (9) LPSTARTUPINFO lpStartupInfo
    使用时应首先进行初始化。
    成员 窗口/控制台 作用
    cb 两者兼有 用作版本控制手段。必须初始化为sizeof(STARTUPINFO)
    lpReserved 两者兼有 保留。必须初始化为NULL (不为NULL也可以)
    lpDesktop 两者兼有 标识启动应用程序所在桌面的名字。如果桌面不存在,便创建一个带有默认属性的桌面,并使用为新进程指定的名字。其值为NULL时,与当前桌面相关联。 (不明白)
    lpTitle 控制台 设定控制台窗口的名称。其值为NULL,则把可执行文件的名字用作窗口名。
    dwXdwY 两者兼有 设定应用程序窗口在屏幕上的位置(以像素为单位)。只有当子进程用CW_USEDEFAULT作为CreateWindow的x参数来创建它的第一个重叠窗口时,才使用这两个坐标。
    dwXSizedwYsize 两者兼有 设定应用程序窗口的宽度和长度(以像素为单位)只有当子进程将CW_USEDEFAULT用作CreateWindow的nWidth参数来创建它的第一个重叠窗口时,才使用这些值。
    dwXCountCharsdwYCountChars 控制台 设定子应用程序的控制台窗口的宽度和高度(以字符为单位)
    dwFillAttribute 控制台 设定控制台窗口的文本和背景颜色
    dwFlags 两者兼有 后面以表格说明。
    wShowWindow 窗口 设定如果子应用程序初次调用的ShowWindow将SW_SHOWDEFAULT作为nCmdShow参数传递时,该应用程序的第一个重叠窗口应该如何出现。
    cbReserved2 两者兼有 保留。必须被初始化为0 (非0也可以)
    lpReserved2 两者兼有 保留。必须被初始化为NULL (为什么)
    hStdInputhStdOutputhStdError 控制台 设定控制台输入输出缓存的句柄。照默hStdInput标识键盘缓存,hStdOutput和hStdError标识控制台窗口缓存。
    dwFlags使用方法:
    标志 含义
    STARTF_USESIZE 使用dwXSize和dwYSize成员
    STARTF_USESHOWWINDOW 使用wShowWindow成员
    STARTF_USEPOSITION 使用dwX和dwY成员
    STARTF_USECOUNTCHARS 使用dwXCountChars和dwYCountChars成员
    STARTF_USEFILLATTRIBUTE 使用dwFillAttribute成员
    STARTF_USESTDHANDLES 使用hStdInput、hStdOutput和hStdError成员
    STARTF_RUN_FULLSCREEN 强制在x86计算机上运行的控制台应用程序以全屏幕方式运行
    STARTF_FORCEONFEEDBACK 启动进程时,临时将系统的箭头光标改为沙漏箭头光标。
    STARTF_FORCEOFFFEEDBACK 启动进程时,不将光标改为沙漏。
    (10) LPPROCESS_INFORMATION lpProcessInformation
    新进程的返回信息。hProcess为新进程内核对象的句柄;hThread为新线程内核对象的句柄。在使用后应当用CloseHandle释放,使该内核的使用计数减一。dwProcessId新进程ID号;dwThreadId新线程ID号。0不能为ID号。虽然系统不会同时有相同的ID号,但是当一个进程的内核句柄被释放后其ID号又可能被新的进程使用。若要确保进程ID或线程ID不被重复使用,唯一的方法是保证进程或线程的内核对象不会被撤消。如果刚刚创建了一个新进程或线程,只要不关闭这些对象的句柄,就能够保证进程对象不被撤消。一旦应用程序结束使用该ID,那么调用CloseHandle就可以释放内核对象,要记住,这时使用或依赖进程ID,对来说将不再安全。如果使用的是子进程,将无法保证父进程或父线程的有效性,除非父进程复制了它自己的进程对象或线程对象的句柄,并让子进程继承这些句柄。
    6. 如何取得本进程的启动信息?
    调用GetStartInfo函数。
    7. 如何终止进程的运行?
    (1) 使主线程的进入点函数返回(最好使用这个方法)。
    这是保证所有线程资源能够得到正确清除的唯一办法。
    让主线程的进入点函数返回,可以确保下列操作的实现:
    •该线程创建的任何C++对象将能使用它们的析构函数正确地撤消。
    •操作系统将能正确地释放该线程的堆栈使用的内存。
    •系统将进程退出代码(在进程的内核对象中维护)设为进入点函数返回值。
    •系统将进程内核对象的使用计数递减1。
    (2) 进程中的一个线程调用ExitProcess函数(应该避免使用这种方法)。
    显式调用ExitProcess和ExitThread是程序不能正确将自己清除的常见原因。调用ExitThread时,进程将继续运行,但可能会泄漏内存或其他资源。
    (3) 另一个进程中的线程调用TerminateProcess函数(应该避免使用这种方法)。
    只有当无法用另一种方法来迫使进程退出时,才应该使用TerminateProcess。
    (4) 进程中的所有线程自行终止运行(这种情况几乎从未发生)。
    如果进程中的所有线程全部终止运行,操作系统就认为没有理由继续保留进程的地址空间。
    一旦进程终止运行(无论采用何种方法),系统将确保该进程不会将它的任何部分遗留下来。绝对没有办法知道该进程是否曾经运行过。进程一旦终止运行,它绝对不会留下任何蛛丝马迹。
    8. 如何枚举系统中运行的进程?
    调用Process32First和Process32Next函数。
    线程的基础知识
    1. 进程与线程有那些区别和联系?
    l 每个进程至少需要一个线程。
    l 进程由两部分构成:进程内核对象,地址空间。线程也由两部分组成:线程内核对象,操作系统用它来对线程实施管理。线程堆栈,用于维护线程在执行代码时需要的所有函数参数和局部变量。
    l 进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个寿命期都在该进程中。
    l 如果在单进程环境中,有多个线程正在运行,那么这些线程将共享单个地址空间。这些线程能够执行相同的代码,对相同的数据进行操作。这些线程还能共享内核对象句柄,因为句柄表依赖于每个进程而不是每个线程存在。
    l 进程使用的系统资源比线程多得多。实际上,线程只有一个内核对象和一个堆栈,保留的记录很少,因此需要很少的内存。因此始终都应该设法用增加线程来解决编程问题,避免创建新的进程。但是许多程序设计用多个进程来实现会更好些。
    2. 如何使用_beginthreadex函数?
    使用方法与CreateThread函数相同,只是调用参数类型需要转换。
    3. 如何使用CreateThread函数?
    当CreateThread被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。使用时应当注意在不需要对线程内核进行访问后调用CloseHandle函数关闭线程句柄。因为CreateThread函数中使用某些C/C++运行期库函数时会有内存泄漏,所以应当尽量避免使用。
    参数 含义
    lpThreadAttributes 如果传递NULL该线程使用默认安全属性。如果希望所有的子进程能够继承该线程对象的句柄,必须将它的bInheritHandle成员被初始化为TRUE。
    dwStackSize 设定线程堆栈的地址空间。如果非0,函数将所有的存储器保留并分配给线程的堆栈。如果是0,CreateThread就保留一个区域,并且将链接程序嵌入.exe文件的/STACK链接程序开关信息指明的存储器容量分配给线程堆栈。
    lpStartAddress 线程函数的地址。
    lpParameter 传递给线程函数的参数。
    dwCreationFlags 如果是0,线程创建后立即进行调度。如果是CREATE_SUSPENDED,系统对它进行初始化后暂停该线程的运行。
    LpThreadId 用来存放系统分配给新线程的ID。
    4. 如何终止线程的运行?
    (1) 线程函数返回(最好使用这种方法)。
    这是确保所有线程资源被正确地清除的唯一办法。
    如果线程能够返回,就可以确保下列事项的实现:
    •在线程函数中创建的所有C++对象均将通过它们的撤消函数正确地撤消。
    •操作系统将正确地释放线程堆栈使用的内存。
    •系统将线程的退出代码设置为线程函数的返回值。
    •系统将递减线程内核对象的使用计数。
    (2) 调用ExitThread函数(最好不要使用这种方法)。
    该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被撤消。
    (3) 调用TerminateThread函数(应该避免使用这种方法)。
    TerminateThread能撤消任何线程。线程的内核对象的使用计数也被递减。TerminateThread函数是异步运行的函数。如果要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数。当使用返回或调用ExitThread的方法撤消线程时,该线程的内存堆栈也被撤消。但是,如果使用TerminateThread,那么在拥有线程的进程终止运行之前,系统不撤消该线程的堆栈。
    (4) 包含线程的进程终止运行(应该避免使用这种方法)。
    由于整个进程已经被关闭,进程使用的所有资源肯定已被清除。就像从每个剩余的线程调用TerminateThread一样。这意味着正确的应用程序清除没有发生,即C++对象撤消函数没有被调用,数据没有转至磁盘等等。
    一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调用GetExitcodeThread来检查由hThread标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码。
    5. 为什么不要使用_beginthread函数和_endthread函数?
    与_beginthreadex函数相比参数少,限制多。无法创建暂停的线程,无法取得线程ID。_endthread函数无参数,线程退出代码必须为0。还有_endthread函数内部关闭了线程的句柄,一旦退出将不能正确访问线程句柄。
    6. 如何对进程或线程的内核进行引用?
    HANDLE GetCurrentProcess(  );
    HANDLE GetCurrentThread(  );
    这两个函数都能返回调用线程的进程的伪句柄或线程内核对象的伪句柄。伪句柄只能在当前的进程或线程中使用,在其它线程或进程将不能访问。函数并不在创建进程的句柄表中创建新句柄。调用这些函数对进程或线程内核对象的使用计数没有任何影响。如果调用CloseHandle,将伪句柄作为参数来传递,那么CloseHandle就会忽略该函数的调用并返回FALSE。
    DWORD GetCurrentProcessId(  );
    DWORD GetCurrentThreadId(  );
    这两个函数使得线程能够查询它的进程的唯一ID或它自己的唯一ID。
    7. 如何将伪句柄转换为实句柄?
    HANDLE hProcessFalse = NULL;
    HANDLE hProcessTrue = NULL;
    HANDLE hThreadFalse = NULL;
    HANDLE hThreadTrue = NULL;hProcessFalse = GetCurrentProcess(  );
    hThreadFalse = GetCurrentThread(  );
    取得线程实句柄:
    DuplicateHandle( hProcessFalse, hThreadFalse, hProcessFalse, &hThreadTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
    取得进程实句柄:
    DuplicateHandle( hProcessFalse, hProcessFalse, hProcessFalse, &hProcessTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
    由于DuplicateHandle会递增特定对象的使用计数,因此当完成对复制对象句柄的使用时,应该将目标句柄传递给CloseHandle,从而递减对象的使用计数。