windows核心编程 第四章 进程

来源:互联网 发布:如何经营好淘宝店铺 编辑:程序博客网 时间:2024/05/17 03:05

转载自:http://blog.csdn.net/oqixilaotou/article/details/8437950


 一般将进程定义成一个正在运行的程序的实例,它由以下两部分构成:

 1)一个内核对象,操作系统用它来管理进程。内核对象也是系统保留进程统计信息的地方;

 2)一个地址空间。其中包含所有可执行文件或DLL模块的代码和数据。此外它还包括动态内存分配,比如线程堆栈和堆的分配。

 进程是有"惰性"的。进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。事实上,一个进程可以有多个线程,所有线程都在进程的地址空间中"同时"地执行代码。为此,每个线程都有他自己的一组CPU寄存器和它自己的堆栈。每个进程至少要有一个线程来执行进程地址空间包含的代码。当系统创建一个进程的时候,会自动地为进程创建第一个线程,这称为主线程(primary thread)。然后,这个线程再创建更多的线程,后者再创建更多的线程……。如果没有线程要执行进程地址空间中包含的代码,进程就失去了继续存在的理由。这时,系统就会自动销毁进程及其地址空间。

所有运行的线程 操作系统会轮流为每一个线程调度一些cpu时间 操作系统会采取循环的方式 为每一个线程分配时间片 从而营造出线程同时并发的假象 当然如果计算机有多个CPU  操作系统会采用更加复杂的算法给线程分配CPU时间 这期间windows内核负责线程的所有管理和调度任务

 1  Windows支持两种类型应用程序

              GUI (Graphical  User  Interface)图形用户界面

              CUI (Console User  Interface)控制台用户界面

创建一个GUI程序:

  1. #include <sstream>  
  2. #include <Windows.h>  
  3. //独一无二的类名,一般用GUID字串,以免与其他程序的类名重复  
  4. static const wchar_t * CLASS_NAME =L"{198CEAB2-AD78-4ed3-B099-247639080CB0}";  
  5.   
  6. /************************************************************************  
  7.     回调函数,当主窗口收到任何Windows消息时被调用  
  8. ************************************************************************/  
  9. LRESULT CALLBACK onMainWndMessage(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)   
  10. {  
  11.     switch (msg) {  
  12.         case WM_DESTROY:  
  13.             PostQuitMessage(0); //如果是“窗口销毁”事件,则应该在消息队列中投递   
  14.             break;              //一个WM_QUIT消息,使GetMessage()返回FALSE     
  15.         default:          
  16.             return DefWindowProc(wnd, msg, wParam, lParam);  
  17.     }      
  18.     return 0;  
  19. }  
  20.   
  21. /************************************************************************     
  22. 登记自己的窗口类  
  23. ************************************************************************/  
  24. bool registerMyClass()   
  25. {  
  26.     WNDCLASSEX  wce = {0};      
  27.     wce.cbSize = sizeof(wce);      
  28.     wce.style  = CS_VREDRAW | CS_HREDRAW;      
  29.     wce.lpfnWndProc = &onMainWndMessage;  //指明回调函数      
  30.     wce.hInstance = GetModuleHandle(0);      
  31.     wce.hIcon = LoadIcon(0, MAKEINTRESOURCE(IDI_WINLOGO));      
  32.     wce.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW));      
  33.     wce.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1);      
  34.     wce.lpszClassName = CLASS_NAME; //独一无二的类名      
  35.     wce.hIconSm = wce.hIcon;      
  36.     return 0 != RegisterClassEx(&wce);  
  37. }  
  38. /************************************************************************     
  39. 创建并显示主窗口  
  40. ************************************************************************/  
  41. bool createMyWindow(int cmdShow)   
  42. {      
  43.     HWND mainWnd = CreateWindowEx(0, CLASS_NAME, L"Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,   
  44.         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, GetModuleHandle(0), 0);     
  45.     if (0 != mainWnd) {         
  46.         ShowWindow(mainWnd, cmdShow);          
  47.         UpdateWindow(mainWnd);          
  48.         return true;      
  49.     } else {  
  50.         return false;     
  51.     }  
  52. }  
  53. /************************************************************************     
  54. 消息循环  
  55. ************************************************************************/  
  56. int messageLoop()   
  57. {      
  58.     MSG msg;    
  59.     while (GetMessage(&msg, 0, 0, 0)) {          
  60.         TranslateMessage(&msg);          
  61.         DispatchMessage(&msg);      
  62.     }      
  63.     return static_cast<int>(msg.wParam);  
  64. }  
  65.   
  66. /************************************************************************  
  67.     WinMain,程序入口  
  68. ************************************************************************/  
  69. int WINAPI WinMain(HINSTANCE, HINSTANCE, char *, int cmdShow)   
  70. {      
  71.     if (registerMyClass() && createMyWindow(cmdShow))   
  72.     {  
  73.         return messageLoop();      
  74.     } else {  
  75.         std::ostringstream msg;          
  76.         msg << "创建主窗口失败,错误代码:" << GetLastError();          
  77.         MessageBoxA(0, msg.str().c_str(), 0, MB_OK | MB_ICONSTOP);         
  78.         return 0;  
  79.     }  
  80. }   


