前言
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
——百度百科
正文
基本情况下,我们查看任务进程,包括kill一个未响应的进程,都会在任务管理器中操作。
不过微软自己有个进程资源管理器看到的会更详细一点,链接戳此处
注:需要科学上网,不然很慢
进程实际上就一个纯粹的容器,进程本身不执行任何东西。代码的实现靠的的是线程,进程只相当于一个环境。
子进程也还是一个进程,它是指由另一个进程(对应称为父进程)所创建的进程。
子进程的线程既可以在父进程终止之后执行代码,也可以在父进程运行过程中执行代码。
创建进程
CreateProcess
用来创建进程的函数。
其原型,参数一贯的又臭又长
1 2 3 4 5 6 7 8 9 10 11 12
| 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 );
|
具体可以去看文档。因为Windows API套娃严重。。他这里的参数还有结构体。有点绷不住。
然后自制进程的话不太理想,反正打开一个程序也是一个进程,就直接配置打开一个程序
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
| #include <stdio.h> #include <windows.h> #include <process.h>
int RunExe(){ STARTUPINFO strStartup; memset(&strStartup, 0, sizeof(strStartup)); strStartup.cb = sizeof(strStartup); PROCESS_INFORMATION szProcessInformation; memset(&szProcessInformation, 0, sizeof(szProcessInformation));
TCHAR szCommandLine[] = L"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"; int ret = CreateProcess( NULL, szCommandLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &strStartup, &szProcessInformation );
if (ret){ printf("Create success ret = %d\n", ret); WaitForSingleObject(szProcessInformation.hProcess,INFINITE); CloseHandle(szProcessInformation.hProcess); CloseHandle(szProcessInformation.hThread); } else{ printf("Create error!\n"); }
return 0; }
int main(){ printf("This is Chrome\n"); RunExe(); system("pause");
return 0; }
|
memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。
可以看到是成功启动了我们的谷歌浏览器,但是自己用的时候要注意,文件路径每个人多少都有不同,所以不要直接套娃。
除了直接打开.exe,还可以通过命令行的方式让他打开网页
TCHAR szCommandLine[] = L"\"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe\"https://www.baidu.com";
如此设置之后,run的时候就能看到浏览器直接打开百度的首页了。
并且进程对象的成员也使得我们能够看到相对应的id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| if (ret){ printf("Create success ret = %d\n", ret); WaitForSingleObject(szProcessInformation.hProcess,INFINITE);
printf("szProcessInformation.hProcess = %d\n", szProcessInformation.hProcess); printf("szProcessInformation.hThread = %d\n", szProcessInformation.hThread); printf("szProcessInformation.dwProcessId = %d\n", szProcessInformation.dwProcessId); printf("szProcessInformation.dwThreadId = %d\n", szProcessInformation.dwThreadId);
CloseHandle(szProcessInformation.hProcess); CloseHandle(szProcessInformation.hThread); } else{ printf("Create error!\n"); }
|
当然用这个调用cmd去执行一些命令好像有点问题,而且有点笨,它都可以直接通过system调用系统命令了。
进程间通信方式
- socket ip和端口
- 剪切板 剪切板的内核对象
- 邮槽 邮槽的内核对象
- 匿名管道(无名管道)
- 命名管道
- Copy_data findwindows wm_copudata Sendmessage
剪切板
新建项目mfc
选择基于对话框,然后就点完成
打开之后长这样就差不多了。
运行一下,跟里面看的差不多
加下来要用到资源视图,如果没打开的可以参照
因为之前看过qt,所以拖动组件和修改id之类的不是什么问题。
做成这样之后双击按钮进入代码实现部分,注意修改项目为多字节,unicode有点问题先不管了,但是可以提一嘴,就是接收的时候需要转换
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
| void CCopyBoardDlg::OnBnClickedSendbutton(){ if (OpenClipboard()){ EmptyClipboard(); char *szSendBuf;
CString strSend{}; GetDlgItemText(IDC_EDIT_SEND,strSend);
HANDLE hClip = GlobalAlloc(GMEM_MOVEABLE, strSend.GetLength()+1);
szSendBuf = (char*)GlobalLock(hClip); strcpy(szSendBuf, strSend);
GlobalUnlock(hClip);
SetClipboardData(CF_TEXT, hClip);
CloseClipboard(); } }
void CCopyBoardDlg::OnBnClickedRecvbutton(){ if (OpenClipboard()){ if (IsClipboardFormatAvailable(CF_TEXT)){ char *szRecvBuf;
HANDLE hClip = GetClipboardData(CF_TEXT); szRecvBuf = (char *)GlobalLock(hClip); SetDlgItemText(IDC_EDIT_RECV, szRecvBuf); GlobalUnlock(hClip); } CloseClipboard(); } }
|
最后实现部分就是这样,在左边的内容框输入,点击发送之后,再点击接收,右边的框就有内容了
可能会有疑惑,为什么说是同步。这个程序看似就是一个进程之间的事情。
实际上因为我们调用了剪切板,所以当我们点击发送的时候,我们系统的剪切板就有了内容,所以我们可以直接在别的地方粘贴出来。
就像这样,我可以直接在项目的源文件中粘贴出来。
当然也可以新建一个作为桥梁嘛。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void CCopyBoardCliDlg::OnBnClickedButtonrecv(){ if (OpenClipboard()){ if (IsClipboardFormatAvailable(CF_TEXT)){ char *szRecvBuf;
HANDLE hClip = GetClipboardData(CF_TEXT); szRecvBuf = (char *)GlobalLock(hClip); SetDlgItemText(IDC_EDITRECV, szRecvBuf); GlobalUnlock(hClip); } CloseClipboard(); } }
|
额老样子一个直接打开一个vs打开。
效果就是我主要的点了发送以后,新写的可以直接通过接收获取。不是视频可能会有理解上的偏差。
邮槽
邮槽是比较老的通信方式了。
使用邮槽的进程分为服务端和客户端。邮槽由服务端创建,在创建时需要指定邮槽名,创建后服务端得到邮槽的句柄。在邮槽创建后,客户端可以通过邮槽名打开邮槽,在获得句柄后可以向邮槽写入消息。
邮槽通信是单项的,只有服务端才能从邮槽中读取消息,客户端只能写入消息。消息遵循先入先出的原则。客户端先写入的消息在服务端现被读取。
通过邮槽通信的数据可以是任意格式,但是一条消息不能大于424字节。
邮槽除了在本机内进行进程间通信外,在主机间也可以通信。但是在主机间进行邮槽通信。数据通过网络传播时使用的是数据报协议(UDP),所以是一种不可靠的通信。通过网络进行邮槽通信时,客户端必须知道服务端的主机名或者域名。
CreateMailslot
额,还是老样子微软会根据多字节还是Unicode会做一个区分,前者多个a,后者多个w。
其原型
1 2 3 4 5 6
| CreateMailslot( _In_ LPCWSTR lpName, _In_ DWORD nMaxMessageSize, _In_ DWORD lReadTimeout, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes );
|
然后设计这么两个东西。
先从服务器开始设计接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void CMyMailslotDlg::OnBnClickedRecvbutton(){ 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; } }
|
然后要读取数据
这一关键点需要用到一个特别的函数ReadFile
1 2 3 4 5 6 7 8 9
| ReadFile( _In_ HANDLE hFile, _Out_writes_bytes_to_opt_(nNumberOfBytesToRead, *lpNumberOfBytesRead)
__out_data_source(FILE) LPVOID lpBuffer, _In_ DWORD nNumberOfBytesToRead, _Out_opt_ LPDWORD lpNumberOfBytesRead, _Inout_opt_ 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
| void CMyMailslotDlg::OnBnClickedRecvbutton(){ 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; if (!ReadFile(hSlot, szBuf, 100, &dwRead, NULL)){ MessageBox("ReadFile error!"); return; } TRACE("---dwRead = %d\n", dwRead); MessageBox(szBuf);
CloseHandle(hSlot); }
|
然后客户端的发送功能其实也挺类似
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
| void CMymailCliDlg::OnBnClickedSendbutton(){ 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("CreateFile failed with %d\n", GetLastError()); return; }
char szBuf[100] = "MailSlot Comming"; DWORD dwWrite; if (!WriteFile(hMailSlot, szBuf, strlen(szBuf)+1, &dwWrite, NULL)){ MessageBox("Write Error!"); 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 );
|
额这里因为是要用到两个进程,就直接在邮槽上改了。
服务端
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 73 74 75 76
| HANDLE hReadPipe; HANDLE hWritePipe;
void CMyMailslotDlg::OnBnClickedSendbutton(){ char szBuf[] = "This Server Pipe"; DWORD dwWrite; if (!WriteFile(hWritePipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL)){ MessageBox(_T("写入数据失败")); return; } }
void CMyMailslotDlg::OnBnClickedRecvbutton(){ char szBuf[100] = { 0 }; DWORD dwRead; TRACE("Begin ReadFile"); if (!ReadFile(hReadPipe, szBuf, 100, &dwRead, NULL)){ MessageBox(_T("读取数据失败")); return; } TRACE("End PipeReadFile"); MessageBox(szBuf); }
void CMyMailslotDlg::OnBnClickedCreBtn(){ SECURITY_ATTRIBUTES sa; sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; sa.nLength = sizeof(SECURITY_ATTRIBUTES); if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)){ MessageBox(_T("匿名管道创建失败")); return; }
STARTUPINFO strStartupInfo; memset(&strStartupInfo, 0, sizeof(strStartupInfo)); strStartupInfo.cb = sizeof(strStartupInfo); strStartupInfo.dwFlags = STARTF_USESTDHANDLES; strStartupInfo.hStdInput = hReadPipe; strStartupInfo.hStdOutput = hWritePipe; strStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
PROCESS_INFORMATION szProcessInformation; memset(&szProcessInformation, 0, sizeof(szProcessInformation));
int iRet = CreateProcess( _T("MymailCli.exe"), NULL, NULL, NULL, TRUE, 0, NULL, NULL, &strStartupInfo, &szProcessInformation );
if (iRet){ CloseHandle(szProcessInformation.hProcess); CloseHandle(szProcessInformation.hThread); szProcessInformation.dwProcessId = 0; szProcessInformation.dwThreadId = 0; szProcessInformation.hThread = NULL; szProcessInformation.hProcess = NULL; } else{ CloseHandle(hReadPipe); CloseHandle(hWritePipe); hReadPipe = NULL; hWritePipe = 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
| HANDLE hReadPipe; HANDLE hWritePipe;
void CMymailCliDlg::OnBnClickedSendbutton(){ hWritePipe = GetStdHandle(STD_OUTPUT_HANDLE);
char szBuf[100] = "This Client Pipe"; DWORD dwWrite; if (!WriteFile(hWritePipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL)){ MessageBox(_T("写入数据失败")); return; } }
void CMymailCliDlg::OnBnClickedRecvbutton(){ hReadPipe = GetStdHandle(STD_INPUT_HANDLE);
char szBuf[100] = { 0 }; DWORD dwRead; if (!ReadFile(hReadPipe, szBuf, 100, &dwRead, NULL)){ MessageBox(_T("读取数据失败")); return; } MessageBox(szBuf); }
|
客户端因为不用管邮槽先,就通过句柄传送。
效果
根据按钮打开新进程。
然后相互点击发送和接收
服务端发送 客户端接收
客户端发送 服务端接收
大致就是这么一个效果。
继续插一嘴:匿名管道只能实现本地两个进程之间的通信,不能实现网络通信。
命名管道
与Socket相似,支持网络之间不同进程的通信
既然也能通信,自然也能通过c/s模式实现
CreateNamePipe
1 2 3 4 5 6 7 8 9 10
| CreateNamedPipeA( _In_ LPCSTR lpName, _In_ DWORD dwOpenMode, _In_ DWORD dwPipeMode, _In_ DWORD nMaxInstances, _In_ DWORD nOutBufferSize, _In_ DWORD nInBufferSize, _In_ DWORD nDefaultTimeOut, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes );
|
插一句,项目有些时候都用多字节的,Unicode有的时候要转换太麻烦了。
服务端
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
| HANDLE hNamedPipe;
void CmyNamePipeSerDlg::OnBnClickedProBtn(){ LPCTSTR szPipeName = TEXT("\\\\.\\pipe\\mypipe"); hNamedPipe = CreateNamedPipe( szPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 1, 1024, 1024, 0, NULL );
if (hNamedPipe == INVALID_HANDLE_VALUE){ TRACE("CreateNamePipe failed with %d\n", GetLastError()); MessageBox(_T("创建命名管道失败")); return; }
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (NULL == hEvent){ MessageBox(_T("创建事件失败")); CloseHandle(hNamedPipe); hNamedPipe = NULL; return; }
OVERLAPPED ovlap; ZeroMemory(&ovlap, sizeof(OVERLAPPED)); ovlap.hEvent = hEvent;
if (!ConnectNamedPipe(hNamedPipe, &ovlap)){ if (ERROR_IO_PENDING != GetLastError()){ 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; } }
|
创建命名管道其实也还好,但是为了一些安全考虑,做了一些措施,本质上都是copy来的。
至于send和recv其实换汤不换药
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void CmyNamePipeSerDlg::OnBnClickedSendBtn(){ char szBuf[] = "This Named Pipe From Server"; DWORD dwWrite;
if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL)){ MessageBox(_T("WriteFile Failed!!!")); return; } }
void CmyNamePipeSerDlg::OnBnClickedRecvbtn(){ char szBuf[100] = { 0 }; DWORD dwRead;
if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL)){ MessageBox(_T("ReadFile Failed!!!")); return; } MessageBox(szBuf); }
|
可以说跟之前的一个模样的~
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| HANDLE hNamedPipe;
void CmyNamePipeCliDlg::OnBnClickedConnBtn(){ LPCTSTR szPipeName = TEXT("\\\\.\\pipe\\mypipe");
if (0 == WaitNamedPipe(szPipeName,NMPWAIT_WAIT_FOREVER)){ MessageBox(_T("当前没有可以利用的管道")); return; }
hNamedPipe = CreateFile( szPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if (hNamedPipe == INVALID_HANDLE_VALUE){ TRACE("CreateFile failed with %d\n", GetLastError()); CloseHandle(hNamedPipe); hNamedPipe = NULL; 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
| void CmyNamePipeCliDlg::OnBnClickedSendBtn(){ char szBuf[100] = "Named of Pipe Client"; DWORD dwWrite;
if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL)){ MessageBox(_T("WriteFile Failed!!!")); CloseHandle(hNamedPipe); return; } }
void CmyNamePipeCliDlg::OnBnClickedRecvBtn(){ char szBuf[100] = { 0 }; DWORD dwRead;
if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL)){ MessageBox(_T("ReadFile Failed!!!")); CloseHandle(hNamedPipe); return; }
MessageBox(szBuf); }
|
测试
首先肯定是先编译了。
可以看到在没有建立管道的时候,点击肯定是报错的。
老样子,点击建立,然后客户端连接之后
服务器发送 客户端接收
客户端发送 服务器接收
ok没啥问题~
除非要考到,不然这玩意我是记不住,看看文档差不多了。
WM_COPUDATA
wParam
传递数据的窗口的句柄
COPYDATASTRUCT
1 2 3 4 5
| typedef struct tagCOPYDATASTRUCT { ULONG_PTR dwData; DWORD cbData; _Field_size_bytes_(cbData) PVOID lpData; } COPYDATASTRUCT, *PCOPYDATASTRUCT;
|
SPY++
工具专门用来查找窗口句柄
这玩意内置在vs里面了,在顶部工具栏里面
打开之后就是这么一个东西
然后点窗口搜索,它可以拖动到指定的窗口去获取句柄
这里拖到我们的vs22上面,可以看到能看到标题和句柄。
拿到这样的句柄之后可以进行通信,不过也要能写代码。不然结构不一样它也没有发送接收什么的。
这里也直接用上面写的修改一下就行了。
客户端发送
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void CmyNamePipeCliDlg::OnBnClickedSendBtn(){ CString strWinTitle = _T("服务端"); CString strMsg = _T("Client COPYDATA");
HWND hwnd = ::FindWindow(NULL, strWinTitle.GetBuffer(0));
if (hwnd != NULL && IsWindow(hwnd)){ COPYDATASTRUCT cpd; cpd.dwData = 0; cpd.cbData = strMsg.GetLength() * sizeof(TCHAR); cpd.lpData = (PVOID)strMsg.GetBuffer(0); ::SendMessage(hwnd, WM_COPYDATA, (WPARAM)(AfxGetApp()->m_pMainWnd),(LPARAM) & cpd); }
strWinTitle.ReleaseBuffer(); strMsg.ReleaseBuffer(); }
|
服务端通过wm_copydata自动接收
1 2 3 4 5 6 7 8 9 10
| BOOL CmyNamePipeSerDlg::OnCopyData(CWnd *pWnd, COPYDATASTRUCT *pCopyDataStruct){ LPCTSTR szText = (LPCTSTR)(pCopyDataStruct->lpData); DWORD dwLength = (DWORD)pCopyDataStruct->cbData; TCHAR szRecvText[1024] = { 0 }; memcpy(szRecvText, szText, dwLength); MessageBox(szRecvText, _T("Y"), MB_OK);
return CDialogEx::OnCopyData(pWnd, pCopyDataStruct); }
|
这个框架是通过编译器造的不用自己敲,里面的实现要自己来
右击图形界面,找到类向导打开,消息里面搜索然后双击就出来了
最后跑一下
当客户端点击发送的时候,服务端此刻不需要点击接收才能收到消息,而是自动的收到了消息。
这种方式可能用的会比较多。
结语
剪切板比较简单,他和匿名管道一样只能实现同一个机器的两个进程通信,不能跨网络。
邮槽基于广播,可以一对多发送,但是只能一个发一个收,要同时进行就要多写点代码。邮槽传输的数据量较小,只能是424字节一下。
命名管道和邮槽都可以进行网络通信,命名管道是点对点的单一通信。
WM_COPYDATA封装数据和解析数据,使用起来也挺方便,不过数据量比较大的话就建议用命名管道。
说参数是挺无聊的,但是实现一下也还行。