0

I'm trying to write an IPC with named pipe.

The server code : http://pastebin.com/tHyAv0e0

The client code : http://pastebin.com/Qd0yGBca

My question is about the server. Following a SO user, i'm trying to use BindIoCompletionCallback() in the server code. The server consists of the following functions:

  • print_last_error : print a readable message of the last error
  • IocpThreadProc : the callback passed to BindIoCompletionCallback(), it calls ConnectNamedPipe() and ReadFile()
  • server_new : creates the named pipe and try to connect to the other end of the pipe (that is, the client), creates an exit event and call BindIoCompletionCallback()
  • server_del : should release the resources
  • the main function which has an infinite loop which wait on the exit event to be signaled.

When the client connects, it send the message "salut, c'est le client !". I have set the buffer of ReadFile() to 5, to test the case where I have to call ReadFile() several times. I have the following output:

connection pending...
waiting for client...
 ** 0, 0
reading data
 * ReadFile : 0
 ** 0, 5
msg:
reading data
 ** 0, 5
 * ReadFile : 5
reading data
msg: , c'e
 * ReadFile : 5
 ** 0, 5
msg: st le
reading data
 * ReadFile : 5
 ** 0, 5
msg:  clie
reading data
 * ReadFile : 5
 ** 0, 4
msg: nt !~
reading data
IO_PENDING
 ** -1073741493, 0
reading data
unexpected error failed with error 109: Le canal de communication a ÚtÚ fermÚ.
WaitForSingleObject : 0

the lines beginning with **: it prints the arguments of the callback

the lines beginning with 'msg' : it prints the message of the buffer filled by Readfile

As the length of the message sent by the client is 24, I should normally get these 5 messages (each of them being of 5 char, except the last one, being of 4 char) :

salut
, c'e
st le 
 clie
nt !

but I can't have the first part of the messge (that is : "salut"). The callback is called when an I/O operation is complete, maybe for this first part. But I have not succeded in calling ReadFile() in a way to get the first part of the message. I have tried to call ReadFile() in the main loop of the main function, in a thread, in server_new(), etc... Everything except the correct way.

Does someone know what to do to fix this issue ?

thank you

vtorri
  • 166
  • 13

1 Answers1

2

your code containing huge count of fundamental errors. more exactly all code - one complete error

look at code snippet (in IocpThreadProc and server_new)

    char buf[READ_BUFSIZE];
    ret = ReadFileEx(svr->pipe, buf, sizeof(buf), &svr->ol, IocpThreadProc);

char buf[READ_BUFSIZE] - this is local variable in function. after you exit from function - this become arbitrary address in stack. so when read operation complete - this faster of all corrupt your stack or will be undefinded result. so this is error. you must pass not stack memory as read buffer or not exit from function until read operation complete

you pass IocpThreadProc as argument to ReadFileEx

lpCompletionRoutine

A pointer to the completion routine to be called when the read operation is complete and the calling thread is in an alertable wait state.

but you never wait in alertable state !

later you use

BindIoCompletionCallback(svr->pipe, IocpThreadProc, 0);

but bind file to IOCP and use APC completion (lpCompletionRoutine ) is mutually exclusive. if say you call BindIoCompletionCallback before ReadFileEx(.., IocpThreadProc) - you will got error ERROR_INVALID_PARAMETER

from NtReadFile source code:

        //
        // If this file has an I/O completion port associated w/it, then
        // ensure that the caller did not supply an APC routine, as the
        // two are mutually exclusive methods for I/O completion
        // notification.
        //

        if (fileObject->CompletionContext && IopApcRoutinePresent( ApcRoutine )) {
            ObDereferenceObject( fileObject );
            return STATUS_INVALID_PARAMETER;
        }

your code "work" ony because you bind IOCP after call ReadFileEx(.., IocpThreadProc). but what happens when read operation is completed ? the APC (for IocpThreadProc) will be inserted to thread and packet queued to IOCP. so IocpThreadProc will be called twice with same data for single operation. it called once only because you never wait in alertable state and not pop APC from thread.

you embedded OVERLAPPED to Server - this is error. you must have unique OVERLAPPED per every asynchronous I/O. more exactly you must define own class, which inherit from OVERLAPPED. have in this class pointer to Server, operation code, may be some additional data. you need allocate this struct before every I/O operation and free it in completion.

GetLastError() in IocpThreadProc !!!

you need use DWORD dwErrorCode here, GetLastError() no sense because here on another thread called, absolte unrelated to operation. and becase this is callback from kernel here really NTSTATUS values is in dwErrorCode, but not win32 errors. say for example on read complete you can got STATUS_PIPE_BROKEN but not ERROR_BROKEN_PIPE but this already big defect in MSDN docs