创建一个CUI程序

  1. #include <iostream>  
  2. #include <windows.h>  
  3. #include <tchar.h>// #define _tmain main  
  4. int  _tmain(int argc,TCHAR  *argv[],TCHAR  *envp[])  
  5. {  
  6.      getchar();  
  7.      return 0;  
  8.  }  





在集成开发环境里面 每一个开发环境会为可执行文件添加一个映像的头问价 操作加载这个程序的时候 会检测这个头文件 从而向系统表明这个可执行文件时GUI程序还是CUI程序 

2 进程的实例句柄
在windows操作系统里会有各式各样的资源 操作系统其实是靠句柄来识别他们分别代表什么资源  我们都知道 每一个进程执行的时候会加载一些资源到进程的地址空间 所以每一个可执行文或者DLL文件就会有独一无二的实例句柄 可执行文件的实例被当作WinMain()函数的第一个参数hInstance传入 在需要加载资源的函数调用中,一般都需要提供此句柄的值。例如,为了从可执行文件的映像中加载一个图标资源,就需要调用下面这个函数:

 HICON   LoadIcon(HINSTANCE  hInstance, // 想要加载图标资源的 句柄
                              PCTSTR    pszICON);

                LoadIcon函数的第一个参数指出哪个文件(可执行文件或DLL文件)包含了想要加载的资源。

                WinMain的hInstance参数的实际值是一个内存基地址;系统将可执行文件的映像加载到进程地址空间中的这个位置。例如,假如系统打开可执行文件,并将它的内容加载到地址0x00400000,则WinMain的hInstance参数值为0x00400000.

             可执行文件的映像具体加载到哪一个基地址,是由链接器决定的。不同的链接器使用不同的默认基地址。使用Microsoft链接器的/BASE:address链接器开关,可以更改要将应用程序加载到哪个基地址。
 可执行文件或DLL文件被加载到进程地址空间的什么位置

                             HMODULE GetModuleHandle(PCTSTR pszModule);  //返回一个句柄/基地址:
 调用函数时,需要传递一个以0终止的字符串,它指定了已在主调进程的地址空间中加载的一个可指定文件或DLL文件的名称。如果系统找到了指定的可执行文件或DLL文件的名称,GetModuleHandle就会返回可执行文件/DLL文件映像加载到的基地址。如果传递的参数是NULL的话,可以直接返回主调进程的可执行文件的基地址。

  1. #define DLLWITHGETMODULE  extern "C" __declspec(dllexport)  
  2. #include <Windows.h>  
  3. #include <stdio.h>  
  4. #include <tchar.h>  
  5.   
  6. extern "C" const IMAGE_DOS_HEADER  __ImageBase;  //伪变量,指向当前正在运行的模块的基地址  
  7.   
  8. void DumpModule()  
  9. {
  10.    
  11.  HMODULE hModule = GetModuleHandle(NULL);//得到当前正在运行的应用程序的基地址  
  12.  _tprintf(TEXT("with GetModuleHandle(NULL) = 0x%x\r\n"),hModule);  
  13.    
  14.    
  15.  _tprintf(TEXT("with __ImageBase = 0x%x\r\n"),(HINSTANCE)&__ImageBase);//使用伪变量得到当前正在运行模块的基地址  
  16.    
  17.  //传入DumpModule函数的地址给GetModuleHandleEx函数,得到DumpModule函数所在模块的基地址  
  18.  hModule = NULL;  
  19.  ::GetModuleHandleEx(  
  20.   GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,  
  21.   (PCTSTR)DumpModule,  
  22.   &hModule);  
  23.  _tprintf(TEXT("with GetModuleHandleEx = 0x%x\r\n"),hModule);  
  24. }  
  25.   
  26. int  _tmain(int argc,TCHAR* argv[])  
  27. {  
  28.     DumpModule(); 
  29.     return 0;  
  30. }  


    这里需要注意的有两点 GetModuleHandle函数有两个特征
    1 它只是检查主调进程的地址空间 也就是说 只有主调进程的地址空间的资源才是这个函数可以找到的 如果整个进程的地址空间里含有一个DLL 但是主调进程里面不含有这个DLL 这样调用GetModuleHandle函数返回的一样是NULL 
    2 因为GetModuleHandle函数只是返回可执行文件的基地址 也就是说即使在DLL文件的代码中含有这个函数 那么返回的任然是可执行文件的基地址 而不是DLL的基地址


    3 进程的命令行
    系统在创建一个新的进程的时候 会传递一个命令给它 这个命令几乎是非空的 至少用于创建新进程的可执行文件的名称是命令行上的第一个标记 

    int WINAPI WinMain(
      HINSTANCE hInstance,//应用程序当前实例的句柄
      HINSTANCE hPrevInstance,//应用程序的先前实例的句柄。对于同一个程序打开两次,出现两个窗口第一次打开的窗口就是先前实例的
    窗口。对于一个32位程序,该参数总为NULL。如果需要检测另外一个实例是否已经存在,则使用CreateMutex函数创建一个独一无二的名字。即使互斥
    名已经存在,CreateMutex函数也是成功的,但是GetLastError函数将返回 ERROR_ALREADY_EXISTS,这就表明
    应用程序有另外一个实例存在,
    因为它首先创建了互斥名。
      LPSTR lpCmdLine,//指向应用程序命令行的字符串的指针,不包括执行文件名。获得整个命令行,参看GetCommandLine。第三个参数lpCmdLine是
    一个以空终止的字符串,指定传递给
    应用程序命令行参数。例如:在D盘下有一个sunxin.txt文件,当我们用鼠标双击这个文件时将启动记事本程序
    (notepad.exe),此时系统会将D:\sunxin.txt作为
    命令行参数传递给记事本程序的WinMain函数,记事本程序在得到这个文件的全路径名后,
    就在窗口中显示该文件的内容。要在VC++
    开发环境中向应用程序传递参数,可以单击菜单【Project】→【Settings】,选择“Debug”选项卡,
    在“Program arguments”编辑框中输入你想传递给应用程序的参数。

      int nCmdShow
    )
     可以在任何需要的地方调用GetCommandLine()来获得一个指向进程完整命令行的指针。

                        例如: LPWSTR str=GetCommandLine();
                               _tprintf(str);
    该函数返回一个缓冲区指针 缓冲区包含完整的命令行(包括已执行文件的完整路径) 但是这里指的注意的是 这个函数返回的总是同一个缓冲区的
    的地址 这就是不应该向pszCmdLine写入数据的一个重要理由 就是一旦修改了它之后 就没有办法知道原来的命令行的地址是什么 也就不知道
    原先的命令行是什么

    4 环境变量

    每一个进程都有他自己关联的环境块 他包含了该应用程序需要使用的信息 环境变量用以指定文件的搜索路径、临时文件目录、
    特定应用程序(application-specific)的选项和其他类似信息。默认情况下,每个进程(process)的环境变量拷贝自父进程
    这里所指的父进程大都是指系统的环境变量和用户环境变量
    当然父进程也能够为子进程指定不同的环境变量。

     环境变量是进程中一组变量信息,环境变量分为系统环境变量、用户环境变量和进程环境变量。系统有全局的环境变量,
    在进程创建时,进程继承系统的全局环境变量当前登录用户的用户环境变量父进程的环境变量。进程也可以有自己的环境变量。

    有两种方式来访问环境块

    1)GetEnvironmentStrings函数用于获取所有环境变量字符串:

    LPTCH WINAPI GetEnvironmentStrings(void);

    返回值:

    成功时,返回指向保存环境变量的缓冲区;

    失败时,返回值为NULL。

    以=号开头的无效字符会被跳过 其他的有效字符会被一一解析 如果不再需要GetEnvironmentStrings函数返回的内存块 应当使用FreeEnvironmentStrings
    函数来释放它

    FreeEnvironmentStrings函数用来释放由GetEnvironmentStrings返回的内存块:

    BOOL WINAPI FreeEnvironmentStrings(

    __in LPTCH lpszEnvironmentBlock

    );

    返回值:

    成功时,返回非零值;

    失败时,返回零值,可调用GetLastError()查看进一步错误消息。

    用户登陆windows是 系统会创建外壳(shell)进程 并将一组环境字符串和它关联 然后系统会检查两个注册表信息来初始化环境变量
    第一个注册表信息包含所有的系统环境变量
    hklm_local_machine\system\currentcontrolset\control\session manager\environment
    第二个注册表的信息包含当前用户的所有环境变量
    hkey_current_user\environment 
     
    子进程会继承一组环境变量 这些环境变量和父进程的相同 但是父进程可以控制子进程的环境变量的继承 这里所说的就是继承就是指子进程
    获得父进程的环境块的一个副本 并且这个副本只是子进程专属  这就意味着子进程和父进程是不共享一个环境块的 子进程对环境变量的更改
    不会影响父进程的环境变量 下面是好i一些应用程序可能会调用的函数

    GetEnvironmentVariable函数用于获取指定的环境变量:或者是判断一个环境变量是否存在

    DWORD WINAPI GetEnvironmentVariable(

    __in_opt LPCTSTR lpName, //环境变量名

    __out_opt LPTSTR lpBuffer, //指向保存环境变量值的缓冲区

    __in DWORD nSize //缓冲区大小(字符数)

    );

    返回值:

    成功时,返回真实的环境变量值大小,不包括null结束符;

    如果lpBuffer大小不足,则返回值是实际所需的字符数大小,lpBuffer内容未定义;

    失败时,返回0;如果指定的环境变量找不到,GetLastError()返回ERROR_ENVVAR_NOT_FOUND。

    SetEnvironmentVariable函数用于设置指定的环境变量:

    BOOL WINAPI SetEnvironmentVariable(

    __in LPCTSTR lpName, //环境变量名,当该值不存在且lpValue不为NULL时,将创建一个新的

    __in_opt LPCTSTR lpValue //环境变量值

    );

    返回值:

    成功时,返回非零值;

    失败时,返回零值,调用GetLastError()查看具体的错误信息。

    该函数对系统环境变量以及其他进程的环境变量不起作用!

    1. #include <windows.h>  
    2. #include <tchar.h>  
    3. #include <stdio.h>  
    4. int _tmain()  
    5. {  
    6.     LPTSTR lpszVariable;  
    7.     LPTCH lpvEnv;  
    8.     //获得环境变量内存块的指针  
    9.     lpvEnv = GetEnvironmentStrings();  
    10.     if(lpvEnv == NULL)  
    11.     {  
    12.         printf("GetEnvironmentStrins failed(%d)/n", GetLastError());  
    13.         return 0;            
    14.     }      
    15.   
    16.     //环境变量字符串是以NULL分隔的,内存块以NULL结尾  
    17.     lpszVariable = (LPTSTR)lpvEnv;  
    18.     while(*lpszVariable)  
    19.    {  
    20.         _tprintf(TEXT("%s\n"), lpszVariable);  
    21.         lpszVariable += lstrlen(lpszVariable) + 1;   //移动指针  
    22.     }   
    23.     FreeEnvironmentStrings(lpvEnv);  
    24.   
    25.     system("pause");  
    26.     return 1;  
    27.   


    1. #include <Windows.h>  
    2. #include <tchar.h>  
    3. #include <stdio.h>  
    4. #include <strsafe.h>  
    5. #define BUFSIZE 4096  
    6.    
    7. int _tmain()  
    8. {  
    9.     TCHAR chNewEnv[BUFSIZE];  
    10.     LPTSTR lpszCurrentVariable;  
    11.     DWORD dwFlags = 0;  
    12.     TCHAR szAppName[] = TEXT("notepad");  
    13.     STARTUPINFO si;  
    14.     PROCESS_INFORMATION pi;  
    15.     BOOL fSuccess;  
    16.   
    17.     //将环境变量字符串拷贝到环境变量内存块中  
    18.     lpszCurrentVariable = (LPTSTR)chNewEnv;  
    19.     if(FAILED(StringCchCopy(lpszCurrentVariable, BUFSIZE, TEXT("AsceSetting=Luffy"))))  
    20.     {  
    21.         printf("String copy failed/n");  
    22.          
    23.     }   
    24.     
    25.     lpszCurrentVariable += lstrlen(lpszCurrentVariable) + 1;     
    26.     if(FAILED(StringCchCopy(lpszCurrentVariable, BUFSIZE, TEXT("AsceVersion=2.0"))))  
    27.     {  
    28.        printf("String copy failed/n");                            
    29.     }      
    30.   
    31.     //使环境变量内存块以NULL结尾  
    32.   
    33.     lpszCurrentVariable += lstrlen(lpszCurrentVariable) + 1;  
    34.     *lpszCurrentVariable = (TCHAR)0;  
    35.   
    36.     //创建子进程,指定一个新的环境变量内存块  
    37.     SecureZeroMemory(&si, sizeof(STARTUPINFO));  
    38.     si.cb = sizeof(STARTUPINFO);  
    39.       
    40. #ifdef UNICODE  
    41.     dwFlags = CREATE_UNICODE_ENVIRONMENT;  
    42.   
    43. #endif  
    44.      
    45.     fSuccess = CreateProcess(NULL, szAppName, NULL, NULL,  
    46.                TRUE, dwFlags, (LPVOID)chNewEnv, //新的环境变量内存块  
    47.                NULL, &si, &pi);  
    48.     if(!fSuccess)  
    49.     {  
    50.         printf("CreateProcess failed(%d)/n", GetLastError());    
    51.           
    52.     }  
    53.     CloseHandle(pi.hThread);  
    54.     CloseHandle(pi.hProcess);  
    55.     printf("新进程的ID号:%d\n",pi.dwProcessId);  
    56.     printf("新进程的主线程ID号:%d\n",pi.dwThreadId);  
    57.     WaitForSingleObject(pi.hProcess, INFINITE);  
    58.     system("pause");  
    59.     return TRUE;  
    60. }