进程通信的五种普通方法
真的很想补上代码,但是空闲时间真的好少!!!
第一种:通过自定义消息来实现
坑贼鸡儿多
SendMessage 和 PostMessage 最大的区别:
SendMessage:发送的消息不进入消息队列,而是 将指定的消息发送到一个或多个窗口,所以会等待对方的处理,直到对方接收到了才返回
PostMessage:发送的消息进入到消息队列,所以不会等待对方的处理,直接放到消息队列就返回
hmod 和 dwThreadId参数说明:
如果需要设置局部钩子(作用范围:单一的进程),那么hmod参数为0,dwThreadId设置为需要钩住的线程的ID。
如果需要设置全局钩子(作用范围:所有有效进程),那么hmod参数为模块句柄(钩子的回调函数需要在DLL中),dwThreadId设置为0。
#include<Windows.h> #include<cstdio> #include<TlHelp32.h> int main(int argc, char* argv[]){ //HWND hwnd; HMODULE hModule; HWND hWnd; HHOOK g_Hook; hModule = LoadLibrary(L"D:\\Visual_Studio_Repos_2013\\进程通信\\Debug\\Win32Project1.dll"); typedef LRESULT(CALLBACK* pWndProc)(int nCode, WPARAM wParam, LPARAM lParam); pWndProc WndProc; WndProc = (pWndProc)GetProcAddress(hModule, "_WndProc@12"); hWnd = FindWindow(L"#32770", L"PE Dialog"); DWORD dwThreadId = GetWindowThreadProcessId(hWnd, NULL); g_Hook = SetWindowsHookEx(WH_CALLWNDPROC, WndProc, hModule, dwThreadId); if (NULL == g_Hook) { MessageBox(NULL, L"安装钩子失败", L"提示", MB_OKCANCEL); } SendMessage(hWnd, WM_USER + 100, 0, 100); //PostMessage(hWnd, WM_USER + 100, 0, 100); printf("%d", GetLastError()); return 0; }
有个坑:虽然自己没有测试真实性,自己猜想的PostMessage不起作用的原因是对方进程中的消息循环机制中就没有要自己HOOK的循环消息的类型,这时候将该消息放入消息队列当程序本身取出该消息的时候发送给系统,系统通过调用就无法识别要发送给谁,所以说可能如果本身有定义的话应该是可以用PostMessage来实现的,然后说下为什么SendMessage可以实现,因为自己查询了SendMessage该函数是直接将消息发送给窗口的,所以识别的过程就过去省略,直接发送给,以上都当自己放屁 发现自己解释的是错的,为什么不删掉?以后当自己看到这里的时候,可以再次回想下,继续思考,重新把它的解释填上!!!
DLL编写如下:
#include "stdafx.h" #include "TTTT.h" #include<cstdio> #pragma comment(linker,"/SECTION:.share,RWS") #pragma data_seg(".share") HHOOK g_Hook = 0; //这几句代码申明g_Hook被多个进程间共享 #pragma data_seg() extern "C" _declspec(dllexport) LRESULT CALLBACK WndProc(int nCode, WPARAM wParam, LPARAM lParam){ if (nCode == HC_ACTION) { TCHAR szWindowText[] = TEXT("HookTest"); PCWPSTRUCT pcw = (PCWPSTRUCT)lParam; if (pcw->message == WM_USER + 100 && pcw->lParam == 100){ MessageBox(0, L"WOW", L"WOW", 0); } } return CallNextHookEx(g_Hook, nCode, wParam, lParam); }
第二种:通过文件映射创建的共享内存来实现
服务端的代码:
#include<Windows.h> #include<cstdio> int main(){ HANDLE hMapping; // 此函数为指定的文件创建一个命名或未命名的文件映射对象 hMapping = CreateFileMapping(INVALID_HANDLE_VALUE,NULL, PAGE_READWRITE, 0, 0x1000, L"MyMapping"); if (hMapping == NULL){ printf("CreateFileMapping Failed, the error is %d", GetLastError()); } LPVOID pMap = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0x1000); if (pMap == NULL){ printf("MapViewOfFile Failed, the error is %d", GetLastError()); } printf("初始打印:*pMap: %x \n", *(PDWORD)pMap); getchar(); printf("二次打印:*pMap: %x \n", *(PDWORD)pMap); getchar(); UnmapViewOfFile(pMap); CloseHandle(hMapping); }
客户端的代码:
#include<Windows.h> #include<cstdio> int main(){ HANDLE hMapping; // 此函数为指定的文件创建一个命名或未命名的文件映射对象 hMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 0x1000, L"MyMapping"); if (hMapping == NULL){ printf("CreateFileMapping Failed, the error is %d", GetLastError()); } LPVOID pMap = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0x1000); if (pMap == NULL){ printf("MapViewOfFile Failed, the error is %d", GetLastError()); } *(PDWORD)pMap = 0x12345678; printf("初始打印:%x", *(PDWORD)pMap); getchar(); UnmapViewOfFile(pMap); CloseHandle(hMapping); }
第三种方法:匿名管道实现父子进程通信
需要注意的是:父进程创建子进程的时候,关于STARTUPINFO的结构体参数需要比正常多几个如下参数,自己理解来看就是把当前父进程的输入输出流都写到对应的读写管道中
si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = hPipeWrite; si.hStdOutput = hPipeRead; si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
父进程:
#include<Windows.h> #include<cstdio> int main(){ HANDLE hPipeRead; HANDLE hPipeWrite; SECURITY_ATTRIBUTES sa; sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; sa.nLength = sizeof(SECURITY_ATTRIBUTES); BOOL bRet = CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0); if (!bRet){ return -1; } STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi; si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = hPipeWrite; si.hStdOutput = hPipeRead; si.hStdError = GetStdHandle(STD_ERROR_HANDLE); WCHAR wszReadBuffer[MAX_PATH]; WCHAR wszWriteBuffer[MAX_PATH] = L"http://zpchcbd.com/"; DWORD dwReadBytes; DWORD dwWriteBytes; //写 if (WriteFile(hPipeWrite, (LPVOID)wszWriteBuffer, MAX_PATH, &dwWriteBytes, NULL)){ wprintf(L"TopProcess > %s", wszWriteBuffer); } CreateProcess(L"D:\\Visual_Studio_Repos_2013\\进程通信\\Debug\\test.exe", NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); /* while (true){ if (ReadFile(hPipeRead, (LPVOID)wszReadBuffer, MAX_PATH, &dwReadBytes, NULL)){ wprintf(L"%s", wszReadBuffer); memset(wszReadBuffer, 0, MAX_PATH); } } */ return 0; }
子进程:
#include<Windows.h> #include<cstdio> int main(){ HANDLE hRead; HANDLE hWrite; hRead = GetStdHandle(STD_OUTPUT_HANDLE); hWrite = GetStdHandle(STD_INPUT_HANDLE); WCHAR wszBuffer[MAX_PATH] = { 0 }; // 只读一次 DWORD dwReadBytes; if (ReadFile(hRead, wszBuffer, MAX_PATH, &dwReadBytes, NULL)){ MessageBox(0, wszBuffer, L"子进程读取到的数据:", MB_OK); } else{ MessageBox(0, 0, L"没有读取到数据", MB_OK); } return 0; }
第四种方法:命名管道进程间通信
其实除了第一种自定义消息,其他自己都写过 不难
服务端:
#include<Windows.h> #include<cstdio> int main(){ HANDLE hPipe = CreateNamedPipe(L"\\\\.\\pipe\\aaaa", PIPE_ACCESS_DUPLEX, PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, MAX_PATH, MAX_PATH, 0, NULL); if (hPipe == INVALID_HANDLE_VALUE){ printf("CreatePipe Failed"); CloseHandle(hPipe); } WCHAR szRecvBuffer[1024]; DWORD dwReadBytes; // 服务端在这里会进行堵塞,等待客户端进行连接 if (ConnectNamedPipe(hPipe, NULL)) { printf("ConnectNamedPipe success \n"); memset(szRecvBuffer, 0, 1024); if (ReadFile(hPipe, szRecvBuffer, MAX_PATH, &dwReadBytes, NULL)) { wprintf(L"ReadFile Success -> %s\n", szRecvBuffer); }else{ printf("ReadFile Failed, Error is %s", GetLastError()); CloseHandle(hPipe); return -1; } } CloseHandle(hPipe); return 0; }
客户端:
#include<Windows.h> #include<cstdio> int main(){ HANDLE hPipe; WCHAR wszBuffer[MAX_PATH] = L"hahahaha!!!"; DWORD dwWriteBytes; if (WaitNamedPipe(L"\\\\.\\pipe\\aaaa", NMPWAIT_WAIT_FOREVER)){ printf("WaitNamedPipe Success \n"); hPipe = CreateFile(L"\\\\.\\pipe\\aaaa", GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hPipe != NULL){ printf("Connect Pipe Success \n"); if (WriteFile(hPipe, wszBuffer, MAX_PATH, &dwWriteBytes, NULL)){ printf("WriteFile data Success \n"); } } } return 0; }
第五种方法:DLL 共享节
简单的说,其实就是声明DLL中的一段数据区为共享区,在学习DLL共享节的时候找到的如下一段话:
在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的,因为所有的进程用的都收同一块地址空间;
而在Win32环境中,情况却发生了变化,每个进程都有了它自己的4GB的内存空间,DLL函数中的代码所创建的任何对象(包括变量)都归调用它的进程所有。
当进程在载入DLL时,操作系统自动把DLL地址映射到该进程的私有空间,也就是进程的虚拟地址空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间。(在物理内存中,多进程载入DLL时,DLL的代码段实际上是只加载了一次,只是将物理地址映射到了各个调用它的进程的虚拟地址空间中,而全局数据会在每个进程都分别加载)。也就是说每个进程所拥有的相同的DLL的全局数据,它们的名称相同,但其值却并不一定是相同的,而且是互不干涉的。
因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问同一个Dll的各进程之间共享存储器是通过存储器映射文件技术实现的。
那么如何设置呢?方法:也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。
设置的语法格式如下:
#pragma data_seg("DLLSharedSection") // 声明共享数据段,并命名该数据段 int SharedData = 123; // 必须在定义的同时进行初始化!!!! #pragma data_seg()
一种方法是在.DEF文件中加入如下语句:
SETCTIONS Shared READ WRITE SHARED
或者是直接声明:
#pragma comment(linker,"/section:.DLLSharedSection,rws")
或者是在编译器里面声明:在项目设置的链接选项(Project Setting –〉Link)中加入如下语句:
/SECTION:Shared,rws
dll编写代码:
#pragma once #define MYAPI extern "C" _declspec(dllexport) MYAPI int __cdecl getData(); MYAPI void __cdecl SetData(int youData); #include "stdafx.h" #include "TTTT.h" #pragma data_seg("MyShareSection") // 声明共享数据段,并命名该数据段 int SharedData = 123; // 必须在定义的同时进行初始化!!!! #pragma data_seg() #pragma comment(linker,"/section:.MyShareSection,rws") MYAPI int __cdecl getData(){ return SharedData; } MYAPI void __cdecl SetData(int dwData){ SharedData = dwData; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY