C++ 64位Windows API结构对齐在命名管道上导致拒绝访问错误

C++ 64位Windows API结构对齐在命名管道上导致拒绝访问错误,c++,winapi,struct,C++,Winapi,Struct,这花了我两天的时间,但我终于缩小了错误\u ACCESS\u DENIED(5)尝试调用NamedPipe时出现的错误的来源,将其视为结构对齐问题。我们有一个32位服务和一个32位应用程序,我正在尝试将该服务更新为64位服务。奇怪的是,在32位模式下一切正常,但在64位模式下,来自32位应用程序的CallNamedPipe报告了一个拒绝访问错误 该服务已在设置安全属性结构,并使用正确初始化的安全描述符填充lpSecurityDescriptor成员。当传递到CreateNamedPipe时,没有

这花了我两天的时间,但我终于缩小了
错误\u ACCESS\u DENIED
(5)尝试
调用NamedPipe
时出现的错误的来源,将其视为结构对齐问题。我们有一个32位服务和一个32位应用程序,我正在尝试将该服务更新为64位服务。奇怪的是,在32位模式下一切正常,但在64位模式下,来自32位应用程序的CallNamedPipe报告了一个拒绝访问错误

该服务已在设置
安全属性
结构,并使用正确初始化的
安全描述符
填充
lpSecurityDescriptor
成员。当传递到
CreateNamedPipe
时,没有报告任何错误。我仍然不知道为什么它没有报告错误;可能坏的安全属性会自动返回默认值,而不是失败

通过多次反复(包括之前一些不完整/不正确的更改结构对齐方式的尝试-调试服务启动代码并不容易),我意识到将默认结构对齐方式设置为1字节(/Zp1)的项目设置导致了问题。当我最终在所有出现的
#include
#pragma pack(pop)
之前使用
#pragma pack(push,8)
时,事情开始起作用

我现在的问题是为什么这是必要的。我看到Windows API中有许多头文件通过包括
pshpack1.h
pshpack2.h
pshpack4.h
pshpack8.h
poppack.h
来显式设置结构对齐。我如何知道Windows API何时控制自己的打包,以及何时我有责任设置正确的打包级别?不是每个关心结构对齐的Windows API声明都应该设置适当的打包,这样我就不必在系统中筛选所有代码,包括Windows API头文件吗?另一种选择是将项目设置更改为使用默认的结构对齐方式,但我必须假设这样做是因为我们的系统中依赖1字节结构对齐的代码比依赖Windows API的代码还要多

这是服务器端代码的外观:

BOOL OpenMyPipe()
{
   SECURITY_ATTRIBUTES sa;
   PSECURITY_DESCRIPTOR pSD;

   printf("sizeof(SECURITY_ATTRIBUTES) == %d\n", sizeof(SECURITY_ATTRIBUTES));
   pSD = (PSECURITY_DESCRIPTOR)GlobalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
   if (pSD == NULL)
      return FALSE;

   if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION))
      return FALSE;

   if (!SetSecurityDescriptorDacl(pSD, TRUE, (PACL)NULL, FALSE))
      return FALSE;

   sa.nLength = sizeof(sa);
   sa.lpSecurityDescriptor = pSD;
   sa.bInheritHandle = FALSE;

   char szPipeName[MAX_PATH];
   sprintf(szPipeName, "\\\\.\\pipe\\%s%s", "__SQLTST_",
      "MAINMR");

   hPipe = CreateNamedPipe(szPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
      PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
      1, 0, 0, NMPWAIT_WAIT_FOREVER, &sa);

   if (hPipe == INVALID_HANDLE_VALUE)
      return FALSE;
   return TRUE;
}
为了简单起见,我用一个小型VB.NET客户端验证了这一点:

Sub Main()
  Dim pipes = System.IO.Directory.GetFiles("\\.\pipe\")

  Using pipe As New System.IO.Pipes.NamedPipeClientStream(".", "__SQLTST_MAINMR")
     Dim message(16) As Byte
     pipe.Connect(3000)
     Array.Copy(BitConverter.GetBytes(Process.GetCurrentProcess().Id), message, 4)
     pipe.Write(message, 0, 16)
  End Using
End Sub

我相信只有当服务器端代码在不同的帐户(如系统帐户)下运行时,才会发生错误。不过,我不知道如何轻松地测试它。我所知道的是,当上述代码与常规应用程序代码在同一帐户下运行时,即使没有设置
SECURITY\u属性
,它们也不会失败。当然,您还必须将服务器代码中的结构对齐设置为1字节才能看到错误。

Windows SDK要求打包为8字节。从

编译项目时应使用默认的结构打包,当前为8字节,因为最大的整数类型为8字节。这样做可以确保头文件中的所有结构类型都以Windows API所期望的对齐方式编译到应用程序中。它还确保具有8字节值的结构正确对齐,并且不会在执行数据对齐的处理器上导致对齐错误

这对于确保数据结构按照系统预期对齐是必要的。我怀疑没有明确这么做的原因是他们想要默认值,所以。更换包装相对较少,且应限于特定情况。如果Microsoft在每个头文件中添加了
#pragma pack(push,8)
,他们会含蓄地说更改对齐方式是正常的

未对齐的结构节省了空间,但降低了访问未对齐成员时产生的性能


Windows SDK确实会出于多种原因更改结构的对齐方式。一种可能是需要读取32位或64位数据结构的文件格式。例如,可以使用
IMAGE\u THUNK\u DATA64
IMAGE\u THUNK\u DATA32
读取PE文件格式。前者需要8字节的填充,而后者需要4字节的填充。类似地,
Wininet.h
将根据数据结构是为32位代码还是64位代码编译而不同地打包数据结构。这些都是包装方面的合理更改,但有一个特定的原因。

我的问题的一部分是这是否是Win API中的一个错误,或者我是否遗漏了一些可能会影响整个系统中数百个API调用的巨大假设。也许问题并不是你所想的那样。我真想知道你为什么要打包结构。你为什么要错误地对齐你的数据?我不知道。我从一个200多万行的系统中继承了这段代码。我只能假设,这是因为我们有许多存储在15多年前的文件中的结构需要像很久以前那样对齐。如果我知道如何在系统帐户下轻松运行服务器代码,我可以合理地确定上面的错误可能会重现。这让我想知道为什么在Windows 8.1 SDK include files目录中包含pshpack8.h的情况会超过80次。我已经添加了一点关于在头文件。有许多合法用途。
/Zp
标志用于将打包设置为您想要的任何内容。并非每个程序都必须使用Windows SDK。