最近在做一个资源管理器背景工具,将模块注入后,需要跟模块进行通信,意向传递一些信息。
一开始想到的是使用共享内存,但是无法进行实时通知。
匿名管道的话需要传入句柄字符串,也不太适用这里的场景。
使用Mailslots可以间隔的去取消息,刚好可以实现这个需求。不同于消息队列的时,没取到消息时,不会阻塞。
Mailslots介绍
mailslot 是驻留在内存中的伪文件,可以使用标准文件函数对其进行访问。 mailslot 消息中的数据可以采用任何形式,但在计算机之间发送时不能大于 424 个字节。 与磁盘文件不同,mailslot 是临时的。 关闭mailslot的所有句柄后,将删除 mailslot 及其包含的所有数据。
mailslot 服务器是创建和拥有 mailslot 的进程。 当进程创建 mailslot 时,我们可以得到一个 mailslot 句柄。 从mailslot 读取消息时,必须使用此句柄。 只有创建mailslot或已通过某些其他机制(如继承 () )获取句柄的进程才能从 mailslot 读取。 所有 mailslot 都是创建它们的进程的本地位置。 进程无法创建远程mailslot。
mailslot 客户端是一个将消息写入 mailslot 的进程。 只要名字跟创建进程里的一致,任何进程都可以把消息写入到这个mailslot。
Mailslot 可以广播域中的消息。 如果域中的多个进程都使用相同的名称创建mailslot,则发送到该mailslot并发送到域的每条消息都将由参与进程接收。 由于一个进程可以控制服务器 mailslot 句柄和在打开 mailslot 进行写入操作时检索的客户端句柄,因此应用程序可以轻松地在域中实现简单的消息传递工具。
若要在计算机之间发送大于 424 字节的消息,请改用 命名管道 或 Windows 套接字 。
这一段介绍来自MSDN,翻译不是非常准确,不过大概能看懂就行了。推荐直接阅读英文原文。
Mailslot名称
格式如下:
\\.\mailslot\[path\]name
如
1 #define DEMO_SLOT_NAME L"\\\\.\\mailslot\\demo_slot"
还可以根据不同进程创建不同的mailslots:
如:
\\.\mailslot\mark\process1
\\.\mailslot\mark\process2
\\.\mailslot\mark\process3
特定远程计算机上的 mailslot:
\\ComputerName\mailslot\[path\]name
指定域中的每个 mailslot :
\\DomainName\mailslot\[path\]name
系统主域中具有给定名称的每个 mailslot:
\\*\mailslot\[path\]name
创建Mailslots
这里主要用到CreateMailslot函数,函数声明如下:
它的作用是:使用指定名称创建 mailslot,并返回 mailslot 服务器可用于对 mailslot 执行操作的句柄。
1 HANDLE 2 WINAPI 3 CreateMailslotW( 4 _In_ LPCWSTR lpName, 5 _In_ DWORD nMaxMessageSize, 6 _In_ DWORD lReadTimeout, 7 _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes 8 );
参数说明:
[in] lpName
mailslot 的名称,关于名称的要求,在前面已经说明了。
[in] nMaxMessageSize
可写入 mailslot 的单个邮件的最大大小(以字节为单位)。 若要指定消息可以是任意大小,请将此值设置为零。
[in] lReadTimeout
值 | 含义 |
---|---|
0 | 如果没有消息,则立即返回 。 (系统不会将立即返回视为 error。) |
MAILSLOT_WAIT_FOREVER ( (DWORD) -1) | 永远等待消息。 |
[in, optional] lpSecurityAttributes
指向 SECURITY_ATTRIBUTES 结构的指针。 结构的 bInheritHandle 成员确定返回的句柄是否可以由子进程继承。 如果 lpSecurityAttributes 为 NULL,则不能继承句柄。
返回值:
如果函数成功,则返回值是 mailslot 的句柄,用于服务器 mailslot 操作。 此函数返回的句柄是异步的或重叠(overlapped)的。
如果函数失败,则返回值为 INVALID_HANDLE_VALUE。 要获得更多的错误信息,请调用 GetLastError。
示例代码如下:
1 #define DEMO_SLOT_NAME L"\\\\.\\mailslot\\demo_slot" 2 3 HANDLE WINAPI MakeSlot(LPCTSTR lpszSlotName) 4 { 5 HANDLE hSlot = CreateMailslot(lpszSlotName, 6 0, //no maximum message size 7 MAILSLOT_WAIT_FOREVER, //no time-out for operations 8 NULL); //default security 9 10 if (hSlot == INVALID_HANDLE_VALUE) 11 { 12 std::cout << "Create mailslot failed with " 13 << GetLastError() 14 << std::endl; 15 return NULL; 16 } 17 else 18 { 19 std::cout << "Create mailslot successfully." << std::endl; 20 return hSlot; 21 } 22 23 }
写入Mailslots
写入mailslot主要是借用CreateFile和WriteFile函数来完成
在Windows平台下,对I/O设备的操作基本是由CreateFile来完成。其中包括文件、文件流、目录、物理磁盘、卷、控制台缓冲区、磁带驱动器、通信资源、mailslot 和管道等。
对于CreateFile/WriteFile的使用,这里暂时不做详细说明,可以参考后续的文章。
写入的示例代码如下:
1 BOOL WriteSlot(LPCTSTR lpszSlotName, LPCTSTR lpszMessage) 2 { 3 //打开mailslot 4 HANDLE hFile = CreateFile(lpszSlotName, GENERIC_WRITE, 5 FILE_SHARE_READ, NULL, OPEN_EXISTING, 6 FILE_ATTRIBUTE_NORMAL, NULL); 7 8 if (hFile == INVALID_HANDLE_VALUE) 9 { 10 std::cout << "CreateFile failed with " << GetLastError() << std::endl; 11 return FALSE; 12 } 13 14 DWORD cbWritten = 0; 15 //写入消息 16 BOOL bResult = WriteFile(hFile,lpszMessage, (DWORD)(lstrlen(lpszMessage) * sizeof(TCHAR)), 17 &cbWritten, NULL); 18 19 if (!bResult) 20 { 21 std::cout << "WriteFile failed with " << GetLastError() << std::endl; 22 CloseHandle(hFile); 23 return FALSE; 24 } 25 else 26 { 27 std::wcout << L"Slot written to [" << lpszMessage << L"] successfully." << std::endl; 28 //释放资源 29 CloseHandle(hFile); 30 return TRUE; 31 } 32 }
读取Mailslots
读取mailSlots使用的是ReadFile函数,在进行读取之前我们可以通过GetMailslotInfo来确定 mailslot 中是否存在消息。
GetMailslotInfo函数声明如下:
1 WINBASEAPI 2 BOOL 3 WINAPI 4 GetMailslotInfo( 5 _In_ HANDLE hMailslot, 6 _Out_opt_ LPDWORD lpMaxMessageSize, 7 _Out_opt_ LPDWORD lpNextSize, 8 _Out_opt_ LPDWORD lpMessageCount, 9 _Out_opt_ LPDWORD lpReadTimeout 10 );
参数说明:
[in] hMailslot
mailslot的句柄
[out, optional] lpMaxMessageSize
此 mailslot 允许的最大邮件大小(以字节为单位)。 此值可以大于或等于创建 mailslot 的 CreateMailslot 函数的 cbMaxMsg 参数中指定的值。 此参数可以为 NULL。
[out, optional] lpNextSize
下一条消息的大小(以字节为单位)。此参数可以为 NULL。 它还可以取以下特殊值
MAILSLOT_NO_MESSAGE( (DWORD) -1) 没有下一条消息
[out, optional] lpMessageCount
函数返回时等待读取的消息总数。 此参数可以为 NULL。
[out, optional] lpReadTimeout
等待读取mailslot的超时时间(以毫秒为单位)。 当函数返回时,将填充此参数。 此参数可以为 NULL。
返回值
如果该函数成功,则返回值为非零值。
如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError。
读取的示例代码如下:
读取前需要先调用CreateMailslot函数来获取mailslot句柄,然后再调用ReadFile进行读取。
1 #include <iostream> 2 #include<Windows.h> 3 #include<tchar.h> 4 #include<strsafe.h> 5 6 #define DEMO_SLOT_NAME L"\\\\.\\mailslot\\demo_slot" 7 8 HANDLE MakeSlot(LPCTSTR lpszSlotName) 9 { 10 HANDLE hSlot = CreateMailslot(lpszSlotName, 11 0, 12 MAILSLOT_WAIT_FOREVER, 13 NULL); 14 15 if (hSlot == INVALID_HANDLE_VALUE) 16 { 17 std::cout << "CreateMailSlot failed with " << GetLastError() << std::endl; 18 return NULL; 19 } 20 else 21 { 22 std::cout << "CreateMailSlot successfully." << std::endl; 23 return hSlot; 24 } 25 } 26 27 BOOL ReadSlot(HANDLE hSlot) 28 { 29 DWORD cbMessage, cMessage, cbRead; 30 BOOL bResult; 31 LPTSTR lpszBuffer; 32 HANDLE hEvent = NULL; 33 OVERLAPPED ov; 34 35 cbMessage = cMessage = cbRead = 0; 36 37 hEvent = CreateEvent(NULL, FALSE, FALSE, L"DemoSlotEvent"); 38 if (hEvent == NULL) 39 return FALSE; 40 41 ov.Offset = 0; 42 ov.OffsetHigh = 0; 43 ov.hEvent = hEvent; 44 45 if (hSlot == NULL) 46 return FALSE; 47 48 bResult = GetMailslotInfo(hSlot, //mailslot handle 49 NULL, //no maximum message size 50 &cbMessage, //size of next message 51 &cMessage, //number of messages 52 NULL); //no read time-out 53 54 if (!bResult) 55 { 56 std::cout << "GetMailSlotInfo failed with " << GetLastError() << std::endl; 57 return FALSE; 58 } 59 60 if (cbMessage == MAILSLOT_NO_MESSAGE) 61 { 62 std::cout << "waiting for a message" << std::endl; 63 return TRUE; 64 } 65 66 //retreive all message 67 while (cMessage != 0) 68 { 69 lpszBuffer = (LPTSTR)GlobalAlloc(GPTR, cbMessage); 70 71 if (NULL == lpszBuffer) 72 return FALSE; 73 74 lpszBuffer[0] = '\0'; 75 76 bResult = ReadFile(hSlot, lpszBuffer, cbMessage, &cbRead, &ov); 77 78 if (!bResult) 79 { 80 std::cout << "ReadFile failed with " << GetLastError() << std::endl; 81 GlobalFree((HGLOBAL)lpszBuffer); 82 CloseHandle(hEvent); 83 return FALSE; 84 } 85 86 std::wcout << L"From Slot:" << lpszBuffer << std::endl; 87 88 GlobalFree((HGLOBAL)lpszBuffer); 89 90 bResult = GetMailslotInfo(hSlot, NULL, &cbMessage, &cMessage, NULL); 91 92 if (!bResult) 93 { 94 CloseHandle(hEvent); 95 std::cout << "GetMailslotInfo failed " << GetLastError() << std::endl; 96 return FALSE; 97 } 98 } 99 100 CloseHandle(hEvent); 101 return TRUE; 102 103 } 104 105 int main() 106 { 107 HANDLE hSlot = MakeSlot(DEMO_SLOT_NAME); 108 109 while (TRUE) 110 { 111 ReadSlot(hSlot); 112 Sleep(1000); 113 } 114 115 CloseHandle(hSlot); 116 }
注意:
mailslot的消息是单向的,创建mailslot的进程只能从mailslot读取消息,但是不能写入。打开mailslot的进程只可以写入,不可以读取。
如果需要进程间的双向通信,请使用其它技术,如命名管道、套接字。
所以需要消息读取端的进程先运行,写入端的进程后运行。
示例代码
详细的使用过程可以参考示例代码
参考资料
mailslots介绍
Mailslots - Win32 apps | Microsoft Learn
理解mailslots消息传递
winapi - Confusion in understanding mailslot - Stack Overflow