EventPipe – 一种使用事件对象在进程之间传输二进制数据的 IPC 方法

原文链接:https://www.x86matthew.com/view_post?id=eventpipe

这篇文章展示了我为隐蔽的进程间通信提出的一个想法。

当系统上的两个进程需要相互通信时,它们通常会使用命名管道或共享内存等常用方法。然而,这些都相当容易检测和监控——我决定想出一种使用 Windows 事件对象的新方法。

事件句柄只有两种状态——开和关。它们通常用于一般的多线程同步,通常不需要任何进一步的调查。

我的基本理论如下: 1. 使用CreateEvent

在接收程序中创建 256 个事件对象。2.在接收程序中对上述所有事件句柄 调用WaitForMultipleObjects 。

3. 发送方程序使用OpenEvent打开接收方程序创建的所有 256 个事件对象。
4、在发送方程序中调用SetEvent,触发每个字节发送的对应事件(0-255)。

上面的理论有几个问题。正如大多数 Windows 开发人员已经知道的那样,WaitForMultipleObjects函数最多只允许 64 个句柄。另一个问题是同步——我们需要知道最初触发事件的确切顺序。

要解决上述问题,需要进行以下更改:

1. 我们不使用 256 个事件对象(每个字节一个),而是将每个字节分成两个事件——我们将在 base-16 中称其为“高”和“低”。例如,当发送 0x95 时,我们会触发 0x9“高”事件和 0x5“低”事件。这意味着我们只需要在任何时候使用WaitForMultipleObjects等待 16 个事件句柄,而不是全部 256 个。作为副作用,这也意味着事件对象的总数减少到 32 个(16 高 + 16 低) .
2. 为了解决同步问题,我们将创建一个称为“确认”标志的额外事件句柄。处理完每个字节后,将在接收程序中触发此事件。发送方程序只会在确认标志设置后发送下一个字节。

我创建了一个函数库来演示这个概念:

#include <stdio.h>
#include <windows.h>

#define EVENT_DATA_HIGH_COUNT 16
#define EVENT_DATA_LOW_COUNT 16

struct EventPipeObjectStruct
{
	HANDLE hEventDataHigh[EVENT_DATA_HIGH_COUNT];
	HANDLE hEventDataLow[EVENT_DATA_LOW_COUNT];
	HANDLE hEventAck;
};

DWORD EventPipe_GetHandles(char *pName, DWORD dwCreate, EventPipeObjectStruct *pEventPipeObject)
{
	char szEventName[512];
	EventPipeObjectStruct EventPipeObject;

	// create "high" data event handles
	for(DWORD i = 0; i < EVENT_DATA_HIGH_COUNT; i++)
	{
		// set current event name
		memset(szEventName, 0, sizeof(szEventName));
		_snprintf(szEventName, sizeof(szEventName) - 1, "EventPipe_%s_H%u", pName, i);

		if(dwCreate == 0)
		{
			// open existing object
			EventPipeObject.hEventDataHigh[i] = OpenEvent(EVENT_ALL_ACCESS, 0, szEventName);
		}
		else
		{
			// create new object
			EventPipeObject.hEventDataHigh[i] = CreateEvent(NULL, 0, 0, szEventName);
		}

		// check for errors
		if(EventPipeObject.hEventDataHigh[i] == NULL)
		{
			return 1;
		}
	}

	// create "low" data event handles
	for(i = 0; i < EVENT_DATA_LOW_COUNT; i++)
	{
		// set current event name
		memset(szEventName, 0, sizeof(szEventName));
		_snprintf(szEventName, sizeof(szEventName) - 1, "EventPipe_%s_L%u", pName, i);

		if(dwCreate == 0)
		{
			// open existing object
			EventPipeObject.hEventDataLow[i] = OpenEvent(EVENT_ALL_ACCESS, 0, szEventName);
		}
		else
		{
			// create new object
			EventPipeObject.hEventDataLow[i] = CreateEvent(NULL, 0, 0, szEventName);
		}

		// check for errors
		if(EventPipeObject.hEventDataLow[i] == NULL)
		{
			return 1;
		}
	}

	// create acknowledgement event
	memset(szEventName, 0, sizeof(szEventName));
	_snprintf(szEventName, sizeof(szEventName) - 1, "EventPipe_%s_A", pName);
	if(dwCreate == 0)
	{
		// open existing object
		EventPipeObject.hEventAck = OpenEvent(EVENT_ALL_ACCESS, 0, szEventName);
	}
	else
	{
		// create new object
		EventPipeObject.hEventAck = CreateEvent(NULL, 0, 0, szEventName);
	}

	if(EventPipeObject.hEventAck == NULL)
	{
		return 1;
	}

	// store data
	memcpy((void*)pEventPipeObject, (void*)&EventPipeObject, sizeof(EventPipeObject));

	return 0;
}

DWORD EventPipe_CreateReceiver(char *pName, EventPipeObjectStruct *pEventPipeObject)
{
	// create event handles
	if(EventPipe_GetHandles(pName, 1, pEventPipeObject) != 0)
	{
		return 1;
	}

	return 0;
}

DWORD EventPipe_Open(char *pName, EventPipeObjectStruct *pEventPipeObject)
{
	// open event handles
	if(EventPipe_GetHandles(pName, 0, pEventPipeObject) != 0)
	{
		return 1;
	}

	return 0;
}

