Подтвердить что ты не робот

С# UnauthorizedAccessException при включении MessageMode для канала с именем только для чтения (класс NamedPipeClientStream)

Проблема с классом NamedPipeClientStream в .NET заключается в том, что вы не можете создать экземпляр этого класса с помощью PipeDirection.In, а затем успешно изменить ReadMode на PipeTransmissionMode.Message.

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

var pipeOut = new NamedPipeServerStream("SomeNamedPipe", 
                                        PipeDirection.Out, 
                                        1, 
                                        PipeTransmissionMode.Message);
var pipeIn = new NamedPipeClientStream(".", 
                                       "SomeNamedPipe", 
                                       PipeDirection.In);
pipeIn.Connect();
pipeIn.ReadMode = PipeTransmissionMode.Message;

Этот код генерирует UnauthorizedAccessException при попытке установить свойство ReadMode.


В поисках информации по этой проблеме я нашел ссылки на нее в других местах, например здесь:

Все эти сообщения упоминают, что это "странно", "странно" и т.д., но не объясняют "почему" это не работает, и все они дают одинаковое обходное решение, что "по какой-то странной причине" направление трубы на InOut заставляет его работать.

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


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

4b9b3361

Ответ 1

Глядя на исходный источник Microsoft, я мог видеть, что настройка свойства ReadMode просто вызывает функцию win32 SetNamedPipeHandleState для выполнения операции, при этом ошибки этого вызова возникают как исключения. В соответствии с документацией Функция SetNamedPipeHandleState в ней указывается о дескрипторе канала, чтобы вызвать эту функцию

дескриптор должен иметь доступ GENERIC_WRITE к именованному каналу для только для записи или чтения/записи, или он должен иметь GENERIC_READ и FILE_WRITE_ATTRIBUTES доступ для канала только для чтения.

В этом и заключается проблема.

Если мы посмотрим на конструкторы для NamedPipeClientStream, которые принимают параметр PipeDirection, мы видим, что они запрашивают только GENERIC_READ доступ для PipeDirection.In и GENERIC_WRITE доступ для PipeDirection.Out (или оба для InOut). Это означает, что любая труба, открытая в режиме Out или InOut, будет работать, поскольку для этих случаев достаточно доступа GENERIC_WRITE, но нам нужны как GENERIC_READ, так и FILE_WRITE_ATTRIBUTES для канала только для чтения, который NamedPipeClientStream класс никогда не запрашивает. Это дефект в классе и должен быть исправлен Microsoft.

Я отправил отчет об ошибке в Microsoft Connect здесь:

https://connect.microsoft.com/VisualStudio/feedback/details/1825187

Пожалуйста, проголосуйте, если вы столкнетесь с этой проблемой самостоятельно, это может помочь ускорить исправление.


До исправления (ни один из 3/2017) эту проблему можно обойти полностью, используя другой конструктор для NamedPipeClientStream.

Вместо этого существует одна перегрузка конструктора, который принимает вместо перечисления PipeDirection перечисление PipeAccessRights, где вы можете указать конкретную комбинацию прав доступа, которые вы хотите получить для дескриптора. Затем конструктор выводит направление канала из комбинации указанных прав доступа (In, если указан ReadData, Out ", если указан WriteData, InOut, если они оба указаны).

Это означает, что вы можете решить эту проблему, не делая ваши дуплексные дуги, просто меняя конструкторную строку следующим образом:

var pipeIn = new NamedPipeClientStream("<ServerName>", "<PipeName>", PipeDirection.In);

:

var pipeIn = 
   new NamedPipeClientStream("<ServerName>", 
                             "<PipeName>", 
                             PipeAccessRights.ReadData | PipeAccessRights.WriteAttributes, 
                             PipeOptions.None, 
                             System.Security.Principal.TokenImpersonationLevel.None, 
                             System.IO.HandleInheritability.None);

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