Можно ли изменить HANDLE, который был открыт для синхронного ввода-вывода, чтобы он был открыт для асинхронного ввода-вывода в течение его срока службы?

Большая часть моей ежедневной работы по программированию в Windows в настоящее время связана с операциями ввода-вывода всех видов (каналы, консоли, файлы, сокеты и т. Д.). Я хорошо осведомлен о различных методах чтения и записи из / в различные типы дескрипторов (синхронное, асинхронное ожидание завершения событий, ожидание обработчиков файлов, порты завершения ввода-вывода и предупреждаемый ввод-вывод). Мы используем многие из них.

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

Итак, сначала я бы спросил:

Предположим, у меня есть ручка:

HANDLE h;

который откуда-то был получен моим процессом для ввода-вывода. Есть ли простой и надежный способ узнать, с какими флагами он был создан? Основной рассматриваемый флаг - FILE_FLAG_OVERLAPPED.

Единственный способ, который мне пока известен, - это попытаться зарегистрировать такой дескриптор в порту завершения ввода-вывода (используя CreateIoCompletionPort()). Если это успешно, дескриптор был создан с помощью FILE_FLAG_OVERLAPPED. Но тогда должен использоваться только порт завершения ввода-вывода, так как дескриптор не может быть отменен для него без закрытия самого HANDLE h.

Если есть простой способ определить наличие FILE_FLAG_OVERLAPPED, у меня возникнет второй вопрос:

Есть ли способ добавить такой флаг к уже существующему дескриптору? Это сделает дескриптор, изначально открытый для синхронных операций, открытым для асинхронных. Будет ли способ создать противоположное (удалить FILE_FLAG_OVERLAPPED, чтобы создать синхронный дескриптор из асинхронного)?

Я не нашел прямого пути после прочтения MSDN и большого количества поисковых запросов. Был бы хоть какой-нибудь трюк, который мог бы сделать то же самое? Например, воссоздать ручку таким же образом, используя функцию CreateFile() или что-то подобное? Что-то даже частично задокументированное или вообще не задокументированное?

Основное место, где мне это может понадобиться, - это определить способ (или изменить способ), которым процесс должен читать / писать из дескрипторов, отправленных ему сторонними приложениями. Мы не можем контролировать, как сторонние продукты создают свои дескрипторы.

Уважаемые гуру Windows: помогите пожалуйста!

С уважением

Мартин


person Martin Dobšík    schedule 19.03.2010    source источник
comment
Обратите внимание, что трюк CreateIoCompletionPort() на самом деле довольно изящный, если предположить, что вы действительно хотите использовать дескриптор с этим конкретным портом. Не думал об этом!   -  person André Caron    schedule 31.03.2015


Ответы (7)


Вижу, я плохо читал MSDN: / Я полностью пропустил функцию ReOpenFile(), который был представлен, вероятно, еще в июне 2003 года в Windows Server 2003 (согласно эта статья). Чтобы хоть немного защитить себя: я ожидаю, что CreateFile() описание будет перекрестной ссылкой с ReOpenFile() описанием. На ReOpenFile() странице есть ссылка на CreateFile() страницу, но не наоборот.

Эта функция, кажется, дает именно то, что мне нужно: добавление или удаление FILE_FLAG_OVELRAPPED в / из уже существующих дескрипторов путем создания нового дескриптора с желаемыми свойствами! :-D Но я еще не тестировал. К сожалению, он доступен только в Windows 2003 Server, Windows Vista и более поздних версиях. На вопрос о предыдущих версиях ОС был дан ответ здесь. Эта функция не существует в общедоступном API в ОС до Windows 2003 Server. Он используется базовой реализацией, но недоступен для разработчиков в этих системах (не поддерживается).

Это практически означает, что у меня нет надежды, по крайней мере, в ближайшие несколько лет, пока мы не прекратим поддержку старых платформ Windows. Это также означает, что ситуация с вводом-выводом ДЕЙСТВИТЕЛЬНО плохая в ОС старше Windows Vista. Другой болезненной частью, которой вообще не хватало, была возможность отмены синхронного и асинхронного ввода-вывода в старых системах.

Более того, я все еще упускаю одну часть ответа: можно ли каким-либо образом проверить наличие флагов? Я не нашел для этого функции. Это означает, что если мы хотим гарантировать наличие какого-либо флага в файловом объекте, то файл всегда нужно открывать заново.

