Использование ReadFile (Ex) с BindIoCompletionCallback

Я пытаюсь написать IPC с именованным каналом.

Код сервера: http://pastebin.com/tHyAv0e0.

Код клиента: http://pastebin.com/Qd0yGBca

У меня вопрос по серверу. Следуя за пользователем SO, я пытаюсь использовать BindIoCompletionCallback () в коде сервера. Сервер состоит из следующих функций:

  • print_last_error: распечатать читаемое сообщение о последней ошибке
  • IocpThreadProc: обратный вызов, переданный в BindIoCompletionCallback (), вызывает ConnectNamedPipe () и ReadFile ()
  • server_new: создает именованный канал и пытается подключиться к другому концу канала (то есть клиенту), создает событие выхода и вызывает BindIoCompletionCallback ()
  • server_del: должен освободить ресурсы
  • основная функция, которая имеет бесконечный цикл, ожидающий сигнала выхода.

Когда клиент подключается, он отправляет сообщение «Salut, c'est le client!». Я установил буфер ReadFile () на 5, чтобы проверить случай, когда мне нужно вызывать ReadFile () несколько раз. У меня такой вывод:

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

строки, начинающиеся с **: он печатает аргументы обратного вызова

строки, начинающиеся с 'msg': он печатает сообщение о буфере, заполненном Readfile

Поскольку длина сообщения, отправляемого клиентом, составляет 24, я обычно должен получить эти 5 сообщений (каждое из которых состоит из 5 символов, кроме последнего, состоящего из 4 символов):

salut
, c'e
st le 
 clie
nt !

но я не могу получить первую часть сообщения (то есть «салют»). Обратный вызов вызывается, когда операция ввода-вывода завершена, возможно, для этой первой части. Но мне не удалось вызвать ReadFile (), чтобы получить первую часть сообщения. Я пытался вызвать ReadFile () в основном цикле основной функции, в потоке, в server_new () и т. Д. Все, кроме правильного пути.

Кто-нибудь знает, что делать, чтобы исправить эту проблему?

Спасибо


person vtorri    schedule 25.12.2016    source источник


Ответы (1)


ваш код содержит огромное количество фундаментальных ошибок. точнее весь код - одна полная ошибка

посмотрите на фрагмент кода (в IocpThreadProc и server_new)

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

char buf[READ_BUFSIZE] - это локальная переменная в функции. после выхода из функции - это становится произвольным адресом в стеке. поэтому, когда операция чтения завершена - это быстрее всего повредит ваш стек или будет неопределенным результатом. так что это ошибка. вы должны передавать не стековую память в качестве буфера чтения или не выходить из функции до завершения операции чтения

вы передаете IocpThreadProc в качестве аргумента ReadFileEx

lpCompletionRoutine

Указатель на процедуру завершения, которая будет вызываться, когда операция чтения завершена и вызывающий поток находится в состоянии ожидания с предупреждением.

но вы никогда не ждете в состоянии готовности!

позже вы используете

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

но привязка файла к IOCP и использование завершения APC (lpCompletionRoutine) является взаимоисключающим. если, скажем, вы звоните BindIoCompletionCallback раньше ReadFileEx(.., IocpThreadProc) - вы получите ошибку ERROR_INVALID_PARAMETER

из исходного кода NtReadFile:

        //
        // 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;
        }

ваш код "работает" только потому, что вы привязываете IOCP после вызова ReadFileEx(.., IocpThreadProc). но что произойдет, когда операция чтения будет завершена? APC (для IocpThreadProc) будет вставлен в поток, а пакет будет поставлен в очередь IOCP. поэтому IocpThreadProc будет вызываться дважды с одними и теми же данными для одной операции. он вызывается один раз только потому, что вы никогда не ждете в тревожном состоянии и не извлекаете APC из потока.

вы встроили OVERLAPPED в Сервер - это ошибка. у вас должен быть уникальный OVERLAPPED для каждого асинхронного ввода-вывода. точнее, вы должны определить собственный класс, унаследованный от OVERLAPPED. иметь в этом классе указатель на Сервер, код операции, могут быть какие-то дополнительные данные. вам необходимо выделить эту структуру перед каждой операцией ввода-вывода и освободить ее по завершении.

