6

我声明了这样的函数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

ProcessEntry无法加载该类型,因为偏移量 44 处的对象字段对齐错误或与另一个字段重叠,该字段不是对象字段。

我还尝试在char[]结构string的. 我总是设置为 568,并从 C++ 程序(64 位构建)中复制了偏移数据:ProcessEntry.ExeFilePack=4Pack=8StructLayoutAttributeProcessEntry.Size

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

我不知道出了什么问题,那么如何 PROCESSENTRY32W 在 C# 中为 64 位应用程序声明?我必须使用 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);
4

3 回答 3

5

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 个字节,这意味着您的FieldOffsetAttributeforParentProcessID并且ExeFile将具有不同的偏移量,具体取决于您运行的是 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; 
 }; 
于 2015-11-10T15:07:51.677 回答
4

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

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

在结构的非托管版本中根本不是问题,ExeFile 成员实际上是一个 WCHAR[] 数组。这只需要对齐到 2,因此无需填充以使成员位于 48。

您必须放弃 LayoutKind.Explicit 并LayoutKind.Sequential改用它。Easy peasy,也可以让您感觉良好,您的代码在 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);
于 2015-11-10T15:19:35.453 回答
0

尝试设置对齐Pack=8 和 Charset.Unicode

开始字段 szExeFile 是 40,而不是 44。
查看每个成员的单独大小。

于 2015-11-10T14:59:26.783 回答