person Martin Dobšík    schedule 19.03.2010
comment
Фактически, если я просматриваю свою локальную копию MSDN, я вижу, что функция не упоминается (не упоминается) абсолютно нигде! Он указан только в списке функций управления файлами. Также Google дает на удивление мало обращений. Это вызывает сомнения в его надежности. Я очень хочу провести первые тесты. Я хотел бы, например, увидеть дескриптор консоли с включенным флагом OVERLAPPED. - person Martin Dobšík; 20.03.2010
comment
можно ли каким-либо образом проверить наличие флагов? хороший вопрос, есть ответ? - person elmarco; 02.07.2012
comment
Примечание для читателей: ReOpenFile() аккуратно работает для файлов, поскольку вы можете повторно открыть файловый поток и добавить поддержку асинхронного ввода-вывода в новый дескриптор, но это не работает с анонимными каналами (всегда возвращает ERROR_PIPE_BUSY). - person André Caron; 31.03.2015

Прошло 3 года и вышла Windows 8. Благодаря регрессу, появившемуся в реализации консоли в Windows 8, мне пришлось что-то делать с проблемой, которая вызвала этот вопрос. Итак, я наконец попытался использовать вызов функции ReOpenFile ().

Одним предложением: для моих целей это бесполезно.

API ReOpenFile () используется для «взятия существующего дескриптора файла и получения другого дескриптора с другим набором прав доступа». По крайней мере, это указано в исходной статье.

Я попытался использовать ReOpenFile () в дескрипторе ввода консоли:

  stdin_in = GetStdHandle(STD_INPUT_HANDLE);
  stdin_in_operlapped = ReOpenFile(stdin_in, GENERIC_READ | GENERIC_WRITE,
                                   FILE_SHARE_READ, FILE_FLAG_OVERLAPPED);
  if (stdin_in_operlapped ==  INVALID_HANDLE_VALUE)
    {
      my_debug("failed to ReOpen stdin handle with OVERLAPPED flag: %d", GetLastError());
      exit(1);
    }

И я получаю следующее: ошибка 1168: «Элемент не найден». «Спасибо, Microsoft». Я даже не буду пытаться использовать его для анонимных каналов, поскольку в документации указано:

«Асинхронные (перекрывающиеся) операции чтения и записи не поддерживаются анонимными каналами. Это означает, что вы не можете использовать функции ReadFileEx и WriteFileEx с анонимными каналами. Кроме того, параметр lpOverlapped команд ReadFile и WriteFile игнорируется, когда эти функции используются с анонимными каналами ».

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

когда для некоторых объектов было выполнено синхронное чтение (по крайней мере, анонимные каналы и консольный ввод в Windows 8), тогда вызов CloseHandle () из другого потока с тем же дескриптором либо завершится ошибкой, либо зависнет, пока ReadFile () не завершится; это означает, что во многих случаях он будет зависать на неопределенное время. Вот почему я хотел заменить синхронный дескриптор асинхронным.

Теперь мне ясно, что в операционных системах Windows просто невозможно отменить некоторые операции чтения прямым способом. При чтении из синхронных дескрипторов нужно просто выйти из приложения, даже если ReadFile () все еще читает из дескриптора в каком-то потоке, потому что надежно разбудить такую ​​операцию чтения просто невозможно. Знаю ... В более новой ОС эту операцию можно отменить. Однако нет никакого способа узнать, находится ли поток уже в вызове ReadFile () или еще нет. Если ReadFile () еще не вызван, значит, операция для отмены отсутствует, и последующее чтение зависнет. Единственный способ - закрыть дескриптор, но эта операция зависает или дает сбой на некоторых объектах и ​​в некоторых операционных системах. Единственное правильное решение - асинхронный ввод-вывод. Но, как я упоминал в начале, наше приложение запускается сторонними приложениями, и мы не можем заставить их все всегда создавать именованные каналы с перекрывающимся флагом, установленным для stdio.

Я сдаюсь и собираюсь реализовать неприятные уродливые хаки ... нам придется продолжать читать без структуры OVERLAPPED из РУЧКИ, которые были созданы с флагом OVERLAPPED, и утечки дескрипторов и потоков ....

