前言

进程(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, //该结构确定子进程是否可以继承返回到新进程对象的句柄,如果为NULL则不能继承
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //该结构确定子进程是否可以继承返回到新线程对象的句柄,如果为NULL则不能继承
_In_ BOOL bInheritHandles, //参数若为TRUE则新进程继承调用进程的每个可继承句柄。如为FALSE则不会继承句柄
_In_ DWORD dwCreationFlags, //控制优先级别和流程创建的标识
_In_opt_ LPVOID lpEnvironment, //指向新进程的环境块的指针,若为NULL则新进程将使用调用进程的环境
_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(){

//Chrome路径
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调用系统命令了。


进程间通信方式

  1. socket ip和端口
  2. 剪切板 剪切板的内核对象
  3. 邮槽 邮槽的内核对象
  4. 匿名管道(无名管道)
  5. 命名管道
  6. 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(){
//打开剪切板 成功返回TRUE
if (OpenClipboard()){
//打开之后首先清空剪切板
EmptyClipboard();
char *szSendBuf;

//获取SEND框的内容
CString strSend{};
GetDlgItemText(IDC_EDIT_SEND,strSend);

//分配一个内存对象,内存对象的句柄就是hclip
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, //指针,该变量接收时同步hFile读取的字节数
_Inout_opt_ LPOVERLAPPED lpOverlapped //默认NULL
);

完整的这个服务端接收功能

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
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);
//1句柄 2消息类型 3主窗口 4copy结构体
::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封装数据和解析数据,使用起来也挺方便,不过数据量比较大的话就建议用命名管道。

说参数是挺无聊的,但是实现一下也还行。