Introduction
This tip aims to provide a C++ wrapper class for facilitating interprocess communication (IPC) using pipes in Windows. The pipe client and pipe server are implemented as CPipeClient and CPipeServer classes, respectively. The classes wrap the underlying Windows API functions to ease the process of reading and writing to pipe. Also, an event-based mechanism is implemented to handle pipe related events. Named pipe is used to allow full-duplex communication between client and server processes.
Background
Although the Windows API has the ReadFile/WriteFile functions for reading/writing data to the pipe, this article on top of it provides an event-based mechanism to ease the pipe communication. Also, all the pipe related events are processed in a separate thread. The PipeClient and PipeServer classes can be easily integrated into any project.
Using the code
Two classes form the wrapper, mainly CPipeClient and CPipeServer. CPipeClient class implements all the logic required for the pipe client and the CPipeServer class implements the pipe server logic. Some part of the code in these classes will look similar, which highlights the commonality of basic pipe communication. In order to use the code, all that is required is creating an instance of CPipeServer and CPipeClient.
Pipe server is first started and waits for a pipe client to get connected. Once a pipe client is connected, the pipe server sends a welcome message to the client. The client then sends a close message to the pipe server. Subsequently, both pipe server and pipe client close. For the purpose of illustration here, only two messages are passed, but one can pass any number of messages and what to do when a message is received and when to close the pipe depends on the application logic.
All data passed between the pipe client and pipe server is of wstring type so one can easily handle unicode data.
CPipeClient is implemented as follows:
class CPipeClient
{
public:
CPipeClient(std::wstring& sName);
virtual ~CPipeClient(void);
int GetEvent() const;
void SetEvent(int nEventID);
HANDLE GetThreadHandle();
HANDLE GetPipeHandle();
void SetData(std::wstring& sData);
void GetData(std::wstring& sData);
void ConnectToServer();
void OnEvent(int nEvent);
static UINT32 __stdcall PipeThreadProc(void* param);
void Close();
bool Read();
bool Write();
private:
CPipeClient(void);
void Init();
void Run();
const std::wstring m_sPipeName; HANDLE m_hPipe; HANDLE m_hThread; int m_nEvent; wchar_t* m_buffer;
};
CPipeServer class is implemented as follows:
class CPipeServer
{
public:
CPipeServer(std::wstring& sName);
virtual ~CPipeServer(void);
int GetEvent() const;
void SetEvent(int nEventID);
HANDLE GetThreadHandle();
HANDLE GetPipeHandle();
void SetData(std::wstring& sData);
void GetData(std::wstring& sData);
void OnEvent(int nEvent);
static UINT32 __stdcall PipeThreadProc(void*);
void WaitForClient();
void Close();
bool Read();
bool Write();
private:
CPipeServer(void);
void Init();
void Run();
const std::wstring m_sPipeName; HANDLE m_hPipe; HANDLE m_hThread; int m_nEvent; wchar_t* m_buffer;
};
In both the classes the function PipeThreadProc is the thread callback function which handles the pipe events. This function loops until a pipe is closed or there is a pipe error. The function OnEvent is invoked whenever a pipe event is received. Thus, application logic can be handled from OnEvent function based on the type of message received.
To write data to pipe all one has to do is set the data using SetData function and SetEvent to AU_IOWRITE. Data to be written or read is put in the m_buffer member of the class.
All constants used are defined in the file PipeConst.h.
PipeClient.cpp and PipeServer.cpp demonstrates how the above classes can be used.
Pipe server driver code looks as follows:
int _tmain(int argc, _TCHAR* argv[])
{
_setmode(_fileno(stdout), _O_U16TEXT);
std::wcout << _T("---------------------Pipe Server--------------------") << std::endl;
std::wstring sPipeName(PIPENAME);
CPipeServer* pServer = new CPipeServer(sPipeName);
::WaitForSingleObject(pServer->GetThreadHandle(), INFINITE);
delete pServer;
pServer = NULL;
return 0;
}
Pipe client driver code looks as follows:
int _tmain(int argc, _TCHAR* argv[])
{
_setmode(_fileno(stdout), _O_U8TEXT);
std::wcout << _T("---------------------Pipe Client--------------------") << std::endl;
std::wstring sPipeName(PIPENAME);
CPipeClient* pClient = new CPipeClient(sPipeName);
::WaitForSingleObject(pClient->GetThreadHandle(), INFINITE);
delete pClient;
pClient = NULL;
return 0;
}
Following is the output when pipe server is first run and then the pipe client.
Points of Interest
The number of instances of pipe server can be controlled inside the Init function. The function call ::CreateNamedPipe has a setting called PIPE_UNLIMITED_INSTANCES to control it. Currently, it defaults to 255. Also, the buffer size has been limited to 1024. One can provide any size depending on the limitation of the underlying system.
History
Update output image.
Download Links
Pipe Server
Pipe Client