person Martin Dobšík    schedule 10.05.2013
comment
Мой совет: никогда не используйте анонимные каналы! Поведение функции Win32 API практически идентично созданию прослушивателя канала со случайным именем (CreateNamedPipe), за которым следуют ConnectNamedPipe и CreateFile (в целях безопасности вы должны затем передать одноразовый номер по каналу, чтобы подтвердить, что другой процесс не подключился). Использование вашей собственной CreatePipe2 оболочки дает вам необходимый контроль над тем, перекрываются ли трубы или нет. И, если я правильно читаю документацию, полученные дескрипторы каналов можно повторно открыть с помощью ReOpenFile, чтобы изменить режим перекрытия. - person Nicholas Wilson; 03.06.2015
comment
Это не удалось, потому что вы используете дескриптор КОНСОЛИ, а не дескриптор файла. - person Demi; 23.12.2017
comment
@NicholasWilson Это потому, что анонимные каналы реализованы как именованные каналы под капотом! - person Demi; 14.01.2018

Если я понимаю, что вам нужно, я хотел бы предложить вам все равно, был ли открыт с перекрывающимся флагом или нет. Я считаю, что вы можете безопасно передавать структуру OVERLAPPED как в синхронном, так и в асинхронном случаях. Ваш код должен уметь обрабатывать ReadFile() возврат false и GetLastError() возврат ERROR_IO_PENDING. Вам также нужно будет добавить соответствующие вызовы в GetOverlappedResult(), WaitForSingleObject() и т. Д.

В статье MSDN о ReadFile() есть полезная информация по этому поводу в разделах «Рекомендации по работе с синхронными дескрипторами файлов» и «Рекомендации по работе с асинхронными дескрипторами файлов» в разделе «Синхронизация и положение файла».

person Brett    schedule 02.11.2010
comment
Я считаю, хм .. вы можете подтвердить? :) - person elmarco; 02.07.2012

Тестирование флагов дескрипторов, вероятно, следует проводить так же, как тестирование разрешений, с которыми был создан дескриптор. Попробуй. Если API не работает, попробуйте откат. Если это не удается, вернуть ошибку.

Я думаю, что действительно показательным является то, как в документации для ReadFile говорится: «Если hFile открывается с помощью FILE_FLAG_OVERLAPPED, ... функция может неверно сообщить, что операция чтения завершена».

Моя интерпретация ошибки такова (и вопрос, который вы должны задать себе): если можно было проверить перекрывающийся статус дескриптора файла, почему ReadFile не выполняет эту проверку, а затем соответственно проверяет структуру OVERLAPPED, чтобы явно потерпеть неудачу, если вызывается без перекрытия с перекрывающимся дескриптором?

person Chris Becke    schedule 19.03.2010
comment
Я задаю этот и многие другие подобные вопросы. к сожалению, прямых ответов от Microsoft нет. Вот почему я спрашиваю здесь. Я не нашел другого способа проверить наличие флага OVERLAPPED, кроме описанного выше. - person Martin Dobšík; 19.03.2010

Я не знаю способа определить флаг дескриптора и побочные эффекты использования ReOpen api, но поскольку ваша цель была

было бы очень полезно иметь только один способ обрабатывать все ручки

Если вы хотите синхронного поведения (я имею в виду использование синхронного api для неперекрывающихся дескрипторов и асинхронного api, подаваемого со структурой OVERLAPPED с последующим ожиданием наложенного события) вы всегда можете использовать async api, также если дескриптор был открыт в режиме без перекрытия.

void connectSynchronous(HANDLE hPipeThatWeDontKnowItsFlag){
    ...
    BOOL bRet = ::ConnectNamedPipe(hPipeThatWeDontKnowItsFlag, pOverlapped);

    if(bRet == FALSE){
        DWORD dwLastErr = ::GetLastError();

        if(dwLastErr == ERROR_IO_PENDING){
            //The handle was opened for asynchronous IO so we have to wait for the operation
            ...waitFor on the overlapped hEvent;

        }else if(dwLastErr == ERROR_PIPE_CONNECTED){
            //The handle was opened for synchronous IO and the client was already connected before this call: that's OK!
            return;
        }else{
            throw Error(dwLastErr);
        }
    }/*else{
        //The handle was opened for synchronous IO and the client has connected: all OK
    }*/
}
person MrAduer    schedule 29.01.2013

Альтернативой взлому CreateIoCompletionPort является создание файла ReadFile с нулевым байтом и NULL lpOverlapped. Если это не удается с ERROR_INVALID_PARAMETER, предположим, что он был открыт с FILE_FLAG_OVERLAPPED.

person Glen Knowles    schedule 21.02.2017

NtSetInformationFile, FileModeInformation, снять флажок FILE_SYNCHRONOUS_IO_NONALERT.

Поведение различается в зависимости от файловой системы. (например, трубы предоставляются НПФС)

Я пробовал использовать анонимный канал в Windows XP и потерпел неудачу.

person youfu    schedule 21.08.2020