GetLastError() in IocpThreadProc !!!

вам нужно использовать DWORD dwErrorCode здесь, GetLastError() нет смысла, потому что здесь, в другом потоке, вызванном, абсолютно не связанном с операцией. и поскольку это обратный вызов ядра, здесь действительно NTSTATUS значения находятся в dwErrorCode, но не в ошибках win32. скажем, например, при завершении чтения вы можете получить STATUS_PIPE_BROKEN, но не ERROR_BROKEN_PIPE, но это уже большой дефект в документах MSDN

пример кода:

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);
}

и около DWORD dwErrorCode в

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

в документации BindIoCompletionCallback есть неясность

Возвращаемое значение

Если функция завершается успешно, возвращаемое значение отличное от нуля.

Если функция не работает, возвращаемое значение равно нулю. Чтобы получить расширенную информацию об ошибке, вызовите GetLastError. Возвращенное значение представляет собой код ошибки NTSTATUS. Чтобы получить соответствующий код системной ошибки, используйте RtlNtStatusToDosError.

что имеется в виду под Возвращенное значение является кодом ошибки NTSTATUS? какое возвращаемое значение?

это DWORD dwErrorCode в _30 _

на самом деле мы передаем указатель режима ядра на IO_STATUS_BLOCK (первые 2 члена OVERLAPPED фактически IO_STATUS_BLOCK). после завершения асинхронной операции - ядро ​​заполняет IO_STATUS_BLOCK и помещает пакет в очередь в IOCP (или APC в поток). ntdll извлекает PIO_STATUS_BLOCK из IOCP (так что мы получили обратно указатель на наш OVERLAPPED, переданный в API ввода-вывода) и заполняем

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

система не выполняет преобразование

dwErrorCode = RtlNtStatusToDosError(Iosb->Status)

но напрямую назначить NTSTATUS DWORD dwErrorCode - поэтому в FileIOCompletionRoutine мы должны сравнивать dwErrorCode не с кодами ошибок wi32, а с кодами NTSTATUS (из "ntstatus.h")

поэтому мы никогда не видели ERROR_BROKEN_PIPE или ERROR_PIPE_NOT_CONNECTED в FileIOCompletionRoutine, но STATUS_PIPE_BROKEN или STATUS_PIPE_DISCONNECTED