code example:

class __declspec(novtable) IoObject
{
    friend struct UIRP;

    LONG _dwRef;

public:

    ULONG AddRef()
    {
        return InterlockedIncrement(&_dwRef);
    }

    ULONG Release()
    {
        ULONG dwRef = InterlockedDecrement(&_dwRef);

        if (!dwRef)
        {
            delete this;
        }

        return dwRef;
    }

protected:

    IoObject()
    {
        _dwRef = 1;
    }

    virtual ~IoObject() 
    {
    };

    virtual void OnComplete(DWORD dwErrorCode, ULONG op, PVOID buf, DWORD dwNumberOfBytesTransfered) = 0;
};

struct UIRP : OVERLAPPED
{
    IoObject* _obj;
    PVOID _buf;
    ULONG _op;

    UIRP(IoObject* obj, ULONG op, PVOID buf = 0)
    {
        RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
        _obj = obj;
        obj->AddRef();
        _op = op;
        _buf = buf;
    }

    void CheckError(BOOL f)
    {
        if (!f)
        {
            DWORD dwErrorCode = RtlGetLastNtStatus();

            if (dwErrorCode != STATUS_PENDING)
            {
                OnComplete(dwErrorCode, 0);
            }
        }
    }

    ~UIRP()
    {
        _obj->Release();
    }

    static BOOL BindIoCompletion(HANDLE hObject)
    {
        return BindIoCompletionCallback(hObject, _OnComplete, 0);
    }

private:

    static void WINAPI _OnComplete(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
    {
        static_cast<UIRP*>(lpOverlapped)->OnComplete(dwErrorCode, dwNumberOfBytesTransfered);
    }

    void OnComplete(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)
    {
        _obj->OnComplete(dwErrorCode, _op, _buf, dwNumberOfBytesTransfered);
        delete this;
    }
};

class __declspec(novtable) CPipe : public IoObject
{
    enum {
        pipe_connect, pipe_read, pipe_write
    };
protected:
    HANDLE _pipe;
    PBYTE _buf;
    ULONG _dataSize;
    ULONG _bufferSize;

public:

    CPipe()
    {
        _pipe = INVALID_HANDLE_VALUE;
        _buf = 0;
        _dataSize = 0;
        _bufferSize = 0;
    }

    BOOL Create(ULONG bufferSize, PCWSTR name);

    BOOL Listen();

    BOOL Write(const void* data, ULONG cb);

    BOOL Disconnect()
    {
        if (IsServer())
        {
            return DisconnectNamedPipe(_pipe);
        }

        HANDLE pipe = InterlockedExchangePointer(&_pipe, INVALID_HANDLE_VALUE);

        if (pipe != INVALID_HANDLE_VALUE)
        {
            CloseHandle(pipe);
        }

        return TRUE;
    }

protected:

    BOOL Read();// usually never call direct

    virtual BOOL OnRead(PVOID buf, ULONG cbTransferred) = 0;

    virtual BOOL OnConnect() = 0;   

    virtual void OnDisconnect() = 0;

    virtual BOOL IsServer() = 0;

    virtual void OnWrite(DWORD /*dwErrorCode*/)
    {
    }

    virtual ~CPipe()
    {
        HANDLE pipe = InterlockedExchangePointer(&_pipe, INVALID_HANDLE_VALUE);

        if (pipe != INVALID_HANDLE_VALUE)
        {
            CloseHandle(pipe);
        }

        if (_buf)
        {
            delete _buf;
        }
    }

private:

    virtual void OnComplete(DWORD dwErrorCode, ULONG op, PVOID buf, DWORD dwNumberOfBytesTransfered);
};

void CPipe::OnComplete(DWORD dwErrorCode, ULONG op, PVOID buf, DWORD dwNumberOfBytesTransfered)
{
    DbgPrint("%u>%s<%p>(%x, %x, %x)\n", IsServer(), __FUNCTION__, this, dwErrorCode, op, dwNumberOfBytesTransfered);

    switch (op)
    {
    case pipe_read:

        switch(dwErrorCode) 
        {
        case STATUS_SUCCESS:
            if (OnRead(buf, dwNumberOfBytesTransfered)) Read();
            break;

        case STATUS_PIPE_BROKEN:        // pipe handle has been closed, server must call DisconnectNamedPipe
        case STATUS_CANCELLED:          // CancelIo[Ex] called
            Disconnect();

        case STATUS_PIPE_DISCONNECTED:  // server call DisconnectNamedPipe
        case STATUS_INVALID_HANDLE:     // we close handle
            OnDisconnect();
            break;

        default:__debugbreak();
        }
        break;

    case pipe_connect:

        switch(dwErrorCode) 
        {
        case STATUS_SUCCESS:            // ERROR_SUCCESS 
        case STATUS_PIPE_CONNECTED:     // ERROR_PIPE_CONNECTED
        case STATUS_PIPE_CLOSING:       // ERROR_NO_DATA (really client can send data before disconnect, exist sense do read)
            if (OnConnect()) Read();
            break;
        case STATUS_PIPE_BROKEN:        // server call CloseHandle before ConnectNamedPipe complete
        case STATUS_PIPE_DISCONNECTED:  // server call DisconnectNamedPipe before ConnectNamedPipe
        case STATUS_CANCELLED:          // server call CancelIo[Ex]
            break;
        default: __debugbreak();
        }
        break;

    case pipe_write:
        OnWrite(dwErrorCode);
        LocalFree(buf);
        break;

    default: __debugbreak();
    }
}

BOOL CPipe::Create(ULONG bufferSize, PCWSTR name)
{
    if (_buf = new UCHAR[bufferSize])
    {
        _bufferSize = bufferSize;
    }
    else
    {
        return FALSE;
    }

    static WCHAR pipeprefix[] = L"\\\\?\\pipe\\";
    PWSTR path = (PWSTR)alloca(wcslen(name) * sizeof(WCHAR) + sizeof(pipeprefix));
    wcscat(wcscpy(path, pipeprefix), name);

    BOOL bServer = IsServer();

    _pipe = bServer 
        ?
    CreateNamedPipeW(path,
        PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
        PIPE_UNLIMITED_INSTANCES,
        PAGE_SIZE, PAGE_SIZE, INFINITE, NULL)
        :
    CreateFile(path, FILE_READ_ATTRIBUTES|FILE_READ_DATA|
        FILE_WRITE_ATTRIBUTES|FILE_WRITE_DATA, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED, 0);

    if (_pipe == INVALID_HANDLE_VALUE || !UIRP::BindIoCompletion(_pipe))
    {
        return FALSE;
    }

    return bServer ? Listen() : OnComplete(0, pipe_connect, 0, 0), TRUE;
}

BOOL CPipe::Listen()
{
    if (UIRP* irp = new UIRP(this, pipe_connect))
    {
        irp->CheckError(ConnectNamedPipe(_pipe, irp));

        return TRUE;
    }

    return FALSE;
}

BOOL CPipe::Read()
{
    ULONG NumberOfBytesToRead = _bufferSize - _dataSize;

    if (!NumberOfBytesToRead)
    {
        return FALSE;
    }

    PVOID buf = _buf + _dataSize;

    if (UIRP* irp = new UIRP(this, pipe_read, buf))
    {
        irp->CheckError(ReadFile(_pipe, buf, NumberOfBytesToRead, 0, irp));

        return TRUE;
    }

    return FALSE;
}

BOOL CPipe::Write(const void* data, ULONG cb)
{
    if (PVOID buf = LocalAlloc(0, cb))
    {
        if (UIRP* irp = new UIRP(this, pipe_write, buf))
        {
            memcpy(buf, data, cb);

            irp->CheckError(WriteFile(_pipe, buf, cb, 0, irp));

            return TRUE;
        }
    }

    return FALSE;
}

class ServerPipe : public CPipe
{
    virtual BOOL OnRead(PVOID buf, ULONG cbTransferred)
    {
        DbgPrint("%.*s\n", cbTransferred, buf);

        char sz[256];
        Write(sz, 1 + sprintf(sz, "response from %p server\n", this));

        return TRUE;
    }

    virtual BOOL OnConnect()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);

        return TRUE;
    }

    virtual void OnDisconnect()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
        Listen();//
    }

    virtual BOOL IsServer()
    {
        return TRUE;
    }

    virtual ~ServerPipe()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }
};

class ClientPipe : public CPipe
{
    int _n;

    virtual BOOL OnRead(PVOID buf, ULONG cbTransferred)
    {
        DbgPrint("%.*s\n", cbTransferred, buf);

        if (--_n)
        {
            char sz[256];
            Write(sz, 1 + sprintf(sz, "request[%u] from %p client\n", _n, this));
            return TRUE;
        }
        return FALSE;
    }

