Задача заключалась в получении списка процессов системы. Для этого в WinAPI существует TlHelp32.h, где определены функции Process32First и Process32Next, каковые в качестве параметра принимают структуру PROCESSENTRY32.
Сама эта структура, в общем-то, обычная, но есть внутри неё массив символов, хранящий название процесса. Длина массива составляет MAX_PATH.
Как известно, в .NET массивы - ссылочные типы, поэтому структуру просто так не объявить: вместо цепочки символов там будет указатель, что в данном случае не надо. Но эта проблема обходится при помощи соответствующего атрибута.
Итак, на C# усечённый тестовый вариант выглядит следующим образом:
class ClrImport
{
const int MaxPath = 260;
const int Th32CsSnapProcess = 0x00000002;
private struct ProcessEntry32
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public uint th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public uint pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = ClrImport.MaxPath)]
public string szExeFile;
public uint th32MemoryBase;
public uint th32AccessKey;
};
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
private static extern bool Process32First(IntPtr hSnapshot, ref ClrImport.ProcessEntry32 lppe);
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
private static extern IntPtr CreateToolhelp32Snapshot(UInt32 dwFlags, UInt32 th32ProcessID);
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
private static extern bool CloseHandle(IntPtr hObject);
public static void Test()
{
IntPtr snapshotHandle = ClrImport.CreateToolhelp32Snapshot(ClrImport.Th32CsSnapProcess, 0);
ClrImport.ProcessEntry32 processEntry = new ClrImport.ProcessEntry32();
processEntry.dwSize = Convert.ToUInt32(Marshal.SizeOf(typeof(ClrImport.ProcessEntry32)));
ClrImport.Process32First(snapshotHandle, ref processEntry);
ClrImport.CloseHandle(snapshotHandle);
}
}
Мучался я с ним полдня.
Феномен заключается в том, что значения полей структуры начинают "разъезжаться". Не помогает ни замена типов с uint на явный UInt32, ни явное указание кодировки символов, ни явный переход на Unicode, ни Explicit-декларация с явным указанием offset в байтах для каждого поля.
Заменив string на char[] чисто эмпирически установил, что начало строки с названием процесса "уезжает" вперёд на 8 символов (или 16 байт, несмотря на то, что данные поступают в ANSI-кодировке).
Феномен не зависит ни от версии .NET, ни от режима Debug/Release. Однако после переписывания этого кода один в один на C++.NET (с поправкой на синтаксис) феномен исчезает. Кроме того, он наблюдается в проектах VS2005 и VS2008, но отсутствует в проектах VS2010.
Как оказалось, причиной является конфигурация сборки: "Any CPU". Как только конфигурация устанавливается в "x86" - всё становится нормальным.
Однако другие структуры с полями простых типов (без массивов внутри) нормально работают и в "Any CPU".
Видимо, в Marshal какой-то баг, касающийся передачи именно внедрённых в структуры массивов.