转载自: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程序:
- #include <sstream>
- #include <Windows.h>
- //独一无二的类名,一般用GUID字串,以免与其他程序的类名重复
- static const wchar_t * CLASS_NAME =L"{198CEAB2-AD78-4ed3-B099-247639080CB0}";
- /************************************************************************
- 回调函数,当主窗口收到任何Windows消息时被调用
- ************************************************************************/
- LRESULT CALLBACK onMainWndMessage(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
- {
- switch (msg) {
- case WM_DESTROY:
- PostQuitMessage(0); //如果是“窗口销毁”事件,则应该在消息队列中投递
- break; //一个WM_QUIT消息,使GetMessage()返回FALSE
- default:
- return DefWindowProc(wnd, msg, wParam, lParam);
- }
- return 0;
- }
- /************************************************************************
- 登记自己的窗口类
- ************************************************************************/
- bool registerMyClass()
- {
- WNDCLASSEX wce = {0};
- wce.cbSize = sizeof(wce);
- wce.style = CS_VREDRAW | CS_HREDRAW;
- wce.lpfnWndProc = &onMainWndMessage; //指明回调函数
- wce.hInstance = GetModuleHandle(0);
- wce.hIcon = LoadIcon(0, MAKEINTRESOURCE(IDI_WINLOGO));
- wce.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW));
- wce.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1);
- wce.lpszClassName = CLASS_NAME; //独一无二的类名
- wce.hIconSm = wce.hIcon;
- return 0 != RegisterClassEx(&wce);
- }
- /************************************************************************
- 创建并显示主窗口
- ************************************************************************/
- bool createMyWindow(int cmdShow)
- {
- HWND mainWnd = CreateWindowEx(0, CLASS_NAME, L"Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
- CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, GetModuleHandle(0), 0);
- if (0 != mainWnd) {
- ShowWindow(mainWnd, cmdShow);
- UpdateWindow(mainWnd);
- return true;
- } else {
- return false;
- }
- }
- /************************************************************************
- 消息循环
- ************************************************************************/
- int messageLoop()
- {
- MSG msg;
- while (GetMessage(&msg, 0, 0, 0)) {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- return static_cast<int>(msg.wParam);
- }
- /************************************************************************
- WinMain,程序入口
- ************************************************************************/
- int WINAPI WinMain(HINSTANCE, HINSTANCE, char *, int cmdShow)
- {
- if (registerMyClass() && createMyWindow(cmdShow))
- {
- return messageLoop();
- } else {
- std::ostringstream msg;
- msg << "创建主窗口失败,错误代码:" << GetLastError();
- MessageBoxA(0, msg.str().c_str(), 0, MB_OK | MB_ICONSTOP);
- return 0;
- }
- }
创建一个CUI程序
- #include <iostream>
- #include <windows.h>
- #include <tchar.h>// #define _tmain main
- int _tmain(int argc,TCHAR *argv[],TCHAR *envp[])
- {
- getchar();
- return 0;
- }
在集成开发环境里面 每一个开发环境会为可执行文件添加一个映像的头问价 操作加载这个程序的时候 会检测这个头文件 从而向系统表明这个可执行文件时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的话,可以直接返回主调进程的可执行文件的基地址。
- #define DLLWITHGETMODULE extern "C" __declspec(dllexport)
- #include <Windows.h>
- #include <stdio.h>
- #include <tchar.h>
- extern "C" const IMAGE_DOS_HEADER __ImageBase; //伪变量,指向当前正在运行的模块的基地址
- void DumpModule()
- {
- HMODULE hModule = GetModuleHandle(NULL);//得到当前正在运行的应用程序的基地址
- _tprintf(TEXT("with GetModuleHandle(NULL) = 0x%x\r\n"),hModule);
- _tprintf(TEXT("with __ImageBase = 0x%x\r\n"),(HINSTANCE)&__ImageBase);//使用伪变量得到当前正在运行模块的基地址
- //传入DumpModule函数的地址给GetModuleHandleEx函数,得到DumpModule函数所在模块的基地址
- hModule = NULL;
- ::GetModuleHandleEx(
- GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
- (PCTSTR)DumpModule,
- &hModule);
- _tprintf(TEXT("with GetModuleHandleEx = 0x%x\r\n"),hModule);
- }
- int _tmain(int argc,TCHAR* argv[])
- {
- DumpModule();
- return 0;
- }
这里需要注意的有两点 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()查看具体的错误信息。
该函数对系统环境变量以及其他进程的环境变量不起作用!
- #include <windows.h>
- #include <tchar.h>
- #include <stdio.h>
- int _tmain()
- {
- LPTSTR lpszVariable;
- LPTCH lpvEnv;
- //获得环境变量内存块的指针
- lpvEnv = GetEnvironmentStrings();
- if(lpvEnv == NULL)
- {
- printf("GetEnvironmentStrins failed(%d)/n", GetLastError());
- return 0;
- }
- //环境变量字符串是以NULL分隔的,内存块以NULL结尾
- lpszVariable = (LPTSTR)lpvEnv;
- while(*lpszVariable)
- {
- _tprintf(TEXT("%s\n"), lpszVariable);
- lpszVariable += lstrlen(lpszVariable) + 1; //移动指针
- }
- FreeEnvironmentStrings(lpvEnv);
- system("pause");
- return 1;
- #include <Windows.h>
- #include <tchar.h>
- #include <stdio.h>
- #include <strsafe.h>
- #define BUFSIZE 4096
- int _tmain()
- {
- TCHAR chNewEnv[BUFSIZE];
- LPTSTR lpszCurrentVariable;
- DWORD dwFlags = 0;
- TCHAR szAppName[] = TEXT("notepad");
- STARTUPINFO si;
- PROCESS_INFORMATION pi;
- BOOL fSuccess;
- //将环境变量字符串拷贝到环境变量内存块中
- lpszCurrentVariable = (LPTSTR)chNewEnv;
- if(FAILED(StringCchCopy(lpszCurrentVariable, BUFSIZE, TEXT("AsceSetting=Luffy"))))
- {
- printf("String copy failed/n");
- }
- lpszCurrentVariable += lstrlen(lpszCurrentVariable) + 1;
- if(FAILED(StringCchCopy(lpszCurrentVariable, BUFSIZE, TEXT("AsceVersion=2.0"))))
- {
- printf("String copy failed/n");
- }
- //使环境变量内存块以NULL结尾
- lpszCurrentVariable += lstrlen(lpszCurrentVariable) + 1;
- *lpszCurrentVariable = (TCHAR)0;
- //创建子进程,指定一个新的环境变量内存块
- SecureZeroMemory(&si, sizeof(STARTUPINFO));
- si.cb = sizeof(STARTUPINFO);
- #ifdef UNICODE
- dwFlags = CREATE_UNICODE_ENVIRONMENT;
- #endif
- fSuccess = CreateProcess(NULL, szAppName, NULL, NULL,
- TRUE, dwFlags, (LPVOID)chNewEnv, //新的环境变量内存块
- NULL, &si, &pi);
- if(!fSuccess)
- {
- printf("CreateProcess failed(%d)/n", GetLastError());
- }
- CloseHandle(pi.hThread);
- CloseHandle(pi.hProcess);
- printf("新进程的ID号:%d\n",pi.dwProcessId);
- printf("新进程的主线程ID号:%d\n",pi.dwThreadId);
- WaitForSingleObject(pi.hProcess, INFINITE);
- system("pause");
- return TRUE;
- }