    virtual BOOL OnConnect()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);

        _n = 3;

        char sz[256];
        Write(sz, 1 + sprintf(sz, "hello from %p client\n", this));

        return TRUE;
    }

    virtual void OnDisconnect()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }

    virtual BOOL IsServer()
    {
        return FALSE;
    }

    virtual ~ClientPipe()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }
};

DWORD CALLBACK ClientThread(void* name)
{
    int n = 2;
    do 
    {
        MessageBox(0,0,L"client",MB_ICONWARNING);
        if (ClientPipe* p = new ClientPipe)
        {
            p->Create(PAGE_SIZE, (PCWSTR)name);
            p->Release();
        }
    } while (--n);

    return 0;
}

void pipeTest()
{
    static WCHAR sname[] = L"__test_pipe__";

    if (HANDLE hThread = CreateThread(0, 0, ClientThread, sname, 0, 0))
    {
        CloseHandle(hThread);
    }

    if (ServerPipe* p = new ServerPipe)
    {
        p->Create(PAGE_SIZE, sname);
        p->Release();
    }

    MessageBox(0,0,0,0);
}

and about DWORD dwErrorCode in

VOID CALLBACK FileIOCompletionRoutine(
  __in  DWORD dwErrorCode,
  __in  DWORD dwNumberOfBytesTransfered,
  __in  LPOVERLAPPED lpOverlapped
);

in BindIoCompletionCallback documentation exist unclarity

Return value

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. To get extended error information, call the GetLastError function. The value returned is an NTSTATUS error code. To retrieve the corresponding system error code, use the RtlNtStatusToDosError function.

what is mean under The value returned is an NTSTATUS error code ? what return value ?

this is DWORD dwErrorCode in FileIOCompletionRoutine

really we pass to kernel mode pointer to IO_STATUS_BLOCK (first 2 members of OVERLAPPED is IO_STATUS_BLOCK actually). when asynchronous operation complete - kernel fill IO_STATUS_BLOCK and queue packet to IOCP (or APC to Thread). ntdll extract PIO_STATUS_BLOCK from IOCP (so we got back pointer to our OVERLAPPED passed to I/O api), and fill

dwErrorCode = Iosb->Status, 
dwNumberOfBytesTransfered = (ULONG)Iosb->Information, 
lpOverlapped = (LPOVERLAPPED)Iosb; 

system not do conversion

dwErrorCode = RtlNtStatusToDosError(Iosb->Status)

but direct assign NTSTATUS to DWORD dwErrorCode - so in FileIOCompletionRoutine we must compare dwErrorCode not with wi32 error codes but with NTSTATUS codes (from "ntstatus.h" )

so we never seen ERROR_BROKEN_PIPE or ERROR_PIPE_NOT_CONNECTED in FileIOCompletionRoutine, but STATUS_PIPE_BROKEN or STATUS_PIPE_DISCONNECTED


