Working example of CreateJobObject/SetInformationJobObject pinvoke in .net?

22,439

Solution 1

This can be little bit late, but still.

I tried all of the examples here, but no one was working for me in 32 and 64 bit mode simultaneously. Finally, I was required to examine all the signatures myself and create corresponding PInvoke routines. I think, somebody else could find this helpful.

Disclaimer: the solution is based on Matt Howells' answer.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace JobManagement
{
    public class Job : IDisposable
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        static extern IntPtr CreateJobObject(IntPtr a, string lpName);

        [DllImport("kernel32.dll")]
        static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

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

        private IntPtr handle;
        private bool disposed;

        public Job()
        {
            handle = CreateJobObject(IntPtr.Zero, null);

            var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
            {
                LimitFlags = 0x2000
            };

            var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
            {
                BasicLimitInformation = info
            };

            int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
            IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
            Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

            if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
                throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing) { }

            Close();
            disposed = true;
        }

        public void Close()
        {
            CloseHandle(handle);
            handle = IntPtr.Zero;
        }

        public bool AddProcess(IntPtr processHandle)
        {
            return AssignProcessToJobObject(handle, processHandle);
        }

        public bool AddProcess(int processId)
        {
            return AddProcess(Process.GetProcessById(processId).Handle);
        }

    }

    #region Helper classes

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


    [StructLayout(LayoutKind.Sequential)]
    struct JOBOBJECT_BASIC_LIMIT_INFORMATION
    {
        public Int64 PerProcessUserTimeLimit;
        public Int64 PerJobUserTimeLimit;
        public UInt32 LimitFlags;
        public UIntPtr MinimumWorkingSetSize;
        public UIntPtr MaximumWorkingSetSize;
        public UInt32 ActiveProcessLimit;
        public UIntPtr Affinity;
        public UInt32 PriorityClass;
        public UInt32 SchedulingClass;
    }

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

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

    public enum JobObjectInfoType
    {
        AssociateCompletionPortInformation = 7,
        BasicLimitInformation = 2,
        BasicUIRestrictions = 4,
        EndOfJobTimeInformation = 6,
        ExtendedLimitInformation = 9,
        SecurityLimitInformation = 5,
        GroupInformation = 11
    }

    #endregion

}

Solution 2

Improving on Alexander's answer, here's a version that uses a SafeHandle. This is a CriticalFinalizerObject that makes working with handles much safer. .NET APIs (such as the Process class) always use SafeHandles with P/Invoke instead of IntPtrs.

internal sealed class ChildProcessManager : IDisposable
{
    private SafeJobHandle _handle;
    private bool _disposed;