и пример кода с использованием нового потока Pool API вместо BindIoCompletionCallback. здесь большое преимущество в том, что в IoCompletionCallback < / a> (PTP_WIN32_IO_CALLBACK) функция обратного вызова на месте ULONG IoResult уже использовала ошибку win32, но не исходный NTSTATUS (IoResult = RtlNtStatusToDosError(Iosb->Status) и примечание ULONG_PTR NumberOfBytesTransferred (vs ULONG dwNumberOfBytesTransfered из _ 55_ (LPOVERLAPPED_COMPLETION_ROUTINE) функция обратного вызова и сравните ее с ULONG_PTR Information из _ 58_.)

#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);
}
person RbMm    schedule 25.12.2016
comment
для комментария buf я знаю, что такое локальная переменная. Если вы посмотрите на код более внимательно, вы увидите, что структура сервера имеет 2 поля (данные и размер) и что макрос LOCAL_APPEND_DATA добавляет buf к полю данных. Так что это вообще не проблема ... - person vtorri; 25.12.2016
comment
@vtorri - нет. вы передаете указатель на этот буфер в ReadFileEx? LOCAL_APPEND_DATA здесь абсолютно не связан. вы передаете buf в качестве входного параметра в ReadFile(svr->pipe, buf, sizeof(buf), NULL, lpOverlapped); - поэтому buf должен быть действителен все время, пока операция не будет завершена. но вы выходите из функции. вы должны понять этот момент. это важно - person RbMm; 25.12.2016
comment
для ReadFileEx () в server_new () это была одна из моих попыток заставить его работать. Про уникальное перекрытие я не знал (новичок в iocp). Вы говорите, что мне нужно выделять эту структуру перед каждым вводом-выводом. Итак, когда, собственно, мне следует выделить это в коде? - person vtorri; 25.12.2016
comment
для буф, ок. Так что добавления buf в структуру сервера должно быть достаточно, верно? - person vtorri; 25.12.2016
comment
overlapped необходимо выделять непосредственно перед каждой операцией ввода-вывода. - person RbMm; 25.12.2016
comment
Так что добавления buf в структуру сервера должно быть достаточно, верно - да - person RbMm; 25.12.2016
comment
для GetLastError (), ок. я буду использовать dwErrorCode - person vtorri; 25.12.2016
comment
и не смешивать apc с iocp - это ошибка. используйте только iocp с ReadFile. без Ex - person RbMm; 25.12.2016
comment
я буду использовать dwErrorCode - да, и существует один тонкий момент - в обратном вызове dwErrorCode, заполненном из IO_STATUS_BLOCK.Status - и это заполнено ядром. так что здесь не код ошибки win32, а NTSTATUS - person RbMm; 25.12.2016
comment
Следует ли заменить ReadFileEx на ReadFile в server_new и действительно ли должен быть Readfile (Ex) в server_new? - person vtorri; 25.12.2016
comment
вы можете использовать любой ReadFileEx или ReadFile, но lpCompletionRoutine должен быть 0, если вы привязываете файл к IOCP. или если вы используете lpCompletionRoutine, вы не должны привязываться к IOCP - person RbMm; 25.12.2016
comment
большое спасибо. Я портирую его, так как мне нужно использовать код в среде, которая принимает только код C. - person vtorri; 25.12.2016
comment
что означает имя класса UIRP? - person vtorri; 22.01.2017
comment
Кроме того, зачем создавать указатель UIRP в методах CPipe Listen (), Write () и Read ()? (кстати, должна быть утечка памяти, вот если не ошибаюсь) - person vtorri; 22.01.2017
comment
@vtorri - UIRP - заполнитель для OVERLAPPED и параметров работы. конечно без утечек памяти. для любой операции, в которой я выделяю irp, я вызываю irp->CheckError(f, _pio);, если dwErrorCode != ERROR_IO_PENDING я прямой вызов OnComplete, иначе _IoCompletionCallback будет вызываться по завершении операции (ищите метод BindIoCompletion), который вызывает OnComplete - и в этом примечании к функции - delete this. использование UIRP - это ключевой момент. если вы не понимаете этого - вы не понимаете ничего. - person RbMm; 22.01.2017
comment
@vtorri - опять же для каждой асинхронной операции нам нужно иметь unique OVERLAPPED. он должен быть выделен перед любой этой операцией и освобожден после завершения операции. - person RbMm; 22.01.2017
comment
я хотел знать значение «UIRP. Вроде аббревиатура. что это ? - person vtorri; 22.01.2017
comment
@vtorri - знакомы ли вы с IRP - это U аналог этого режима ser и имеет такое же время жизни - person RbMm; 22.01.2017
comment
в вашем примере BindIoCompletionCallback, если я не ошибаюсь, член _dataSize CPipe всегда равен 0. Это нормально? Кроме того, OnConnect всегда возвращает TRUE, так какова цель теста в OnComplete (случай pipe_connect)? Вы можете просто позвонить в OnConnect, а затем прочитать - person vtorri; 07.02.2017
comment
@vtorri - в этом конкретном примере ClientPipe::OnRead я не использую _dataSize. но можно его буферизовать - _dataSize += cbTransferred (скажем, если вы обнаружите неполные данные). ClientPipe::OnConnect is always returning TRUE - ClientPipe является реализацией базового класса CPipe. это только (ClientPipe код) демонстрация (в отличие от CPipe - это настоящий код). в этой демонстрации я всегда возвращаю ИСТИНА. но другая реализация может вернуть и FALSE. базовый класс CPipe не может знать, что возвращает виртуальная функция OnConnect, которая реализована в производном классе - person RbMm; 07.02.2017
comment
Я перенес ваш код в код C и во время тестов я сначала запускаю сервер (ни один клиент не запущен). Итак, ConnectNamedPipe возвращает ERROR_PIPE_LISTENING (согласно MSDN), следовательно, в OnComplete значение ошибки - STATUS_PIPE_LISTENING, что не управляется в операторах switch (следовательно, сигнал SIGTRAP запускается из-за __debugbreak). Как мне вести это дело? - person vtorri; 03.03.2017
comment
@ vtorri - вы используете PIPE_NOWAIT флаг в CreateNamedPipe вызове? ERROR_PIPE_LISTENING возвращается только для этого случая (неблокирующий режим) - я не использую этот флаг / режим в текущем коде - person RbMm; 03.03.2017
comment
я не использую PIPE_NOWAIT. Я просто переписал ваш код c ++ на C, следуя той же схеме объектно-ориентированного программирования: pastebin.com/n5JXyp1Z (скомпилировано с помощью gcc , а не vc ++) Обратная трассировка: pastebin.com/T5WBNj8i Как вы можете видеть на кадре № 0, ошибка 3221225651 (что на самом деле является 0xC00000B3 в шестнадцатеричной системе), то есть STATUS_PIPE_LISTENING - person vtorri; 03.03.2017
comment
@vtorri - ваша ошибка в _pipe_set функции - строка 372 - вы вызываете _pipe_on_complete((IO_Obj *)p, 0, PIPE_CONNECT, NULL, 0) для сервера и клиента. но здесь нужен только клиент. еще раз загляните в мой код - pastebin.com/ftXukV4m (строка 261) - bServer ? Listen() : OnComplete(0, pipe_connect, 0, 0), TRUE; - в результате вы называете завершенным для подключите, когда он действительно не завершен. чем вызвать чтение, когда нет соединения и, конечно же, возникла ошибка STATUS_PIPE_LISTENING - person RbMm; 04.03.2017
comment
@vtorri - также вывести ошибку в шестнадцатеричном формате (% x), это будет намного удобнее для чтения, похоже, вы не используете подсчет ссылок, что тоже не так, и ntdll has no import lib - конечно, ложь - он существует (ntdll.lib и ntdllp.lib в любой WDK установке) - person RbMm; 04.03.2017
comment
@vtorri - _io_obj_new похоже не используешь, а когда выделяешь объект - не выставляешь refcount = 1 - это фатальная ошибка - person RbMm; 04.03.2017
comment
действительно, я не устанавливал часть io_obj, большое спасибо, теперь вроде работает. Что касается ntdll, мы (наш проект) не можем полагаться на то, что наши пользователи установили WDK. Так что LoadLibrary () достаточно, и в любом случае правильно, afaik - person vtorri; 04.03.2017
comment
в вашем коде, какой смысл вызывать Release () сразу после Create ()? не следует ли вызывать Release () в конце (например, после закрытия диалогового окна)? - person vtorri; 04.03.2017
comment
@vtorri - вы совершенно заблуждаетесь насчет ntdll.lib - эта библиотека нужна только вам (например, и kernel32.lib), вам (но не клиентам) нужно установить WDK (или скопировать отдельный ntdll.lib). ntdll.dll - всегда присутствует в любых окнах (или окна не загружаются вообще). - person RbMm; 04.03.2017
comment
@vtorri - моя база кода для подсчета ссылок - нужно вызвать Release для объекта после того, как мы его больше не используем. я создаю объект - при ref = 1 любая операция io начинается +1 ссылка, конец операции io -1 ссылка. - person RbMm; 04.03.2017
comment
Предположим, что сообщение длиннее, чем буфер (например: установка PAGE_SIZE на 5 вместо 512 в вашем коде), есть ли способ получить уведомление о том, что для этого сообщения больше нет данных для чтения? - person vtorri; 04.03.2017
comment
прочитать файл return min(cbDataExist, cbBuffer). если cbDataExist > cbBuffer, вы сможете прочитать это при следующих звонках. обычная ситуация - person RbMm; 04.03.2017
comment
что такое cbBuffer и cbDataExists? В документации Readfile () указано, что у него есть nNumberOfBytesToRead (размер буфера) и lpNumberOfBytesRead, указатель, который должен быть установлен в NULL, если используется FILE_FLAG_OVERLAPPED (наш случай) - person vtorri; 05.03.2017
comment
По-прежнему существует разница в выводе вашего кода и моего порта (я добавил printf в вывод CheckError. С вашим кодом с PAGE_SIZE == 5: pastebin.com/KAQUPcPp и вывод с моим кодом с тем же размером страницы: pastebin.com / b93pMbXp - person vtorri; 06.03.2017