and code example by using new Thread Pool API instead BindIoCompletionCallback. here big advantage that in IoCompletionCallback (PTP_WIN32_IO_CALLBACK) callback function in place ULONG IoResult already used win32 error, but not raw NTSTATUS ( IoResult = RtlNtStatusToDosError(Iosb->Status) and note ULONG_PTR NumberOfBytesTransferred (vs ULONG dwNumberOfBytesTransfered from FileIOCompletionRoutine (LPOVERLAPPED_COMPLETION_ROUTINE) callback function and compare this with ULONG_PTR Information from IO_STATUS_BLOCK. )

#define StartIo(irp, pio, f) StartThreadpoolIo(_pio); irp->CheckError(f, _pio);

class __declspec(novtable) IoObject
{
    friend struct UIRP;

    LONG _dwRef;

public:

    ULONG AddRef()
    {
        return InterlockedIncrement(&_dwRef);
    }

    ULONG Release()
    {
        ULONG dwRef = InterlockedDecrement(&_dwRef);

        if (!dwRef)
        {
            delete this;
        }

        return dwRef;
    }

protected:

    IoObject()
    {
        _dwRef = 1;
    }

    virtual ~IoObject() 
    {
    };

    virtual void OnComplete(DWORD dwErrorCode, ULONG op, PVOID buf, DWORD dwNumberOfBytesTransfered) = 0;
};

struct UIRP : OVERLAPPED
{
    IoObject* _obj;
    PVOID _buf;
    ULONG _op;

    UIRP(IoObject* obj, ULONG op, PVOID buf = 0)
    {
        RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
        _obj = obj;
        obj->AddRef();
        _op = op;
        _buf = buf;
    }

    void CheckError(BOOL f, PTP_IO pio)
    {
        if (!f)
        {
            DWORD dwErrorCode = GetLastError();

            if (dwErrorCode != ERROR_IO_PENDING)
            {
                CancelThreadpoolIo(pio);
                OnComplete(dwErrorCode, 0);
            }
        }
    }

    ~UIRP()
    {
        _obj->Release();
    }

    static PTP_IO BindIoCompletion(HANDLE hObject)
    {
        return CreateThreadpoolIo(hObject, _IoCompletionCallback, 0, 0);
    }

private:

    static VOID CALLBACK _IoCompletionCallback(
        __inout      PTP_CALLBACK_INSTANCE /*Instance*/,
        __inout_opt  PVOID /*Context*/,
        __inout_opt  PVOID Overlapped,
        __in         ULONG IoResult,
        __in         ULONG_PTR NumberOfBytesTransferred,
        __inout      PTP_IO /*Io*/
        )
    {
        static_cast<UIRP*>(Overlapped)->OnComplete(IoResult, (ULONG)NumberOfBytesTransferred);
    }

    void OnComplete(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)
    {
        _obj->OnComplete(dwErrorCode, _op, _buf, dwNumberOfBytesTransfered);
        delete this;
    }
};

class __declspec(novtable) CPipe : public IoObject
{
    enum {
        pipe_connect, pipe_read, pipe_write
    };
protected:
    HANDLE _pipe;
    PTP_IO _pio;
    PBYTE _buf;
    ULONG _dataSize;
    ULONG _bufferSize;

public:

    CPipe()
    {
        _pipe = INVALID_HANDLE_VALUE;
        _buf = 0;
        _dataSize = 0;
        _bufferSize = 0;
        _pio = 0;
    }

    BOOL Create(ULONG bufferSize, PCWSTR name);

    BOOL Listen();

    BOOL Write(const void* data, ULONG cb);

    BOOL Disconnect()
    {
        if (IsServer())
        {
            return DisconnectNamedPipe(_pipe);
        }

        HANDLE pipe = InterlockedExchangePointer(&_pipe, INVALID_HANDLE_VALUE);

        if (pipe != INVALID_HANDLE_VALUE)
        {
            CloseHandle(pipe);
        }

        return TRUE;
    }

protected:

    BOOL Read();// usually never call direct

    virtual BOOL OnRead(PVOID buf, ULONG cbTransferred) = 0;

    virtual BOOL OnConnect() = 0;   

    virtual void OnDisconnect() = 0;

    virtual BOOL IsServer() = 0;

    virtual void OnWrite(DWORD /*dwErrorCode*/)
    {
    }

    virtual ~CPipe()
    {
        if (_pio)
        {
            CloseThreadpoolIo(_pio);
        }

        HANDLE pipe = InterlockedExchangePointer(&_pipe, INVALID_HANDLE_VALUE);

        if (pipe != INVALID_HANDLE_VALUE)
        {
            CloseHandle(pipe);
        }

        if (_buf)
        {
            delete _buf;
        }
    }

private:

    virtual void OnComplete(DWORD dwErrorCode, ULONG op, PVOID buf, DWORD dwNumberOfBytesTransfered);
};

void CPipe::OnComplete(DWORD dwErrorCode, ULONG op, PVOID buf, DWORD dwNumberOfBytesTransfered)
{
    DbgPrint("%u>%s<%p>(%x, %x, %x)\n", IsServer(), __FUNCTION__, this, dwErrorCode, op, dwNumberOfBytesTransfered);

    switch (op)
    {
    case pipe_read:

        switch(dwErrorCode) 
        {
        case ERROR_SUCCESS:
            if (OnRead(buf, dwNumberOfBytesTransfered)) Read();
            break;

        case ERROR_BROKEN_PIPE:         // pipe handle has been closed , server must call DisconnectNamedPipe
        case ERROR_OPERATION_ABORTED:   // CancelIo[Ex] called
            Disconnect();

        case ERROR_PIPE_NOT_CONNECTED:  // server call DisconnectNamedPipe
        case ERROR_INVALID_HANDLE:      // we close handle
            OnDisconnect();
            break;

        default:__debugbreak();
        }
        break;

    case pipe_connect:

        switch(dwErrorCode) 
        {
        case ERROR_SUCCESS:             // client just connected 
        case ERROR_PIPE_CONNECTED:      // client already connected
        case ERROR_NO_DATA:             // client already connected and disconnected (really client can send data before disconnect, exist sense do read)
            if (OnConnect()) Read();
            break;
        case ERROR_BROKEN_PIPE:         // server call CloseHandle before ConnectNamedPipe complete
        case ERROR_PIPE_NOT_CONNECTED:  // server call DisconnectNamedPipe before ConnectNamedPipe
        case ERROR_OPERATION_ABORTED:   // server call CancelIo[Ex]
            break;
        default: __debugbreak();
        }
        break;

    case pipe_write:
        OnWrite(dwErrorCode);
        LocalFree(buf);
        break;

    default: __debugbreak();
    }
}

BOOL CPipe::Create(ULONG bufferSize, PCWSTR name)
{
    if (_buf = new UCHAR[bufferSize])
    {
        _bufferSize = bufferSize;
    }
    else
    {
        return FALSE;
    }

    static WCHAR pipeprefix[] = L"\\\\?\\pipe\\";
    PWSTR path = (PWSTR)alloca(wcslen(name) * sizeof(WCHAR) + sizeof(pipeprefix));
    wcscat(wcscpy(path, pipeprefix), name);

    BOOL bServer = IsServer();

    _pipe = bServer 
        ?
    CreateNamedPipeW(path,
        PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
        PIPE_UNLIMITED_INSTANCES,
        PAGE_SIZE, PAGE_SIZE, INFINITE, NULL)
        :
    CreateFile(path, FILE_READ_ATTRIBUTES|FILE_READ_DATA|
        FILE_WRITE_ATTRIBUTES|FILE_WRITE_DATA, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED, 0);

    if (_pipe == INVALID_HANDLE_VALUE || !(_pio = UIRP::BindIoCompletion(_pipe)))
    {
        return FALSE;
    }

    return bServer ? Listen() : OnComplete(0, pipe_connect, 0, 0), TRUE;
}

BOOL CPipe::Listen()
{
    if (UIRP* irp = new UIRP(this, pipe_connect))
    {
        StartIo(irp, _pio, ConnectNamedPipe(_pipe, irp));

        return TRUE;
    }

    return FALSE;
}

BOOL CPipe::Read()
{
    ULONG NumberOfBytesToRead = _bufferSize - _dataSize;

    if (!NumberOfBytesToRead)
    {
        return FALSE;
    }

    PVOID buf = _buf + _dataSize;

    if (UIRP* irp = new UIRP(this, pipe_read, buf))
    {
        StartIo(irp, _pio, ReadFile(_pipe, buf, NumberOfBytesToRead, 0, irp));

        return TRUE;
    }

    return FALSE;
}

BOOL CPipe::Write(const void* data, ULONG cb)
{
    if (PVOID buf = LocalAlloc(0, cb))
    {
        if (UIRP* irp = new UIRP(this, pipe_write, buf))
        {
            memcpy(buf, data, cb);

            StartIo(irp, _pio, WriteFile(_pipe, buf, cb, 0, irp));

            return TRUE;
        }
    }

    return FALSE;
}

class ServerPipe : public CPipe
{
    virtual BOOL OnRead(PVOID buf, ULONG cbTransferred)
    {
        DbgPrint("%.*s\n", cbTransferred, buf);

        char sz[256];
        Write(sz, 1 + sprintf(sz, "response from %p server\n", this));

        return TRUE;
    }

    virtual BOOL OnConnect()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);

        return TRUE;
    }

    virtual void OnDisconnect()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
        Listen();//
    }

    virtual BOOL IsServer()
    {
        return TRUE;
    }

    virtual ~ServerPipe()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }
};

