进程
程序是计算机指令的集合,它以文件的形式存储在磁盘上。
进程通常上被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动,一个程序可以对应多个进程。
进程是资源申请,高度和独立运行的单位,因此,它使用系统中的运行资源,而程序不能申请使用系统资源,不能被系统高度也不能作为独立运行的单位,因此它不占系统运行资源。
进程的组成
内核对象也是系统用来存放关于进程的统计信息的地方,内核对象是操作系统内部分配的一个内在块,该内存块是一种数据结构,其成员负责维护该对象的各种信息。
它包含所有可执行模块或DLL模块的代码和数据,另外,它也包含动态内存分配的地址空间,例如线程的栈和堆分配空间。
- 进程从来不执行任何东西,它只是纯粹的容器,或说是线程的执行环境。
若要使它完成某项操作,它必须拥有一个在它环境中运行的的线程,次线程负责执行包含在进程的地址空间中的代码,也就是,真正完成代码执行的线程。
子进程
子进程还是一个进程,指的是由另一个进程(对应称之为父进程)所创建的进程。
单任务的同步机制——线程、子进程都可以实现。
需要保护地址空间。
子进程的线程既可以在父进程终止之后执行代码,也可以在父进程运行的过程中执行代码。
创建进程
CreateProcessW
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| CreateProcess函数 CreateProcessW( _In_opt_ LPCWSTR lpApplicationName, _Inout_opt_ LPWSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCWSTR lpCurrentDirectory, _In_ LPSTARTUPINFOW lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInformation );
|
示例:创建一个用firefox打开bing的进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include<stdio.h> #include<tchar.h> #include<windows.h> void RunExe() { TCHAR szlpCommandLine[] = _T("\"D:\\Mozilla Firefox\\firefox.exe\"https://cn.bing.com/"); STARTUPINFO strStartupInfo; memset(&strStartupInfo,0,sizeof(strStartupInfo)); strStartupInfo.cb = sizeof(strStartupInfo); PROCESS_INFORMATION szProcessInformation; memset(&szProcessInformation,0,sizeof(szProcessInformation)); int iRet = CreateProcess(NULL, szlpCommandLine, NULL, NULL, false,CREATE_NEW_CONSOLE,NULL,NULL,&strStartupInfo,&szProcessInformation);
if (iRet) { WaitForSingleObject(szProcessInformation.hProcess,3000); CloseHandle(szProcessInformation.hProcess); CloseHandle(szProcessInformation.hThread); szProcessInformation.dwProcessId = 0; szProcessInformation.dwThreadId = 0; szProcessInformation.hThread = 0; szProcessInformation.hProcess = 0; printf_s("Success iRet = %d\n",iRet);
} else { printf_s("Create Failed iRet = %d,errorcode =%d\n",iRet,GetLastError()); }
} int main(void) {
printf("test\n"); RunExe(); system("pause"); return 0; }
|
进程间的通信方式
- socket编程——IP和端口
- 剪贴板——剪贴板的内核对象
- 邮槽——邮槽的内核对象
- 匿名管道——内核对象
- 命名管道——内核对象
- Copy_data findwindows wm_copydata——消息sendmessage
剪贴板
系统维护管理的一块内存区域。
原理:当一个进程在复制数据时,是将数据放到内存区域中,当另一个进程在粘贴数据时,从该内存区域取出数据,显示到窗口上面。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| void CClipboardDlg::OnBnClickedButton2() { if (OpenClipboard()) { EmptyClipboard(); char* szSendBuf; CStringW strSendW; GetDlgItemText(IDC_EDIT_SEND, strSendW);
CStringA strSend = (CStringA)strSendW;
HANDLE hClip = GlobalAlloc(GMEM_MOVEABLE, strSend.GetLength() + 1); szSendBuf = (char*)GlobalLock(hClip); strcpy(szSendBuf, strSend); TRACE("szSendBuf = %s", szSendBuf); GlobalUnlock(hClip); SetClipboardData(CF_TEXT, hClip); CloseClipboard(); }
}
void CClipboardDlg::OnBnClickedButton3() { if (OpenClipboard()) { if (IsClipboardFormatAvailable(CF_TEXT)) { HANDLE hClip; char* pBuf; hClip = GetClipboardData(CF_TEXT); pBuf = (char*)GlobalLock(hClip); USES_CONVERSION; LPCWSTR strBuf = A2W(pBuf); GlobalUnlock(hClip); SetDlgItemText(IDC_EDIT_RECV, strBuf); } CloseClipboard(); }
}
|
![image-20220103195035740](https://gitee.com/Do2eM0N/blogimg/raw/master/202201051810854.png)
邮槽
使用邮槽通信的进程分为服务端和客户端。邮槽有服务端创建,在创建时需要指定邮槽名,创建之后服务端得到邮槽的句柄 。在邮槽创建后,客户端可以通过邮槽名的打开邮槽,在获得句柄后可以向邮槽写入消息。
邮槽通信是单向的,只有服务端才能从邮槽中读取消息,客户端只能写入消息。消息是先入先出的。客户端先写入的消息在服务端先被读取。
通过邮槽通信的数据可以是任意格式的,但是一条消息不能大于424字节。
邮槽除了在本机内进程进程间通信外,在主机间也可以通信。在主机间进程邮槽通信时,数据通过网络传播时使用的是数据包协议(UDP),所以是一种不可靠通信。通过网络进程邮槽通信时,客户端必须知道服务端的主机名或域名。
示例:
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| void CChildView::OnSlot() {
LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot"); HANDLE hSlot = CreateMailslot(szSlotName, 0, MAILSLOT_WAIT_FOREVER, NULL);
if (hSlot == INVALID_HANDLE_VALUE) { TRACE("CreateMailslot failed with %d\n", GetLastError()); return; } char szBuf[100] = { 0 }; DWORD dwRead; TRACE("Begin ReadFile"); if (!ReadFile(hSlot, szBuf, 100, &dwRead, NULL)) { MessageBox(_T("读取数据失败")); CloseHandle(hSlot); return; } TRACE("End ReadFile"); MessageBox((CStringW)szBuf); CloseHandle(hSlot); }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void CChildView::OnSend() { LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot"); HANDLE hMailSlot = CreateFile(szSlotName,FILE_GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if (hMailSlot == INVALID_HANDLE_VALUE) { TRACE("CreateMail fail with %d\n",GetLastError()); return; } char szBuf[] = "ZYX is handsome"; DWORD dwWrite; if (!WriteFile(hMailSlot,szBuf,strlen(szBuf)+1,&dwWrite,NULL)) { MessageBox(_T("写入数据失败")); CloseHandle(hMailSlot); return; } CloseHandle(hMailSlot); }
|
匿名管道
匿名管道是一个没有命名的单向管道,本质上就是一个共享的内存,抽象成是管道。
通常用来在父进程和子进程之间通信。只能实现本地两个进程之间的通信。不能实现网络通信。
优点是效率高,原理本质上就是共享内存。
CreatePipe
1 2 3 4 5 6
| CreatePipe( _Out_ PHANDLE hReadPipe, _Out_ PHANDLE hWritePipe, _In_opt_ LPSECURITY_ATTRIBUTES lpPipeAttributes, _In_ DWORD nSize );
|
命名管道
与Socket相似,支持网络之间进程的通信。
CreateNamePipe
1 2 3 4 5 6 7 8 9 10
| HANDLE CreateNamedPipeA( LPCSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes );
|
ConnectNamePipe
1 2 3 4
| BOOL ConnectNamedPipe( HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped );
|
示例:
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| void CChildView::OnCreateNamePipe() { LPCTSTR szhPipeName = TEXT("\\\\.\\pipe\\mypipe"); hNamedPipe = CreateNamedPipe(szhPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 1, 1024, 1024, 0, NULL); if (hNamedPipe == INVALID_HANDLE_VALUE) { TRACE("CreateNamedPipe failed with %d\n", GetLastError()); MessageBox(_T("创建命名管道失败")); return; } HANDLE hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); if (hEvent == NULL) { MessageBox(_T("创建事件失败")); CloseHandle(hNamedPipe); hNamedPipe = NULL; return; } OVERLAPPED ovlap; ZeroMemory(&ovlap,sizeof(OVERLAPPED)); ovlap.hEvent = hEvent; if (!ConnectNamedPipe(hNamedPipe, &ovlap)) { if(GetLastError() != ERROR_IO_PENDING) { MessageBox(_T("等待客户端连接失败")); CloseHandle(hNamedPipe); CloseHandle(hEvent); hNamedPipe = NULL; hEvent = NULL; return; } } if (WaitForSingleObject(hEvent, INFINITE) == WAIT_FAILED) { MessageBox(_T("等待对象失败")); CloseHandle(hNamedPipe); CloseHandle(hEvent); hNamedPipe = NULL; hEvent = NULL; return; } }
void CChildView::OnSreadNamePipe() { char szBuf[100] = { 0 }; DWORD dwRead; if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL)) { MessageBox(_T("读取数据失败")); return; } MessageBox((CStringW)szBuf); }
void CChildView::OnSwriteNamePipe() { char szBuf[] = "ZYX is handsome Server"; DWORD dwWrite; if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL)) { MessageBox(_T("写入数据失败")); return; } }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| void CChildView::OnConNamePipe() { LPCTSTR szNamePipeName = TEXT("\\\\.\\pipe\\mypipe");
if (WaitNamedPipe(szNamePipeName, NMPWAIT_WAIT_FOREVER) == 0) { MessageBox(_T("当前没有可以利用的管道")); return; } hNamedPipe = CreateFile(szNamePipeName,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if (hNamedPipe == INVALID_HANDLE_VALUE) { MessageBox(_T("打开命名管道失败!")); hNamedPipe = NULL; return; } }
void CChildView::OnReadNamePipe() { char szBuf[100] = { 0 }; DWORD dwRead; if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL)) { MessageBox(_T("读取数据失败")); return; } MessageBox((CStringW)szBuf); }
void CChildView::OnWriteNamePipe() { char szBuf[] = "ZYX is handsome Client"; DWORD dwWrite; if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL)) { MessageBox(_T("写入数据失败")); CloseHandle(hNamedPipe); return; } CloseHandle(hNamedPipe); }
|
WM_COPYDATA
利用WM_COPYDATA这个消息进行通信。
是最常用、最灵活的进程间通信方式。
一个应用程序发送WM_COPYDATA消息以将数据传递给另一个应用程序。
SPY++专门够用来查找窗口句柄。
要给进程发送数据,首先要拿到该窗口的句柄,也就是要拿到标题(因为句柄有可能会发生变化)。
发送端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void CWMCOPYDATASENDDlg::OnBnClickedSend() { CString strWindowTitle = _T("MFCRecv"); CString strDataToSend = _T("Hello"); HWND hRecvWnd = ::FindWindow(NULL, strWindowTitle.GetBuffer(0)); if (hRecvWnd != NULL && ::IsWindow(hRecvWnd)) { COPYDATASTRUCT cpd; cpd.dwData = 0; cpd.cbData = strDataToSend.GetLength()*sizeof(TCHAR); cpd.lpData = (PVOID)strDataToSend.GetBuffer(0); ::SendMessage(hRecvWnd,WM_COPYDATA,(WPARAM)(AfxGetApp()->m_pMainWnd),(LPARAM)&cpd); } strDataToSend.ReleaseBuffer(); }
|
接收端:
1 2 3 4 5 6 7 8 9 10 11 12
| BOOL CWMCOPYDATADlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct) { LPCTSTR szText = (LPCTSTR)(pCopyDataStruct->lpData); DWORD dwLength = pCopyDataStruct->cbData; TCHAR szRecvText[1024] = { 0 }; memcpy(szRecvText,szText,dwLength); MessageBox(szRecvText, _T("Bingo"), MB_OK); return CDialogEx::OnCopyData(pWnd, pCopyDataStruct); }
|
比较&总结
- 剪贴板比较简单,剪贴板和匿名管道只能实现同一机器的两个进程通信。而不能实现网络进程之间的通信。
- 邮槽是基于广播的,可以一对多发送。但只能一个发送,一个接收(单向)。
- 命名管道和邮槽可以进程网络通信。命名管道只能是点对点的单一通信。
- 邮槽的缺点就是传输的数据量很小,424字节以下。
- WM_COPYDATA封装数据非常方便,如果数据量较大,建议使用命名管道。