C# 中的“PROCESSENTRY32W”?

`PROCESSENTRY32W` in C#?

我这样声明函数 Process32FirstW 和结构 PROCESSENTRY32W

[DllImport("KERNEL32.DLL", CallingConvention = CallingConvention.StdCall, EntryPoint = "Process32FirstW")]
private static extern bool Process32FirstW (IntPtr hSnapshot, ref ProcessEntry pProcessEntry);

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Size = 568)]
internal struct ProcessEntry {
    [FieldOffset(0)] public int Size;
    [FieldOffset(8)] public int ProcessId;
    [FieldOffset(32)] public int ParentProcessID;
    [FieldOffset(44), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string ExeFile;
}

当调用 Process32FirstW(使用 64 位进程)时,我总是得到 TypeLoadException

The type ProcessEntry couldn't be loaded, because the object field at offset 44 is aligned wrong or is overlapped by another field, which isn't an object field.

我还尝试使用 char[] 而不是 string 作为 ProcessEntry.ExeFile 并在结构的 StructLayoutAttribute 中使用 Pack=4Pack=8。我总是将 ProcessEntry.Size 设置为 568 并且我从 C++ 程序(64 位构建)复制了偏移数据:

typedef unsigned long long ulong;
PROCESSENTRY32W entry;

wcout << sizeof(PROCESSENTRY32W) << endl;                           // 568
wcout << (ulong)&entry.dwSize - (ulong)&entry << endl;              // 0
wcout << (ulong)&entry.th32ProcessID - (ulong)&entry << endl;       // 8
wcout << (ulong)&entry.th32ParentProcessID - (ulong)&entry << endl; // 32
wcout << (ulong)&entry.szExeFile - (ulong)&entry << endl;           // 44

我不知道出了什么问题,所以 如何在 C# 中为 64 位应用程序声明 PROCESSENTRY32W 我必须使用 C++/CLI 还是我只是在这里做错了什么?


编辑: 运行 这段代码作为 64 位程序对我来说工作得很好

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

PROCESSENTRY32W entry;
entry.dwSize = sizeof(PROCESSENTRY32W);

if (Process32FirstW(hSnapshot, &entry)) {
    do {
        // Do stuff
    } while (Process32NextW(hSnapshot, &entry));
}

CloseHandle(hSnapshot);

尝试设置对齐方式 Pack=8 and Charset.Unicode

开始字段 szExeFile 是 40,而不是 44。
查看每个成员的个人尺寸。

PROCESSENTRY32 完全定义为

typedef struct tagPROCESSENTRY32 {
  DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID;
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads;
  DWORD     th32ParentProcessID;
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  TCHAR     szExeFile[MAX_PATH];
} PROCESSENTRY32, *PPROCESSENTRY32;

你忽略了ULONG_PTR th32DefaultHeapID;,那个成员在32位系统上是4字节,在64位系统上是8字节,也就是说你的FieldOffsetAttribute对于ParentProcessIDExeFile将有不同的偏移量,具体取决于您是 运行 32 位还是 64 位。看看你的数学,你似乎假设它总是 8 个字节。

最简单的解决方法是不要明确定义偏移量并使用 IntPtr 动态计算正确的偏移量。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct PROCESSENTRY32 
{ 
   public uint dwSize; 
   public uint cntUsage; 
   public uint th32ProcessID; 
   public IntPtr th32DefaultHeapID; 
   public uint th32ModuleID; 
   public uint cntThreads; 
   public uint th32ParentProcessID; 
   public int pcPriClassBase; 
   public uint dwFlags; 
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst=260)] public string szExeFile; 
 }; 

是的,这行不通。当您使用 LayoutKind.Explicit 时,managed 结构的结构布局将是您指定的布局。以及结构的非托管版本。然而,在这种特定情况下,这违反了 .NET 内存模型。这表明对象引用,如 ProcessEntry.ExeFile,总是 atomic.

只有变量正确对齐才能实现原子性。所以它可以用一个内存总线周期更新。在 64 位模式下,这需要将对象引用对齐到 8,因为对象引用是 8 字节指针。问题是,偏移量 44 仅与 4 对齐,而不是 8。

在非托管版本的结构中完全没有问题,ExeFile 成员实际上是一个 WCHAR[] 数组。这只需要与 2 对齐,因此无需填充以获取 48 处的成员。

您必须放弃 LayoutKind.Explicit,改用 LayoutKind.Sequential。简单易行,还可以让您感觉良好,因为您的代码在 32 位模式下仍能正常工作。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct ProcessEntry {
    public int Size;
    public int Usage;
    public int ProcessId;
    public IntPtr DefaultHeapId;
    public int ModuleId;
    public int Threads;
    public int ParentProcessID;
    public int Priority;
    public int Flags;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string ExeFile;
}

而且一张支票永远不会伤害:

    System.Diagnostics.Debug.Assert(IntPtr.Size == 8 &&
        Marshal.OffsetOf(typeof(ProcessEntry), "ExeFile") == (IntPtr)44);