DWORD EventPipe_RecvRawByte(EventPipeObjectStruct *pEventPipeObject, BYTE *pByte)
{
	DWORD dwEventDataHighValue = 0;
	DWORD dwEventDataLowValue = 0;
	BYTE bByte = 0;

	// wait for "high" data value
	dwEventDataHighValue = WaitForMultipleObjects(EVENT_DATA_HIGH_COUNT, pEventPipeObject->hEventDataHigh, 0, INFINITE);
	if(dwEventDataHighValue >= EVENT_DATA_HIGH_COUNT)
	{
		return 1;
	}

	// wait for "low" data value
	dwEventDataLowValue = WaitForMultipleObjects(EVENT_DATA_LOW_COUNT, pEventPipeObject->hEventDataLow, 0, INFINITE);
	if(dwEventDataLowValue >= EVENT_DATA_LOW_COUNT)
	{
		return 1;
	}

	// calculate byte value from high/low value
	bByte = (BYTE)((dwEventDataHighValue * EVENT_DATA_LOW_COUNT) + dwEventDataLowValue);

	// set acknowledgement event
	if(SetEvent(pEventPipeObject->hEventAck) == 0)
	{
		return 1;
	}

	// store byte value
	*pByte = bByte;

	return 0;
}

DWORD EventPipe_RecvRawData(EventPipeObjectStruct *pEventPipeObject, BYTE *pData, DWORD dwLength)
{
	BYTE *pCurrPtr = NULL;

	// receive all requested bytes
	pCurrPtr = pData;
	for(DWORD i = 0; i < dwLength; i++)
	{
		// get current byte
		if(EventPipe_RecvRawByte(pEventPipeObject, pCurrPtr) != 0)
		{
			return 1;
		}

		// increase ptr
		pCurrPtr++;
	}

	return 0;
}

DWORD EventPipe_RecvDataBlock(EventPipeObjectStruct *pEventPipeObject, BYTE *pData, DWORD dwMaxLength, DWORD *pdwDataLength)
{
	DWORD dwBlockLength = 0;

	// get block length
	if(EventPipe_RecvRawData(pEventPipeObject, (BYTE*)&dwBlockLength, sizeof(DWORD)) != 0)
	{
		return 1;
	}

	// validate length
	if(dwBlockLength > dwMaxLength)
	{
		return 1;
	}

	// get block data
	if(EventPipe_RecvRawData(pEventPipeObject, pData, dwBlockLength) != 0)
	{
		return 1;
	}

	if(pdwDataLength != NULL)
	{
		// store block length
		*pdwDataLength = dwBlockLength;
	}

	return 0;
}

DWORD EventPipe_SendRawByte(EventPipeObjectStruct *pEventPipeObject, BYTE bByte)
{
	// set "high" data event
	if(SetEvent(pEventPipeObject->hEventDataHigh[bByte / EVENT_DATA_LOW_COUNT]) == 0)
	{
		return 1;
	}

	// set "low" data event
	if(SetEvent(pEventPipeObject->hEventDataLow[bByte % EVENT_DATA_LOW_COUNT]) == 0)
	{
		return 1;
	}

	// wait for acknowledgement
	if(WaitForSingleObject(pEventPipeObject->hEventAck, INFINITE) != 0)
	{
		return 1;
	}

	return 0;
}

DWORD EventPipe_SendRawData(EventPipeObjectStruct *pEventPipeObject, BYTE *pData, DWORD dwLength)
{
	BYTE *pCurrPtr = NULL;

	// send all requested bytes
	pCurrPtr = pData;
	for(DWORD i = 0; i < dwLength; i++)
	{
		// send current byte
		if(EventPipe_SendRawByte(pEventPipeObject, *pCurrPtr) != 0)
		{
			return 1;
		}

		// increase ptr
		pCurrPtr++;
	}

	return 0;
}

DWORD EventPipe_SendDataBlock(EventPipeObjectStruct *pEventPipeObject, BYTE *pData, DWORD dwLength)
{
	// send block length
	if(EventPipe_SendRawData(pEventPipeObject, (BYTE*)&dwLength, sizeof(DWORD)) != 0)
	{
		return 1;
	}

	// send block data
	if(EventPipe_SendRawData(pEventPipeObject, pData, dwLength) != 0)
	{
		return 1;
	}

	return 0;
}

接收程序:

int main()
{
	EventPipeObjectStruct EventPipeObject;
	char szRecvString[512];
	DWORD dwDataLength = 0;

	memset((void*)&EventPipeObject, 0, sizeof(EventPipeObject));
	if(EventPipe_CreateReceiver("x86matthew", &EventPipeObject) != 0)
	{
		return 1;
	}

	for(;;)
	{
		memset(szRecvString, 0, sizeof(szRecvString));
		if(EventPipe_RecvDataBlock(&EventPipeObject, (BYTE*)szRecvString, sizeof(szRecvString) - 1, &dwDataLength) != 0)
		{
			return 1;
		}

		printf("Received %u bytes: '%s'\n", dwDataLength, szRecvString);
	}

	return 0;
}

发件人程序:

int main(int argc, char *argv[])
{
	EventPipeObjectStruct EventPipeObject;

	if(argc != 2)
	{
		return 1;
	}

	memset((void*)&EventPipeObject, 0, sizeof(EventPipeObject));
	if(EventPipe_Open("x86matthew", &EventPipeObject) != 0)
	{
		return 1;
	}

	if(EventPipe_SendDataBlock(&EventPipeObject, (BYTE*)argv[1], strlen(argv[1])) != 0)
	{
		return 1;
	}

	return 0;
}

潜在的未来改进:

– 目前只有单向通信 – 添加双工支持。
– 事件对象的命名允许我们在远程进程中打开句柄 – 使它们“匿名”并将句柄复制到远程进程中。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注