1

Тема: AHK: асинхронный именованный канал (pipe)

Здравствуйте, а у кого-нибудь получалось настроить асинхронную работу именованных каналов в ahk?
У меня получается только синхронная.
Насколько я понимаю, для асинхронной работы надо:
1. При создании именованного канала (CreateNamedPipe) использовать флаг FILE_FLAG_OVERLAPPED.
И вот тут уже начинается немного мути:
Судя по

If overlapped mode is not enabled, functions performing read, write, and connect operations on the pipe handle do not return until the operation is finished.

использования этого флага должно бы быть достаточно, но практика показывает, что, например, операция соединения сразу же не возвращается, а продолжает висеть до первого успешного соединения (т.е. когда клиент обратится к этому именованному каналу).
Поэтому, требуется использовать и флаг PIPE_NOWAIT, невзирая на

Note that nonblocking mode is supported for compatibility with Microsoft LAN Manager version 2.0 and should not be used to achieve asynchronous I/O with named pipes.

2. При соединении именованного канала (ConnectNamedPipe) требуется указать на валидную OVERLAPPED структуру.
И из-за того, что используется PIPE_NOWAIT - команда вернёт 0 с ошибкой

[#536] Идет ожидание открытия процессом другой стороны канала.
[#536] Waiting for a process to open the other end of the pipe.

т.к. клиент ещё не обратился к каналу.

3. После этого следует как-то отловить момент обращения клиента к каналу

4. При обращении клиента к каналу - сервер должен записать в канал данные (WriteFile/WriteFileEx)

Отсюда возникают сложности и вопросы.
Насколько я понимаю, следуя логике msdn - отслеживать момент подключения клиента каналу надо через состояние события, созданного через CreateEvent. Указатель на это событие должен быть использован при создании OVERLAPPED структуры.
Это вроде не сложно сделать (вроде), но вот как потом проверять состояние этого события?
Msdn говорит, что макросом HasOverlappedIoCompleted указывая ему на OVERLAPPED структуру, но msdn в то же время говорит, что проверять его стоит только если последняя ошибка равняется 997 (ERROR_IO_PENDING).
Это что же, надо вызывать ConnectNamedPipe заново? Пробовал, всегда одна и та же ошибка №536.

Как правильно-то делать?

2

Re: AHK: асинхронный именованный канал (pipe)

Я этой темой не интересовался, не смогу подсказать, но в любом случае подобный пост стоило бы начать с описания задачи, которую пытаетесь решить. Ну и привести какой-нибудь рабочий код для примера.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

3 (изменено: ШКОЛЯРРРРРРЪ, 2016-10-27 20:10:08)

Re: AHK: асинхронный именованный канал (pipe)

Вроде задачу описал…
Именованные каналы используются для передачи данных двух типов: сообщений и байтов. Я хочу байты передавать.
Передача возможна как по сети, так и локально, для организации межпроцессной коммуникации.
Рабочего кода нет, по этой причине и пишу сюда.
Есть лишь отдельные куски на вызов команд.

; server
OnExit, Exit
PIPE_ACCESS_DUPLEX := 3, FILE_FLAG_OVERLAPPED := 0x40000000, FILE_FLAG_WRITE_THROUGH := 0x80000000, PIPE_NOWAIT := 1

pipe_name := "test", open_mode := PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH, pipe_mode := PIPE_NOWAIT, content := (A_IsUnicode ? Chr(0xFEFF) : Chr(239) Chr(187) Chr(191)) "MsgBox, Hello world!"	; With BOM prepended.

testHWND := DllCall("CreateNamedPipe", "Str","\\.\pipe\" pipe_name , "UInt",open_mode, "UInt",pipe_mode, "UInt",255, "UInt",0, "UInt",0, "UInt",0, "Ptr",0, "Ptr")	; использоваться не будет, но особенность работы каналов в ahk требует этого.
pipeHWND := DllCall("CreateNamedPipe", "Str","\\.\pipe\" pipe_name , "UInt",open_mode, "UInt",pipe_mode, "UInt",255, "UInt",0, "UInt",0, "UInt",0, "Ptr",0, "Ptr")

overlapped := ""
VarSetCapacity(overlapped, 8 + 3 * A_PtrSize, 0)
eventHwnd := DllCall("CreateEvent", "Ptr",0, "Int",true, "Int",0, "Ptr",0, "Ptr")
NumPut(eventHwnd, overlapped, 8 + 2 * A_PtrSize, "Ptr")

DllCall("ConnectNamedPipe", "Ptr",pipeHWND, "Ptr",overlapped, "Int")

If (A_LastError == 536)
	MsgBox, Connecting to a pipe resulted into error 536`nWaiting for a process to open the other end of the pipe.
ExitApp

CallThisWhenClientConnects:
	DllCall("WriteFile", "Ptr",pipeHWND, "Str",content, "UInt",(StrLen(content) + 1) * (A_IsUnicode ? 2 : 1), "UIntP",0, "Ptr",overlapped, "Int")
	DllCall("CloseHandle", "Ptr",pipeHWND, "Int")	; это неправильно, но без этого, почему-то, не работает.
Return

Exit:
	DllCall("CloseHandle", "Ptr",testHWND, "Int")
	DllCall("CloseHandle", "Ptr",pipeHWND, "Int")
	ExitApp
; client
pipe_name := "test"
Run, %A_AhkPath% "\\.\pipe\%pipe_name%"

После WriteFile почему-то не отдаётся сигнал о завершении передачи на данных по каналу клиенту до тех пор, пока я не вызову CloseHandle на сервере - это ещё одна проблема, но оказывается, что она известна и по ссылке приводится решение, которое я не уверен, что подойдёт для ahk: как вызов метода read должен спасти в ситуации, где, насколько я понимаю, требуется решение на серверной стороне?

4

Re: AHK: асинхронный именованный канал (pipe)

ШКОЛЯРРРРРРЪ пишет:

FILE_FLAG_OVERLAPPED := 0xc0000000

Вообще-то значение должно быть 0x40000000. У вас тут ещё добавлено FILE_FLAG_WRITE_THROUGH. Оно точно не мешает?

5

Re: AHK: асинхронный именованный канал (pipe)

Вот набросал пример подключения. Сначала запустите сервер, потом клиент.


; Сервер
PIPE_ACCESS_DUPLEX := 3, FILE_FLAG_OVERLAPPED := 0x40000000, PIPE_TYPE_BYTE := 0
INVALID_HANDLE_VALUE := -1, ERROR_IO_PENDING := 997, ERROR_IO_INCOMPLETE := 996
PipeName := "HelloPipe", FuncSuffix := A_IsUnicode? "W":"A"

hPipe := DllCall("CreateNamedPipe" . FuncSuffix
        , "str", "\\.\pipe\" . PipeName
        , "uint", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED
        , "uint", PIPE_TYPE_BYTE
        , "uint", 1
        , "uint", 0
        , "uint", 0
        , "uint", 0
        , "ptr", 0
        , "ptr")
If (hPipe = INVALID_HANDLE_VALUE) {
    MsgBox, Ошибка CreateNamedPipe: %A_LastError%
    Return
}
VarSetCapacity(OVERLAPPED, 8 + 3 * A_PtrSize, 0)
hEvent := DllCall("CreateEvent", "ptr", 0, "int", True, "int", False, "ptr", 0, "ptr")
If (hEvent = 0) {   
    MsgBox, Ошибка CreateEvent: %A_LastError%
    Return
}
NumPut(hEvent, OVERLAPPED, 8 + 2 * A_PtrSize, "ptr")
Ret := DllCall("ConnectNamedPipe", "ptr", hPipe, "ptr", &OVERLAPPED)
If (Ret = 0 && A_LastError != ERROR_IO_PENDING) {
    MsgBox, Ошибка ConnectNamedPipe: %A_LastError%
    Return
}

Loop
{
    Ret := DllCall("GetOverlappedResult", "ptr", hPipe, "ptr", &OVERLAPPED
                                        , "ptr", 0, "int", False)
    If (Ret != False) {
        Break
    }
    If (A_LastError != ERROR_IO_INCOMPLETE) {
        MsgBox, Ошибка GetOverlappedResult: %A_LastError%
        Return
    }
}

MsgBox, Сервер`nКлиент подключился.

; Клиент
GENERIC_READ := 0x80000000, GENERIC_WRITE := 0x40000000
FILE_SHARE_READ := 1, FILE_SHARE_WRITE := 2
OPEN_EXISTING := 3, FILE_FLAG_OVERLAPPED := 0x40000000
INVALID_HANDLE_VALUE := -1
PipeName := "HelloPipe", FuncSuffix := A_IsUnicode? "W":"A"
hPipe := DllCall("CreateFile" . FuncSuffix
            , "str", "\\.\pipe\" . PipeName
            , "uint", GENERIC_READ | GENERIC_WRITE
            , "uint", FILE_SHARE_READ | FILE_SHARE_WRITE
            , "ptr", 0
            , "uint", OPEN_EXISTING
            , "uint", 0
            , "uint", FILE_FLAG_OVERLAPPED
            , "ptr", 0
            , "ptr")
If (hPipe = INVALID_HANDLE_VALUE) {
    MsgBox, Ошибка CreateFile: %A_LastError%
    Return
}
MsgBox, Клиент`nСервер подключен.

6

Re: AHK: асинхронный именованный канал (pipe)

Во-первых, спасибо за попытку помочь, тут дело очень сложное.

YMP пишет:

Вообще-то значение должно быть 0x40000000. У вас тут ещё добавлено FILE_FLAG_WRITE_THROUGH. Оно точно не мешает?

Верно подмечено, я уж и забыл, что и FILE_FLAG_WRITE_THROUGH добавил. Вероятно, стоило тестировать и без него (над проблемой я уже неделю бьюсь).

YMP пишет:

Вот набросал пример подключения. Сначала запустите сервер, потом клиент.

Я пока пытаюсь его разобрать и это лишь часть комментариев/вопросов по вашему коду:
1. Не знаю зачем вы используете FuncSuffix := A_IsUnicode? "W":"A", ведь в справке по DllCall() сказано:

If no function can be found by the given name, an A (ANSI) or W (Unicode) suffix is automatically appended based on which version of AutoHotkey is running the script. For example, "MessageBox" is the same as "MessageBoxA" in ANSI versions and "MessageBoxW" in Unicode versions.

2. У меня реальный код тоже изобилует всяческими проверками, но некоторые из них не нужны. Например, при использовании флага FILE_FLAG_OVERLAPPED на этапе создания канала - нет смысла проверять возвращаемое ConnectNamedPipe значение, оно всегда будет равно нулю. Я не с целью придраться это пишу, а просто я уже документацию раз 20, наверно, прочитал и потому и делюсь мелкими нюансами. Я-то сначала вообще всё через try dllcall(…) throw exception(…) сделал, а потом понял, что я это сииильно погорячился так делать для каждой команды и что не всегда ошибка означает что-то плохое.
3. Благодаря вам заметил у себя в коде ошибку в вызове ConnectNamedPipe:
DllCall("ConnectNamedPipe", "ptr", hPipe, "ptr", &OVERLAPPED)
из-за того, что я отправлял саму структуру, а не её адрес (я запутался и зря полагался на "Ptr") - я как раз и наблюдал то поведение, которое я описал в первом посте и которое противоречит документации msdn:

ШКОЛЯРРРРРРЪ пишет:

И вот тут уже начинается немного мути:
Судя по

If overlapped mode is not enabled, functions performing read, write, and connect operations on the pipe handle do not return until the operation is finished.

использования этого флага должно бы быть достаточно, но практика показывает, что, например, операция соединения сразу же не возвращается, а продолжает висеть до первого успешного соединения (т.е. когда клиент обратится к этому именованному каналу).
Поэтому, требуется использовать и флаг PIPE_NOWAIT, невзирая на

Note that nonblocking mode is supported for compatibility with Microsoft LAN Manager version 2.0 and should not be used to achieve asynchronous I/O with named pipes.

4. Эту же ошибку я и в вызову GetOverlappedResult совершил, поэтому-то она всегда и выдавала мне false, а ещё я заметил, что вместо GetOverlappedResult я ошибочно вызывал HasOverlappedIoCompleted.
5. Сейчас я сам попытаюсь на основе вашего примера сделать передачу данных, но пока у меня какие-то странные ошибки: конкретно ваш скрипт - работает, а аналогично выстроенный но мой (который базируется на внешней библиотеке с функциями-обёртками вокруг DllCall вызовов) - отказывается. Причём всё идентично и я пока не понимаю в чём дело, плюс началась чертовщина, вроде того, что GetOverlappedResult возвращает пустоту (т.е. false), но при этом и проверка != false срабатывает. :-/ Смотрю длину строки - 0. Но не false. У меня уже шарики за ролики сейчас заезжают, я попозже ещё попытаюсь.

7

Re: AHK: асинхронный именованный канал (pipe)

ШКОЛЯРРРРРРЪ пишет:

1. Не знаю зачем вы используете FuncSuffix := A_IsUnicode? "W":"A", ведь в справке по DllCall() сказано:

Это я то ли пропустил, то ли забыл.

ШКОЛЯРРРРРРЪ пишет:

2. У меня реальный код тоже изобилует всяческими проверками, но некоторые из них не нужны. Например, при использовании флага FILE_FLAG_OVERLAPPED на этапе создания канала - нет смысла проверять возвращаемое ConnectNamedPipe значение, оно всегда будет равно нулю.

Будет равно нулю, но это иногда означает ошибку в работе функции, а иногда нет. Для этого я и проверяю ещё A_LastError. Всё в соответствии с документацией.

ConnectNamedPipe Function пишет:

If the operation is asynchronous, ConnectNamedPipe returns immediately. If the operation is still pending, the return value is zero and GetLastError returns ERROR_IO_PENDING. (You can use the GetOverlappedResult function to retrieve the results after the operation has finished.) If the function fails, the return value is zero and GetLastError returns a value other than ERROR_IO_PENDING or ERROR_PIPE_CONNECTED.

8

Re: AHK: асинхронный именованный канал (pipe)

Хотя нет, вы правы, проверять возвращаемое значение тут бесполезно. Им стоило написать, что в этом случае оно не используется, а не то, что оно равно нулю. А то только с толку сбивают.

9

Re: AHK: асинхронный именованный канал (pipe)

Я с трудом, но разобрался, что за дичь у меня происходит.
У вас в коде есть такой кусок:

    Ret := DllCall("GetOverlappedResult", "ptr", hPipe, "ptr", &OVERLAPPED
                                        , "ptr", 0, "int", False)
    If (Ret != False) {
        Break
    }

И чего-то я не понимаю какого чёрта он ведёт себя иначе, чем такой кусок, который использовался у меня:

    Ret := DllCall("GetOverlappedResult", "ptr", hPipe, "ptr", &OVERLAPPED
                                        , "ptr", 0, "int", False)
    If (Ret) {
        Break
    }

Другими словами: разве if (var) не то же самое, что и If (var != false)?

10

Re: AHK: асинхронный именованный канал (pipe)

Нет, не одно и то же. Если сама команда DllCall не сможет нормально отработать, то Ret будет пустой, а False — это число 0. В этом случае моя проверка будет пройдена, а ваша нет.

11

Re: AHK: асинхронный именованный канал (pipe)

Извините за лирическое отступление (off topic).
Где-то на подсознательном уровне я знал, что это разные проверки, но в них легко запутаться и я такое постоянно забываю, так что для меня это каждый раз открытие, ведь операция присваивания значения в переменную - это же присваивание выражения (expression, правой части), а пустое выражение же, вроде как, то же самое, что и false или 0 (по крайней мере у меня в голове оно так ассоциируется).


Чего-то дальше пока дела медленно двигаются: теперь момент подключения клиента известен, всё работает в асинхронном режиме, но записать содержимое в канал не получалось: по логике msdn я должен это делать вызовом, например, WriteFile и вызов должен бы возвращаться с ошибкой у меня бы должна происходить ошибкой ERROR_IO_PENDING (997, Overlapped I/O operation is in progress), что должно сигнализировать о протекающем процессе передачи.
Но вместо этой ошибки - команда возвращалась с ошибкой ERROR_NO_DATA (232 The pipe is being closed / Идет закрытие канала).
Оказалось, что надо на стороне клиента просто какой-то Sleep хотя бы использовать или подождать иным образом, т.к. WriteFile на сервере начинает отрабатывать уже после того, как клиент завершил CreateFile.

Потом я немного запнулся: клиент после CreateFile должен вызывать ReadFile (или подобную команду).
Заметил, что у вас вызов CreateFile предполагается со флагом FILE_FLAG_OVERLAPPED, но из-за ошибки в коде этого не происходит. В вашем коде для клиентской стороны в вызове CreateFile - лишняя строка:

            , "uint", 0

Ошибку исправил.
Но вот я не уверен, что следующий шаг я делаю правильно: я на клиентской стороне петлёй циклично вызываю ReadFile до тех пор, пока GetLastError не начнёт что-то выдавать, а на серверной стороне или закрываю хэндл (CloseHandle), либо после некоторого Sleep (чтобы клиент успел всё прочитать, но это ненадёжный вариант: я тут Sleep на глазок могу только выставить) инициирую разъединение канала (DisconnectNamedPipe).
Выполнение одного из этих двух действий на стороне сервера оборачивается тем, что на стороне клиента ReadFile генерирует ошибку ERROR_PIPE_NOT_CONNECTED (233 No process is on the other end of the pipe.) в случае, если сервер выполнил DisconnectNamedPipe, или ошибку ERROR_BROKEN_PIPE (109 The pipe has been ended.), в случае, если сервер выполнил CloseHandle.
Пока я остановился на CloseHandle, т.к. он не требует Sleep, но мне кажется, что что-то я делаю не правильно, хоть в итоге всё и работает кое-как.

12

Re: AHK: асинхронный именованный канал (pipe)

А какого эффекта вы хотите добиться?

13

Re: AHK: асинхронный именованный канал (pipe)

А, не берите в голову, оказывается то, чего я хочу (запускать именованные каналы как-будто это файлы) - это немного хак.