class ClientPipe : public CPipe
{
    int _n;

    virtual BOOL OnRead(PVOID buf, ULONG cbTransferred)
    {
        DbgPrint("%.*s\n", cbTransferred, buf);

        if (--_n)
        {
            char sz[256];
            Write(sz, 1 + sprintf(sz, "request[%u] from %p client\n", _n, this));
            return TRUE;
        }

        return FALSE;
    }

    virtual BOOL OnConnect()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);

        _n = 3;

        char sz[256];
        Write(sz, 1 + sprintf(sz, "hello from %p client\n", this));

        return TRUE;
    }

    virtual void OnDisconnect()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }

    virtual BOOL IsServer()
    {
        return FALSE;
    }

    virtual ~ClientPipe()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }
};

DWORD CALLBACK ClientThread(void* name)
{
    int n = 2;
    do 
    {
        MessageBox(0,0,L"client",MB_ICONWARNING);
        if (ClientPipe* p = new ClientPipe)
        {
            p->Create(PAGE_SIZE, (PCWSTR)name);
            p->Release();
        }
    } while (--n);

    return 0;
}

void pipeTest()
{
    static WCHAR sname[] = L"__test_pipe__";

    if (HANDLE hThread = CreateThread(0, 0, ClientThread, sname, 0, 0))
    {
        CloseHandle(hThread);
    }

    if (ServerPipe* p = new ServerPipe)
    {
        p->Create(PAGE_SIZE, sname);
        p->Release();
    }

    MessageBox(0,0,0,0);
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • for the buf comment, i know what a local variable is. If you look at the code more closer, you'll that the Server structure has 2 fields (data and size) and that the macro LOCAL_APPEND_DATA append buf to the data field. So it's not a problem at all... – vtorri Dec 25 '16 at 18:31
  • @vtorri - no. you pass pointer to this buffer to ReadFileEx ?LOCAL_APPEND_DATA here absolute unrelated. you pass `buf` as input parameter to `ReadFile(svr->pipe, buf, sizeof(buf), NULL, lpOverlapped);` - so buf must be valid all time, until op is not complete. but you exit from func. you must understand this point. this is important – RbMm Dec 25 '16 at 18:35
  • for the ReadFileEx() in server_new(), that was one of my attempt to make it work. About the unique overlapped, i didn't know (i'm new to iocp). You say that I need to allocate this struct before every I/O. So when, actually, should I allocate it in the code ? – vtorri Dec 25 '16 at 18:43
  • for buf, , ok. So adding buf to the Server struct should be sufficient, right ? – vtorri Dec 25 '16 at 18:45
  • overlapped need allocate just before every I/O operation. – RbMm Dec 25 '16 at 18:45
  • So adding buf to the Server struct should be sufficient, right - yes – RbMm Dec 25 '16 at 18:45
  • for GetLastError(), ok. i'll use dwErrorCode – vtorri Dec 25 '16 at 18:46
  • and not mix apc with iocp - this is error. use only iocp with ReadFile. without Ex – RbMm Dec 25 '16 at 18:46
  • "i'll use dwErrorCode" - yes, and exist one thin moment - in callback `dwErrorCode` filled from `IO_STATUS_BLOCK.Status` - and this is filled in kernel. so here not win32 error code, but NTSTATUS – RbMm Dec 25 '16 at 18:48
  • Is ReadFileEx should be replaced with ReadFile in server_new, and actually, should there a Readfile(Ex) in server_new ? – vtorri Dec 25 '16 at 18:52
  • you can use any ReadFileEx or ReadFile but lpCompletionRoutine must be 0 if you bind file to IOCP. or if you use lpCompletionRoutine you must not bind to IOCP – RbMm Dec 25 '16 at 18:53
  • thank you very much. I will port it as I have to use the code in a framework that accepts only C code. – vtorri Dec 25 '16 at 20:09
  • what is the meaning of the name of the UIRP class ? – vtorri Jan 22 '17 at 06:10
  • Also, why creating an UIRP pointer in the CPipe methods Listen(), Write() and Read() ? (btw there should be memory leak, here if i'm not mistaken) – vtorri Jan 22 '17 at 06:12
  • @vtorri - `UIRP` is place holder for `OVERLAPPED` and operation parameters. of course no memory leaks. for any operation where i allocate `irp` i call `irp->CheckError(f, _pio);` if `dwErrorCode != ERROR_IO_PENDING` i direct call `OnComplete` otherwise `_IoCompletionCallback` will be called when operation finished (look for `BindIoCompletion` method) which call `OnComplete` - and in this function note - `delete this` . using `UIRP` is key point of all. if you not understand this - you not understand nothing. – RbMm Jan 22 '17 at 09:33
  • @vtorri - again for every asynchronous operation we need have **unique** `OVERLAPPED`. it must be allocated before any this operation and free when operation finished. – RbMm Jan 22 '17 at 09:34
  • i wanted to know the meaning of 'UIRP". It seems to be an acronym. what is it ? – vtorri Jan 22 '17 at 10:27
  • @vtorri - are you familiar with [IRP](https://msdn.microsoft.com/en-us/library/windows/hardware/ff550694(v=vs.85).aspx) - this is *U*ser mode analog of this and *have the same life time* – RbMm Jan 22 '17 at 10:33
  • in your BindIoCompletionCallback example, if i'm not mistaken, the _dataSize member of CPipe is always equal to 0. Is it normal ? Also, OnConnect is always returning TRUE, so what is the purpose of the test in OnComplete (pipe_connect case) ? You can just call OnConnect, then Read – vtorri Feb 07 '17 at 05:21
  • @vtorri - in this specific example `ClientPipe::OnRead` i not use _dataSize. but possible buffer it - `_dataSize += cbTransferred` (say if you detect incomplete data). `ClientPipe::OnConnect is always returning TRUE` - the `ClientPipe` is implementation of base class `CPipe`. this is (`ClientPipe` code) demo only (unlike `CPipe` - this is real code). in this demo i always return TRUE. but another implementation can return and `FALSE`. the base class `CPipe` can not know what return virtual function `OnConnect` which is implemented in derived class – RbMm Feb 07 '17 at 09:27
  • i have ported your code to C code and during my tests, I first launch the server (no client is running). So ConnectNamedPipe is returning ERROR_PIPE_LISTENING (according to MSDN), hence, in OnComplete, the error value is STATUS_PIPE_LISTENING, which is not managed in the switch statements (hence, SIGTRAP signal is launched because of __debugbreak). How should I manage that case ? – vtorri Mar 03 '17 at 05:56
  • @vtorri- are you use `PIPE_NOWAIT` flag in `CreateNamedPipe` call ? `ERROR_PIPE_LISTENING` returned only for this case (*nonblocking mode*) - I not use this flag/mode in onw code – RbMm Mar 03 '17 at 08:13
  • i do not use PIPE_NOWAIT. I just rewrote your c++ code to C, following the same OO design : http://pastebin.com/n5JXyp1Z (compiled with gcc, not vc++) The backtrace : http://pastebin.com/T5WBNj8i As you can see in frame #0, the error is 3221225651, (which is actually 0xC00000B3 in hexa), that is STATUS_PIPE_LISTENING – vtorri Mar 03 '17 at 19:22
  • @vtorri - your error in `_pipe_set` function - line 372 - you call `_pipe_on_complete((IO_Obj *)p, 0, PIPE_CONNECT, NULL, 0)` for server and client. but need only for client here. look again in my code - http://pastebin.com/ftXukV4m (line 261) - `bServer ? Listen() : OnComplete(0, pipe_connect, 0, 0), TRUE;` - as result you call complete for connect, when it really not complete. than call read when no connection and of course got error `STATUS_PIPE_LISTENING` – RbMm Mar 03 '17 at 21:13
  • @vtorri - also print error in hex (%x) this will be much more readable, look like you not use ref counting, what is also wrong, and `ntdll has no import lib` - of course false - it exist (`ntdll.lib` and `ntdllp.lib` in any `WDK` installation) – RbMm Mar 03 '17 at 21:17
  • @vtorri - `_io_obj_new` you look like not use, and when allocate object - you not set `refcount = 1` - this is fatal error – RbMm Mar 03 '17 at 21:19
  • indeed, i've not set io_obj part, thank you very much, it seems to work now. About ntdll, we (our project) can't rely on the fact that our users have installed WDK. So LoadLibrary() is sufficient, and anyway correct, afaik – vtorri Mar 03 '17 at 22:12
  • in your code, what's the point of calling Release() just after Create() ? shouldn't Release() be called at the end (once the dialog box is closed for example) ? – vtorri Mar 03 '17 at 22:16
  • @vtorri - you completely mistake about `ntdll.lib` - this lib is need for you only (like and `kernel32.lib` for example) you (but not clients) need install WDK (or copy separate `ntdll.lib`) . `ntdll.dll` - is exist always on any windows (or windows not boot at all). – RbMm Mar 03 '17 at 22:24
  • @vtorri - my code base on ref counting - need call `Release` on object after we no more use it. i create object - with ref=1, any io operation begin +1 ref, io operation end -1 ref. – RbMm Mar 03 '17 at 22:26
  • Suppose that the message is longer than the buffer (for example: setting PAGE_SIZE to 5 instead of 512 in your code), is there a way to be notified that there is no more data to read for that message ? – vtorri Mar 04 '17 at 05:48
  • read file return `min(cbDataExist, cbBuffer)`. if `cbDataExist > cbBuffer` you can read it in next calls. usual situation – RbMm Mar 04 '17 at 08:42
  • what are cbBuffer and cbDataExists ? Readfile() documentation says that it has nNumberOfBytesToRead (the buffer size) and lpNumberOfBytesRead, pointer which should be set to NULL if FILE_FLAG_OVERLAPPED is used (our case) – vtorri Mar 05 '17 at 05:43
  • There is still a difference in the output of your code and my port (i've added a printf in CheckError. output with your code with PAGE_SIZE==5 : http://pastebin.com/KAQUPcPp and output with my code with same page size : http://pastebin.com/b93pMbXp – vtorri Mar 06 '17 at 05:26