如何处理 pinvoke 结构中的 "no set size" 数组?

How to deal with "no set size" arrays in pinvoke structs?

你会如何在 C# 中编写这种类型的结构?

struct _JOBOBJECT_BASIC_PROCESS_ID_LIST {
  DWORD     NumberOfAssignedProcesses;
  DWORD     NumberOfProcessIdsInList;
  ULONG_PTR ProcessIdList[1];
}

sins ProcessIdList 数组没有设置大小,你会怎么做?是不是就这么写:

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_PROCESS_ID_LIST
{
   int NumberOfAssignedProcesses;
   int NumberOfProcessIdsInList;
   IntPtr ProcessIdList; //Must point to a allocated array, thanks jdweng for letting me know.
}

或者您只是分配一个足够大的尺寸,例如:

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_PROCESS_ID_LIST
{
   int NumberOfAssignedProcesses;
   int NumberOfProcessIdsInList;
   [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_PATH)]
   UIntPtr[] ProcessIdList; //Works just fine, but is limited to the SizeConst.
}

我认为你提到的任何一种方法都应该有效。

另外,c#中有一个匹配的特性:使用fixed关键字定义一个数组:

struct JOBOBJECT_BASIC_PROCESS_ID_LIST
{
   int NumberOfAssignedProcesses;
   int NumberOfProcessIdsInList;
   fixed IntPtr ProcessIdList[1];
}

查看文档: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/unsafe-code-pointers/fixed-size-buffers

也没有边界检查,因此您应该能够轻松阅读 struckt 末尾后面的内容:

Note

Except for memory created by using stackalloc, the C# compiler and the common language runtime (CLR) do not perform any security buffer overrun checks. As with all unsafe code, use caution.

通常会声明这种结构(例如在 WLan API 中还有其他类似的结构):

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct JOBOBJECT_BASIC_PROCESS_ID_LIST
    {
        public int NumberOfAssignedProcesses;
        public int NumberOfProcessIdsInList;
        public IntPtr[] ProcessIdList;
        public JOBOBJECT_BASIC_PROCESS_ID_LIST(IntPtr pList)
        {
            int nIntSize = Marshal.SizeOf<int>(); // 4
            NumberOfAssignedProcesses = Marshal.ReadInt32(pList, 0);
            NumberOfProcessIdsInList = Marshal.ReadInt32(pList, nIntSize);
            ProcessIdList = new IntPtr[NumberOfProcessIdsInList];
            for (int i = 0; i < NumberOfProcessIdsInList; i++)
            {
                IntPtr pItemList = IntPtr.Zero;
                if (Marshal.SizeOf<IntPtr>() == 4)
                    pItemList = new IntPtr(pList.ToInt32() + (i * Marshal.SizeOf<IntPtr>()) + (nIntSize * 2));
                else
                    pItemList = new IntPtr(pList.ToInt64() + (i * Marshal.SizeOf<IntPtr>()) + (nIntSize * 2));
                IntPtr nPID = new IntPtr();
                nPID = Marshal.ReadIntPtr(pItemList, 0);
                ProcessIdList[i] = nPID;
            }
        }
    }

