1

I'm trying to PInvoke UpdateProcThreadAttribute() with PROC_THREAD_ATTRIBUTE_PREFERRED_NODE attribute, so that I could launch a process on a specific NUMA node. I'm working on Windows Server 2019.

I found this question that helped me to PInvoke UpdateProcThreadAttribute(): How to call CreateProcess() with STARTUPINFOEX from C# and re-parent the child

When I'm using PROC_THREAD_ATTRIBUTE_PARENT_PROCESS everything works fine, but when trying to set PROC_THREAD_ATTRIBUTE_PREFERRED_NODE as an attribute I keep getting error 24: ERROR_BAD_LENGTH (The program issued a command but the command length is incorrect).

public class ProcessCreator
{
    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CreateProcess(
        string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
        ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
        IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UpdateProcThreadAttribute(
        IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue,
        IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool InitializeProcThreadAttributeList(
        IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CloseHandle(IntPtr hObject);

    [DllImport("kernel32.dll")]
    static extern uint GetLastError();

    public static bool CreateProcess(string appName, string args, string dir, out PROCESS_INFORMATION pInfo, ILogger logger)
    {
        const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
        const int PROC_THREAD_ATTRIBUTE_PREFERRED_NODE = 0x00020004;

        pInfo = new PROCESS_INFORMATION();
        var sInfoEx = new STARTUPINFOEX();
        sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx);
        IntPtr lpValue = IntPtr.Zero;

        try
        {
            var lpSize = IntPtr.Zero;
            var success = InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize);
            if (success || lpSize == IntPtr.Zero)
            {
                return false;
            }

            sInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize);
            success = InitializeProcThreadAttributeList(sInfoEx.lpAttributeList, 1, 0, ref lpSize);
            if (!success)
            {
                return false;
            }

            // This value should persist until the attribute list is destroyed using the DeleteProcThreadAttributeList function
            lpValue = Marshal.AllocHGlobal(IntPtr.Size);

            // Setting preferred NUMA to be 0, for example.
            Marshal.WriteIntPtr(lpValue, (IntPtr)0);


            success = UpdateProcThreadAttribute(
                sInfoEx.lpAttributeList,
                0,
                (IntPtr)PROC_THREAD_ATTRIBUTE_PREFERRED_NODE,
                lpValue,
                (IntPtr)IntPtr.Size,
                IntPtr.Zero,
                IntPtr.Zero);
            if (!success)
            {
                logger.Info($"Error: {GetLastError()}");
                return false;
            }

            var pSec = new SECURITY_ATTRIBUTES();
            var tSec = new SECURITY_ATTRIBUTES();
            pSec.nLength = Marshal.SizeOf(pSec);
            tSec.nLength = Marshal.SizeOf(tSec);
            return CreateProcess(appName, args, ref pSec, ref tSec, false, 0, IntPtr.Zero, dir, ref sInfoEx, out pInfo);
        }
        finally
        {
            // Free the attribute list
            if (sInfoEx.lpAttributeList != IntPtr.Zero)
            {
                DeleteProcThreadAttributeList(sInfoEx.lpAttributeList);
                Marshal.FreeHGlobal(sInfoEx.lpAttributeList);
            }
            Marshal.FreeHGlobal(lpValue);

            // Close process and thread handles
            if (pInfo.hProcess != IntPtr.Zero)
            {
                CloseHandle(pInfo.hProcess);
            }
            if (pInfo.hThread != IntPtr.Zero)
            {
                CloseHandle(pInfo.hThread);
            }
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct STARTUPINFOEX
    {
        public STARTUPINFO StartupInfo;
        public IntPtr lpAttributeList;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct STARTUPINFO
    {
        public Int32 cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public Int32 dwX;
        public Int32 dwY;
        public Int32 dwXSize;
        public Int32 dwYSize;
        public Int32 dwXCountChars;
        public Int32 dwYCountChars;
        public Int32 dwFillAttribute;
        public Int32 dwFlags;
        public Int16 wShowWindow;
        public Int16 cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SECURITY_ATTRIBUTES
    {
        public int nLength;
        public IntPtr lpSecurityDescriptor;
        public int bInheritHandle;
    }
}

Any help would be much appreciated.

Hussien Mostafa
  • 159
  • 2
  • 18
Dan Sagher
  • 21
  • 1
  • As per documentation, `lpValue` is a **pointer**, and you just pass `0` there. So, to pass 0 as NUMA node number, you need to allocate memory with `Marshal.AllocHGlobal` then write `0` to this memory with `Marshal.WriteIntPtr`, `Marshal.WriteInt16` or similar. – Serg Jan 31 '22 at 11:30
  • 1
    johnnycrash has [figured it out](https://stackoverflow.com/questions/18647965/createprocess-problems-when-using-proc-thread-attribute-preferred-node-or-proc-t), the size needs to be 2. `ushort` in C#. – Hans Passant Jan 31 '22 at 13:33

0 Answers0