进程间通信
- 通过 窗口的消息机制发送信息
- 通过文件交流
- 通过tcp/udp 交流
- 创建一个空进程专门用来存储数据(不安全)
附加: 内存申请的状态
MEM_RESERVE //申请空间 ,暂不使用
MEM_RESET //表示不再使用空间,可以回收
MEM_RESET_UNDO //表示返回了 想撤销 MEM_RESET 操作,返回0的话表示内存已被更改,撤销失败
MEM_COMMIT //表示 直接 申请并且划分一片空间
方案一: 在异进程中 申请内存空间
- 假设
A
向B
发送信息 A
在B
进程中申请一片空间,并且在里面写入数据B
直接就可以 获取A
写入的信息virtualallocex
负责在异进程中申请空间
//我们先 在 两边 都 定义一个窗口编号
#define WM_MYMSG WM_USER + 1
发送方cpp
//进程标示符
DWORD dwPID = 0;
//1:获取接收信心的窗口的句柄
HWND hRecv = FindWindowA(NULL, "Recver");
//2:通过窗口句柄 获取 进程标示符
GetWindowThreadProcessId(hRecv, &dwPID);
//3:根据进程标示符 打开进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,
TRUE,
dwPID);
//4:根据进程句柄 申请内存空间
LPVOID addr =VirtualAllocEx(hProcess,
(LPVOID)NULL,
0x256,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
//5:准备 要传递的信息
char* pBuf = new char[256];
strcpy_s((char*)addr, 256, "Hello World!");
//保存 写入结果的变量
DWORD dwWritedBytes = 0;
//6: 写入信息
WriteProcessMemory(hProcess,
addr,
"Hello World!",
strlen("Hello World!") + 1,
&dwWritedBytes);
/*
7: 信息写入完成后 向接收者窗口 发送 信息消息
WPARAM 是我们申请到的地址
LPARAM 是内存片的大小
*/
SendMessage(hRecv, WM_MYMSG, (WPARAM)addr, strlen("Hello World!") + 1);
//8: 接收方处理完成后在释放消息
VirtualFreeEx(hProcess, addr, 0x256, MEM_RELEASE);
接收方cpp
case WM_MYMSG://捕获 我们自定义的消息编号
{
char* pBuf = (char*)wParam;
MessageBoxA(NULL, pBuf, NULL, NULL);
}
方案二: WM_COPYDATA
- win已经封装了一个消息传递机制
- 发送
WM_COPYDATA
消息即可 - 操作系统会首先将发送的数据拷贝到高2GB共享区,然后再拷贝到接收方的用户空间中,所以会有两次拷贝,因此在数据量不大的时候,可以使用此方法。
操作步骤:
SendMessage
时系统把我们的消息 复制到系统内存区间WM_COPYDATA
消息处理时 把传递的消息 复制给lParam
- 我们 强转
lParam
就获取到了 数据结构体
发送方 CPP
//1: 获取 接收者的窗口句柄
HWND hRecv = FindWindowA(NULL, "Recver");
//2: 准备 专用的 信息传送结构体
COPYDATASTRUCT cds;
//3: 填充结构体
cds.dwData = 1;//这个是自定义标志,建议用来标志信息类型
cds.cbData = strlen("Hello World!") + 1;//传递信息长度
cds.lpData = "Hello World!";//信息的实体
//4: 发送 窗口消息/传递消息
SendMessage(hRecv, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds);
接收方 CPP
case WM_COPYDATA:// 捕获 消息
{
//获取消息结构体
PCOPYDATASTRUCT pCds = (PCOPYDATASTRUCT)lParam;
//提取消息
MessageBoxA(NULL, (LPCSTR)pCds->lpData, NULL, NULL);
}
break;
方案三: DLL 共享节
- 创建一个节
在 代码中件 强制 赋予 共享属性
- link
- def 文件
项目
->属性
->命令行
- 别忘了在变量前添加
__declspec(dllexport)
- 使用方直接
隐式/静态 加载
饥渴使用共享的数据节了 - 使用时 署名
extern __declspec(dllimport)
定义数据节
dll 的cpp中我们定义 是共享数据节
//数据节开始,我们定义这个数据节的名称为 MY_DATA
#pragma data_seg("MY_DATA")
/*
这里写我们需要共享的数据
一定要先初始化
*/
int g_nData = 2;
char g_szBuff[MAXBYTE] = "Hello";
//结束 数据节
#pragma data_seg()
赋予共享属性
- 上一步我们已经点一好了我们需要共享的 数据节点
- 但是这些 数据结 默认不是共享的,不同程序中仍然有不同数据
- 我们要强制赋予她们 共享属性,一共三种方法
方案1 link 说明
//我们 直接给 名为 MY_DATA 的数据节 设置为 rws权限
#pragma comment(linker,"/section:MY_DATA,rws")
方案2 编译器设定
我们链接的时候添加以下参数
/SECTION:MY_DATA,rws
方案3 def文件中定义
SETCTIONS
MY_DATA READ WRITE SHARED
读取共享节点的数据
- 我们要 隐式/静态 加载 lib,DLL
- 我们 通过
extern __declspec(dllimport)
声明使用 外部变量 - 程序中正常使用即可
//隐式/静态 加载 lib,DLL
#pragma comment(lib,"创建共享DLL")
//声明 我们使用的是 dll中的变量
extern __declspec(dllimport) char g_szBuff[12];
int main()
{
std::cout << g_szBuff<<std::endl;
}
方案三: 文件映射
- 官网连接
- 内存映射文件允许在进程中预定一块地址空间区域,将磁盘上的文件映射到地址空间中,对其访问,好像整个文件已经被载入到内存一样
- 文件<->内存 双向影响,任何一处的更改会影响到另一边
- 文件映射 是一个对象 分为 有名 ,匿名
- 有名称 的文件映射 可以跨进程使用
- 用来共享数据很方便,但是不适合通信
步骤
- 创建文件内核对象&文件映射内核对象
- 将文件映射对象的部分或全部映射到进程的地址空间
- 操作文件
- 从进程的地址空间撤销对文件数据的映射
- 关闭文件对象&文件映射对象
相关api
CreateFileMapping 创建内存映射文件对象(如果这里给 mapping 起了个名字那么 其他进程可以通过名字直接使用这个mapping)
OpenFileMapping 根据mapping名称打开映射
MapViewOfFile 将内存文件映射对象部分或全部映射到进程地址空间
FlushViewOfFile 强制系统把部分或全部修改过的数据写回到磁盘中
UnmapViewOfFile 取消映射
实例
创建映_绑定文件
- 标准的用法
- 我们把一个具体的文件映射到内存中
- 内存<->文件相互影响
- 文件映射到内存中的各大小是可以设置的,一般设置为文件大小
void maping_file() {
//1:打开文件
HANDLE hFile = CreateFileA("ReadMe.txt",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return;
}
//2:将文件映射到 内存中
HANDLE hFileMap = CreateFileMapping(hFile,
NULL,
PAGE_READWRITE,
0,
0,
NULL);
if (hFileMap == NULL)
{
return;
}
//3:打开 映射
LPVOID addr =MapViewOfFile(hFileMap,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
UnmapViewOfFile(addr);
}
创建映_绑定内存
- 有时候我们不需要整个文件内存映射到内存中
- 我们需要的仅仅是 多个程序间共享 一块内存
- 我们可以创建一个 没有实体文件的共享内存
void maping_memory()
{
//1: 我们直接创建一个没有文件 实体的映射
HANDLE hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
0x1000,
"CR31_file");
if (hFileMap == NULL)
{
return;
}
//2: 打开映射
LPVOID addr =MapViewOfFile(hFileMap,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (addr != NULL) {
//3: 打开的映射 实际上就是一个指针
memcpy(addr, "Hello", sizeof("Hello"));
UnmapViewOfFile(addr);
}
}
通过名称 打开映射
- 我们只需要知道 映射的名称就可以在任意程序中打开这个映射
void open_mapping()
{
//1: 通过名称搜索映射
HANDLE hFileMap = OpenFileMappingA(FILE_MAP_ALL_ACCESS,
FALSE,
"CR31_file");
if (hFileMap == NULL)
{
return;
}
//2: 打开映射
LPVOID addr = MapViewOfFile(hFileMap,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (addr != NULL) {
UnmapViewOfFile(addr);
}
方案四: 管道
- 管道分为 匿名管道,命名管道
- 匿名管道 可以用来跨线程
- 命名管道 可以跨进程
CreateProcess
时 可以绑定到一个 管道- 如果管道没有输出
ReadFile()
时会等待,在当前位置等待 ReadFile()
不能保证 会读出指定数量的 已存在的 数据,建议根据 返回的 数据读取量 计算后进行下一次读取
相关 api
CreatePipe() 创建一个管道, 获取 读取信息的句柄 以及 发送信息的句柄
WriteFile() 通过 发送信息的句柄 向管道写入信息,这里注意下 同步/异步
ReadFile() 通过 读取信息的句柄 读取管道中的信息,读取后会删除读取到的信息,读取不到的话会等待
PeekNamedPipe() 效果和 ReadFile 一样,只是读取后没有删除 读取到的信息,可以通过它检测 信息有多大
实例
int pipe() {
HANDLE hReadPipe;
HANDLE hWritePipe;
//创建一个匿名管道
BOOL bRet = CreatePipe(&hReadPipe,
&hWritePipe,
NULL,
0);
DWORD dwWritedBytes = 0;
DWORD dwReadedBytes = 0;
char szWriteBuf[256] = "Hello World!";
char szReadBuf[256] = { 0 };
//写入数据
bRet = WriteFile(hWritePipe,
szWriteBuf,
strlen(szWriteBuf) + 12,
&dwWritedBytes,
NULL);
if (!bRet) {
return 0;
}
//读取信息
bRet = ReadFile(hReadPipe,
szReadBuf,
256,
&dwReadedBytes,
NULL);
return 0;
}