6

I declared the function Process32FirstW and the structure PROCESSENTRY32W like this:

[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;
}

When calling Process32FirstW (with a 64-bit process), I always get a TypeLoadException saying

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.

I also tried using char[] instead of string for ProcessEntry.ExeFile and using Pack=4 and Pack=8 in the structure's StructLayoutAttribute. I always set ProcessEntry.Size to 568 and I copied the offset data from a C++ program (64-bit build):

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

I can't figure out, what is going wrong, so how to declare PROCESSENTRY32W in C# for a 64-bit application? Do I have to use C++/CLI or am I simply doing something wrong here?


EDIT: Running this code as a 64-bit program works perfectly fine for me

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);
Cubi73
  • 1,891
  • 3
  • 31
  • 52
  • Your struct does not match the MSDN's [PROCESSENTRY32](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684839(v=vs.85).aspx) anywhere close. – Scott Chamberlain Nov 10 '15 at 14:43
  • +scott-chamberlain Why? dwSize is 4 bytes in size, like my Size field. Both are located at offset 0. The PID is also 4 bytes like mine and at offset 8. The parent PID also is 4 bytes in size and seems to be at offset 32 according to the C++ sample. I used 260 wide characters at offset 44. Where does my struct differ from the one in the MSDN? – Cubi73 Nov 10 '15 at 14:46
  • You are ignoring the fact that `ULONG_PTR` is differently sized on 32 and 64 bit systems, wrote up a full answer. – Scott Chamberlain Nov 10 '15 at 15:08

3 Answers3

5

PROCESSENTRY32 is fully defined as

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;

You ignore ULONG_PTR th32DefaultHeapID;, that member is 4 bytes on 32 bit systems and 8 bytes on 64 bit systems, that means your FieldOffsetAttribute for ParentProcessID and ExeFile will have a different offset depending on if you are running 32 bits vs 64 bits. Looking at your math it appears you assumed it would always be 8 bytes.

The easiest workaround is don't explicitly define the offsets and use IntPtr to dynamicly figure the correct offset.

[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; 
 }; 
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
4

Yes, this cannot work. When you use LayoutKind.Explicit then the structure layout of the managed struct will be the one you specify. As well as the unmanaged version of the struct. In this specific case however that violates the .NET memory model. Which dictates that object references, like ProcessEntry.ExeFile, are always atomic.

Atomicity can only be achieved if the variable is properly aligned. So it can be updated with a single memory bus cycle. In 64-bit mode, that requires an object reference to be aligned to 8 since object references are 8-byte pointers. Problem is, an offset of 44 is aligned only to 4, not to 8.

Not a problem in the unmanaged version of the struct at all, the ExeFile member is actually a WCHAR[] array. Which only requires an alignment to 2 so no need to pad to get the member at 48.

You must give up on LayoutKind.Explicit and use LayoutKind.Sequential instead. Easy peasy, also can make you feel good that your code still works properly in 32-bit mode.

[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;
}

And a check never hurts:

    System.Diagnostics.Debug.Assert(IntPtr.Size == 8 &&
        Marshal.OffsetOf(typeof(ProcessEntry), "ExeFile") == (IntPtr)44);
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I read something about alignment, but I didn't know about atomicity. But when using sequential layout the ExeFile field also gets aligned to 4. Isn't this a violation too? – Cubi73 Nov 10 '15 at 15:23
  • 1
    Now the *managed* layout of the struct no longer matches the *unmanaged* layout. The managed struct has the member at 48, the unmanaged at 44. The struct is no longer *blittable*, but it wasn't in the first place anyway because of the string. – Hans Passant Nov 10 '15 at 15:25
0

Try setting alignment Pack=8 and Charset.Unicode.

Start field szExeFile is 40, not 44.
See individual size of each member.

lsalamon
  • 7,998
  • 6
  • 50
  • 63