使用 5 个记事本启动并分配给 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 的作业的测试, 然后 QueryInformationJobObject 使用此结构枚举 PID =>

        private IntPtr hJob = IntPtr.Zero;

        bool bRet = false;
        hJob  = CreateJobObject(IntPtr.Zero, "Test Job Object");
        JOBOBJECT_EXTENDED_LIMIT_INFORMATION jbeli = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        jbeli.BasicLimitInformation.LimitFlags |= (JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_BREAKAWAY_OK);
        int nLength = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr pJobInfo = Marshal.AllocHGlobal(nLength);
        Marshal.StructureToPtr(jbeli, pJobInfo, false);           
        SetInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, pJobInfo, (uint)nLength);
        Marshal.FreeHGlobal(pJobInfo);

        int nNbProcesses = 5;
        for (int i = 0; i < nNbProcesses; i++)
        {
            using (Process exeProcess = new Process())
            {
                exeProcess.StartInfo.FileName = "notepad";
                exeProcess.Start();
                exeProcess.WaitForInputIdle();
                IntPtr hProcess = exeProcess.Handle;
                bRet = AssignProcessToJobObject(hJob, hProcess);
            }
        }

        JOBOBJECT_BASIC_PROCESS_ID_LIST jobpil = new JOBOBJECT_BASIC_PROCESS_ID_LIST();
        jobpil.NumberOfAssignedProcesses = nNbProcesses;
        int nSize = Marshal.SizeOf<JOBOBJECT_BASIC_PROCESS_ID_LIST>() + (nNbProcesses - 1) * Marshal.SizeOf<IntPtr>();
        IntPtr pJobpil = Marshal.AllocHGlobal(nSize);
        Marshal.StructureToPtr(jobpil, pJobpil, false);
        int nReturnLength = 0;
        bRet = QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectBasicProcessIdList,  pJobpil, nSize, out nReturnLength);
        if (bRet)
        {
            var processidlist = new JOBOBJECT_BASIC_PROCESS_ID_LIST(pJobpil);
            foreach (var pid in processidlist.ProcessIdList)
            {
                Console.WriteLine("PID: {0}", pid.ToString());
            }
        }
        else
        {
            int nErr = Marshal.GetLastWin32Error();
            Win32Exception win32Exception = new Win32Exception(nErr);
            this.Activate();
            MessageBox.Show("Error: " + win32Exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        Marshal.FreeHGlobal(pJobpil);


    // CloseHandle can be added in Form1_FormClosed :

    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        CloseHandle(hJob);
    }

声明=>

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName);

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool SetInformationJobObject(IntPtr hJob, JOBOBJECTINFOCLASS JobObjectInfoClass, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool CloseHandle(IntPtr hObject);

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool QueryInformationJobObject(IntPtr hJob, JOBOBJECTINFOCLASS JobObjectInformationClass, [Out, MarshalAs(UnmanagedType.SysUInt)] IntPtr lpJobObjectInformation, int cbJobObjectInformationLength, out int lpReturnLength);

        [StructLayout(LayoutKind.Sequential)]
        struct JOBOBJECT_BASIC_LIMIT_INFORMATION
        {
            public ulong PerProcessUserTimeLimit;
            public ulong PerJobUserTimeLimit;
            public int LimitFlags;
            public IntPtr MinimumWorkingSetSize;
            public IntPtr MaximumWorkingSetSize;
            public int ActiveProcessLimit;
            public IntPtr Affinity;
            public int PriorityClass;
            public int SchedulingClass;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct IO_COUNTERS
        {
            public ulong ReadOperationCount;
            public ulong WriteOperationCount;
            public ulong OtherOperationCount;
            public ulong ReadTransferCount;
            public ulong WriteTransferCount;
            public ulong OtherTransferCount;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
        {
            public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
            public IO_COUNTERS IoInfo;
            public IntPtr ProcessMemoryLimit;
            public IntPtr JobMemoryLimit;
            public IntPtr PeakProcessMemoryUsed;
            public IntPtr PeakJobMemoryUsed;
        }

        //
        // Basic Limits
        //
        public const int JOB_OBJECT_LIMIT_WORKINGSET = 0x00000001;
        public const int JOB_OBJECT_LIMIT_PROCESS_TIME = 0x00000002;
        public const int JOB_OBJECT_LIMIT_JOB_TIME = 0x00000004;
        public const int JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008;
        public const int JOB_OBJECT_LIMIT_AFFINITY = 0x00000010;
        public const int JOB_OBJECT_LIMIT_PRIORITY_CLASS = 0x00000020;
        public const int JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME = 0x00000040;
        public const int JOB_OBJECT_LIMIT_SCHEDULING_CLASS = 0x00000080;

        //
        // Extended Limits
        //
        public const int JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x00000100;
        public const int JOB_OBJECT_LIMIT_JOB_MEMORY = 0x00000200;
        public const int JOB_OBJECT_LIMIT_JOB_MEMORY_HIGH = JOB_OBJECT_LIMIT_JOB_MEMORY;
        public const int JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x00000400;
        public const int JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800;
        public const int JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000;
        public const int JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
        public const int JOB_OBJECT_LIMIT_SUBSET_AFFINITY = 0x00004000;
        public const int JOB_OBJECT_LIMIT_JOB_MEMORY_LOW = 0x00008000;

        public enum JOBOBJECTINFOCLASS
        {
            JobObjectBasicAccountingInformation = 1,
            JobObjectBasicLimitInformation,
            JobObjectBasicProcessIdList,
            JobObjectBasicUIRestrictions,
            JobObjectSecurityLimitInformation,  // deprecated
            JobObjectEndOfJobTimeInformation,
            JobObjectAssociateCompletionPortInformation,
            JobObjectBasicAndIoAccountingInformation,
            JobObjectExtendedLimitInformation,
            JobObjectJobSetInformation,
            JobObjectGroupInformation,
            JobObjectNotificationLimitInformation,
            JobObjectLimitViolationInformation,
            JobObjectGroupInformationEx,
            JobObjectCpuRateControlInformation,
            JobObjectCompletionFilter,
            JobObjectCompletionCounter,
            JobObjectReserved1Information = 18,
            JobObjectReserved2Information,
            JobObjectReserved3Information,
            JobObjectReserved4Information,
            JobObjectReserved5Information,
            JobObjectReserved6Information,
            JobObjectReserved7Information,
            JobObjectReserved8Information,
            JobObjectReserved9Information,
            JobObjectReserved10Information,
            JobObjectReserved11Information,
            JobObjectReserved12Information,
            JobObjectReserved13Information,
            JobObjectReserved14Information = 31,
            JobObjectNetRateControlInformation,
            JobObjectNotificationLimitInformation2,
            JobObjectLimitViolationInformation2,
            JobObjectCreateSilo,
            JobObjectSiloBasicInformation,
            JobObjectReserved15Information = 37,
            JobObjectReserved16Information,
            JobObjectReserved17Information,
            JobObjectReserved18Information,
            JobObjectReserved19Information = 41,
            JobObjectReserved20Information,
            MaxJobObjectInfoClass
        }