    public ChildProcessManager()
    {
        _handle = new SafeJobHandle(CreateJobObject(IntPtr.Zero, null));

        var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
        {
            LimitFlags = 0x2000
        };

        var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
        {
            BasicLimitInformation = info
        };

        var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        var extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
                (uint)length))
        {
            throw new InvalidOperationException($"Unable to set information.  Error: {Marshal.GetLastWin32Error()}");
        }
    }

    public void Dispose()
    {
        if (_disposed) return;

        _handle.Dispose();
        _handle = null;
        _disposed = true;
    }

    private void ValidateDisposed()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(ChildProcessManager));
    }

    public void AddProcess(SafeProcessHandle processHandle)
    {
        ValidateDisposed();
        if (!AssignProcessToJobObject(_handle, processHandle))
        {
            throw new InvalidOperationException("Unable to add the process");
        }
    }

    public void AddProcess(Process process)
    {
        AddProcess(process.SafeHandle);
    }

    public void AddProcess(int processId)
    {
        using (var process = Process.GetProcessById(processId))
        {
            AddProcess(process);
        }
    }

    #region Safe Handle

    // ReSharper disable once ClassNeverInstantiated.Local
    private sealed class SafeJobHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        public SafeJobHandle(IntPtr handle) : base(true)
        {
            SetHandle(handle);
        }

        protected override bool ReleaseHandle()
        {
            return CloseHandle(handle);
        }

        [DllImport("kernel32", SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        private static extern bool CloseHandle(IntPtr hObject);
    }

    #endregion

    #region Win32
    // ReSharper disable InconsistentNaming

    [DllImport("kernel32", CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateJobObject(IntPtr a, string lpName);

    [DllImport("kernel32")]
    private static extern bool SetInformationJobObject(SafeJobHandle hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32", SetLastError = true)]
    private static extern bool AssignProcessToJobObject(SafeJobHandle job, SafeProcessHandle process);

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

    [StructLayout(LayoutKind.Sequential)]
    internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
    {
        public long PerProcessUserTimeLimit;
        public long PerJobUserTimeLimit;
        public uint LimitFlags;
        public UIntPtr MinimumWorkingSetSize;
        public UIntPtr MaximumWorkingSetSize;
        public uint ActiveProcessLimit;
        public UIntPtr Affinity;
        public uint PriorityClass;
        public uint SchedulingClass;
    }

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

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

    public enum JobObjectInfoType
    {
        AssociateCompletionPortInformation = 7,
        BasicLimitInformation = 2,
        BasicUIRestrictions = 4,
        EndOfJobTimeInformation = 6,
        ExtendedLimitInformation = 9,
        SecurityLimitInformation = 5,
        GroupInformation = 11
    }

    // ReSharper restore InconsistentNaming
    #endregion
}

Solution 3

To summarize, the signatures posed by Alexander Yezutov work under both x86 and x64. Matt Howells signatures use a number of UInt32's when UIntPtr should be used instead. I used the following P/Invoke signature for the CloseHandle which seems to work fine:

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

The following must be added to the app.manifest as posted by Mas:

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
  <!-- A list of all Windows versions that this application is designed to work with. Windows will automatically select the most compatible environment.-->

    <!--The ID below indicates application support for Windows Vista -->
    <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>

    <!--The ID below indicates application support for Windows 7 -->
    <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>

</application>

and lastly, this won't work (at least under Win 7) when launched from Visual Studio. The parent process must be started from Windows Explorer.

Solution 4

There is a post that uses apis that you are trying to use. Perhaps you can get some insight from there.

Kill child process when parent process is killed

Share:
22,439
Suraj
Author by

Suraj

Hi! I'm Suraj and here is a little about me, on my blog That! But How?

Updated on October 21, 2020

Comments

  • Suraj
    Suraj over 3 years

    I'm struggling to put together a working example of pinvoke'ing CreateJobObject and SetInformationJobObject. Through various google searches (including Russian and Chinese posts!) I've cobbled together the following code. I think the definition of JOBOBJECT_BASIC_LIMIT_INFORMATION changes based on platform (32/64-bit). The CreateJobObject/AssignProcessToJobObject seems to work. SetInformationJobObject fails - either with error 24 or 87.

    Process myProcess // POPULATED SOMEWHERE ELSE
    
    // Create Job & assign this process and another process to the job
    IntPtr jobHandle = CreateJobObject( null , null );
    AssignProcessToJobObject( jobHandle , myProcess.Handle );
    AssignProcessToJobObject( jobHandle , Process.GetCurrentProcess().Handle );
    
    // Ensure that killing one process kills the others                
    JOBOBJECT_BASIC_LIMIT_INFORMATION limits = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
    limits.LimitFlags = (short)LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    IntPtr pointerToJobLimitInfo = Marshal.AllocHGlobal( Marshal.SizeOf( limits ) );
    Marshal.StructureToPtr( limits , pointerToJobLimitInfo , false );   
    SetInformationJobObject( job , JOBOBJECTINFOCLASS.JobObjectBasicLimitInformation , pionterToJobLimitInfo ,  ( uint )Marshal.SizeOf( limits ) )
    ...
    
    
            [DllImport( "kernel32.dll" , EntryPoint = "CreateJobObjectW" , CharSet = CharSet.Unicode )]
            public static extern IntPtr CreateJobObject( SecurityAttributes JobAttributes , string lpName );
    
            public class SecurityAttributes
            {
    
                public int nLength; //Useless field = 0
                public IntPtr pSecurityDescriptor; //хз))
                public bool bInheritHandle; //Возможность наследования
    
                public SecurityAttributes()
                {
                    this.bInheritHandle = true;
                    this.nLength = 0;
                    this.pSecurityDescriptor = IntPtr.Zero;
                }
            }
    
            [DllImport( "kernel32.dll" )]
            static extern bool SetInformationJobObject( IntPtr hJob , JOBOBJECTINFOCLASS JobObjectInfoClass , IntPtr lpJobObjectInfo , uint cbJobObjectInfoLength );
    
            public enum JOBOBJECTINFOCLASS
            {
                JobObjectAssociateCompletionPortInformation = 7 ,
                JobObjectBasicLimitInformation = 2 ,
                JobObjectBasicUIRestrictions = 4 ,
                JobObjectEndOfJobTimeInformation = 6 ,
                JobObjectExtendedLimitInformation = 9 ,
                JobObjectSecurityLimitInformation = 5
            }
    
    
            [StructLayout( LayoutKind.Sequential )]
            struct JOBOBJECT_BASIC_LIMIT_INFORMATION
            {
                public Int64 PerProcessUserTimeLimit;
                public Int64 PerJobUserTimeLimit;
                public Int16 LimitFlags;
                public UIntPtr MinimumWorkingSetSize;
                public UIntPtr MaximumWorkingSetSize;
                public Int16 ActiveProcessLimit;
                public Int64 Affinity;
                public Int16 PriorityClass;
                public Int16 SchedulingClass;
            }
    
            public enum LimitFlags
            {
                JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008 ,
                JOB_OBJECT_LIMIT_AFFINITY = 0x00000010 ,
                JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800 ,
                JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x00000400 ,
                JOB_OBJECT_LIMIT_JOB_MEMORY = 0x00000200 ,
                JOB_OBJECT_LIMIT_JOB_TIME = 0x00000004 ,
                JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000 ,
                JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME = 0x00000040 ,
                JOB_OBJECT_LIMIT_PRIORITY_CLASS = 0x00000020 ,
                JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x00000100 ,
                JOB_OBJECT_LIMIT_PROCESS_TIME = 0x00000002 ,
                JOB_OBJECT_LIMIT_SCHEDULING_CLASS = 0x00000080 ,
                JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000 ,
                JOB_OBJECT_LIMIT_WORKINGSET = 0x00000001
            }
    
    
            [DllImport( "kernel32.dll" )]
            [return: MarshalAs( UnmanagedType.Bool )]
            static extern bool AssignProcessToJobObject( IntPtr hJob , IntPtr hProcess );
    
            [StructLayout( LayoutKind.Sequential )]
            public struct SECURITY_ATTRIBUTES
            {
                public int nLength;
                public IntPtr lpSecurityDescriptor;
                public int bInheritHandle;
            }
    
  • user314104
    user314104 over 12 years
    Strange. I tried this, and I couldn't get it to work on 64-bit, just like all the other P/Invoke solutions for this.
  • Esoteric Screen Name
    Esoteric Screen Name over 11 years
    Win32.CloseHandle(handle); is imported from kernel32.dll, yes? Your code (unmodified aside from a conversion to VB.NET) wouldn't compile for me without the external reference, though I am using .NET 2.0. I notice both your and Matt's answers don't import it explicitly, and I'm wondering what I'm missing.
  • Alexander Yezutov
    Alexander Yezutov over 11 years
    Yes, it seems you are absolutely right. Win32 is a custom class, which is not here.
  • Mas
    Mas over 11 years
    I used this for Win32.CloseHandle. I still couldn't get this to work, and the trick was to add an app.manifest as described here. Just note that this won't work in the VS debugger. You have to launch the parent process on its own.
  • Suraj
    Suraj over 11 years
    Andrew - thanks for the contribution. I'm a bit removed from this problem so I can't validate, but it sounds like you know what you're talking about =)
  • Adam Plocher
    Adam Plocher about 11 years
    Alexander, you are a life saver! I used Matt's example and had some trouble (presumably because it's x64), your update did the trick! I'm using the JobObject to spawn a process from a Windows Service and it's working great.
  • Eleasar
    Eleasar almost 9 years
    Great tjamls Alexander and Mas! Adding instead of Win32.CloseHandle worked [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject); did the trick.
  • Keith
    Keith almost 9 years
    Doesn't Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false) leave the unmanaged memory allocated to the extendedInfoPtr? Is there a memory leak if DestroyStructure is not called?
  • Aussie Ash
    Aussie Ash over 8 years
    instead of using win32.Close handle use a pinvoke ` public void Close() { CloseHandle(handle); handle = IntPtr.Zero; } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject);`
  • usr
    usr about 8 years
    The DWORD mappings are wrong, should be uint and not short. Update: JOBOBJECT_BASIC_LIMIT_INFORMATION is broken in many places. Beware.
  • It's a trap
    It's a trap over 6 years
    @AdamPlocher how did you guys get this to work? The Win32 class is not present.
  • Adam Plocher
    Adam Plocher over 6 years
    @It'satrap adding a class called Win32 with the following should fix it: [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject); (note, that's 3 lines of code, line 1: attribute, line2: other attrib, line 3 static extern)
  • Adam Plocher
    Adam Plocher over 6 years
    @It'satrap also take a look at Andrew's answer below. I believe the manifest file is needed now-a-days. Plus he has my code in a more readable format (apparently the second attrib isn't needed)
  • It's a trap
    It's a trap over 6 years
    @AdamPlocher Is it possible to set limit falg to more than 1 value. I see that we can either kill all processes or limit the maximum Ram used or so on. How can we put more than 1 restriction on the job object
  • Psddp
    Psddp about 5 years
    Can this be used in .NET Core?
  • Arundale Ramanathan
    Arundale Ramanathan over 3 years
    Works like a charm. Thanks to you and Matt.
  • Arundale Ramanathan
    Arundale Ramanathan over 3 years
    This is giving compilation error at _handle.Dispose()
  • Guillermo Prandi
    Guillermo Prandi over 3 years
    @Keith I believe Marshal.AllocHGlobal() is the problem. I think Marshal.FreeHGlobal() should be called after using the structure.