为只读命名管道 (NamedPipeClientStream class) 启用 MessageMode 时出现 C# UnauthorizedAccessException

C# UnauthorizedAccessException when enabling MessageMode for read-only named pipe (NamedPipeClientStream class)

.NET 中的 NamedPipeClientStream class 存在问题,您无法使用 PipeDirection.In 创建此 class 的实例,然后成功更改ReadModePipeTransmissionMode.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;

此代码将在尝试设置 ReadMode 属性 时抛出 UnauthorizedAccessException


在搜索有关此问题的信息时,我在其他地方找到了对它的引用,例如此处:

所有这些帖子都提到这是 "weird"、"odd" 等,但没有解释 "why" 它不起作用,并且都给出了相同的解决方法, "for some strange reason" 将管道方向设置为 InOut 使其工作。

这确实使它起作用,但它需要从根本上改变管道的定义,在两端,全双工而不是单向,我认为这是一种非常糟糕的方法,除非您能够同时更改客户端和服务器,否则这甚至是不可能的。


我的问题是,为什么在入站管道上启用消息模式会导致异常,有没有比将管道更改为双向模式更好的方法来解决这个问题?

查看 Microsoft 参考源,我可以看到设置 ReadMode 属性 只是调用 win32 SetNamedPipeHandleState 函数来执行操作,此调用引发的错误为例外。根据文档 SetNamedPipeHandleState function,它说明了管道句柄,以便调用此函数

the handle must have GENERIC_WRITE access to the named pipe for a write-only or read/write pipe, or it must have GENERIC_READ and FILE_WRITE_ATTRIBUTES access for a read-only pipe.

问题就出在这里。

如果我们查看采用 PipeDirection 设置的 NamedPipeClientStream 的构造函数,我们会发现它们只请求 GENERIC_READ 访问 PipeDirection.InGENERIC_WRITE 访问对于 PipeDirection.Out(或对于 InOut)。这意味着在 OutInOut 模式下打开的任何管道都可以工作,因为 GENERIC_WRITE 访问权限足以满足这些情况,但我们需要 GENERIC_READFILE_WRITE_ATTRIBUTES对于只读管道,NamedPipeClientStream class 从不请求。这是 class 中的一个缺陷,应由 Microsoft 更正。

我已在此处提交有关 Microsoft Connect 的错误报告:

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

如果您自己遇到这个问题,请给它投票,它可能有助于加快修复速度。


在修复(none 截至 3/2017)之前,可以通过为 NamedPipeClientStream 使用不同的构造函数来完全解决此问题。

构造函数有一个重载,而不是 PipeDirection 枚举,而是 PipeAccessRights 枚举,您可以在其中指定要为处理。然后构造函数从指定访问权限的组合中导出管道的方向(In 如果指定 ReadDataOut" 如果指定 WriteDataInOut 如果它们都指定了)。

这意味着,您无需使管道成为全双工即可解决此问题,只需像这样更改构造函数行即可:

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

如果您使用此备用构造函数作为建议的解决方法,结果将与您从构造函数的第一种形式获得的结果相同且无法区分,但将获得此附加访问权限,因此可以启用消息模式。