public static Win32HardwareBreakpoint TrySet(SafeObjectHandle threadHandle,
                                                     HardwareBreakpointType type, HardwareBreakpointSize size, IntPtr address)
        {
            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IntPtr.Size != 8)
            {
                throw new PlatformNotSupportedException("Only 64 bit Windows is supported");
            }

            var breakpoint = new Win32HardwareBreakpoint {
                Address      = address,
                Size         = size,
                Type         = type,
                ThreadHandle = threadHandle
            };

            bool self = threadHandle.DangerousGetHandle() == Win32Thread.GetCurrentThread().DangerousGetHandle();

            if (self)
            {
                int threadID = GetCurrentThreadId();
                breakpoint.ThreadHandle = Win32Thread.OpenThread(ThreadAccess.All, inheritHandle: false, threadID: threadID);
                if (breakpoint.ThreadHandle.IsInvalid)
                {
                    throw new Win32Exception();
                }
            }

            breakpoint.CompletionEvent = Win32Event.CreateEvent(IntPtr.Zero, manualReset: false, initialState: false, name: null);
            breakpoint.Op = Operation.Set;

            Win32Thread.ThreadProc threadProc = Win32HardwareBreakpoint.th;
            var th = Marshal.GetFunctionPointerForDelegate(threadProc);
            var h  = GCHandle.Alloc(breakpoint);

            if (Win32Thread.CreateThread(IntPtr.Zero, UIntPtr.Zero, th,
                                         parameter: (IntPtr)h, creationFlags: 0, out int _).IsInvalid)
            {
                throw new Win32Exception();
            }

            WaitForSingleObject(breakpoint.CompletionEvent, dwMilliseconds: Constants.INFINITE);
            breakpoint.CompletionEvent.Dispose();

            if (self)
            {
                breakpoint.ThreadHandle.Dispose();
            }

            breakpoint.ThreadHandle = threadHandle;
            if (!breakpoint.SUCC)
            {
                h.Free();
                return(null);
            }

            breakpoint.gcHandle = h;

            GC.KeepAlive(threadProc);
            return(breakpoint);
        }
        static void Remove(GCHandle handle)
        {
            if (!handle.IsAllocated)
            {
                throw new ArgumentNullException(nameof(handle));
            }

            var  breakpoint = (Win32HardwareBreakpoint)handle.Target;
            bool isSelf     = false;

            if (breakpoint.ThreadHandle.DangerousGetHandle() == Win32Thread.GetCurrentThread().DangerousGetHandle())
            {
                int threadID = GetCurrentThreadId();
                breakpoint.ThreadHandle = Win32Thread.OpenThread(ThreadAccess.All, inheritHandle: false, threadID);
                if (breakpoint.ThreadHandle.IsInvalid)
                {
                    throw new Win32Exception();
                }
                isSelf = true;
            }

            breakpoint.CompletionEvent = Win32Event.CreateEvent(IntPtr.Zero, manualReset: false, initialState: false, name: null);
            breakpoint.Op = Operation.Remove;

            Win32Thread.ThreadProc threadProc = Win32HardwareBreakpoint.th;
            var th = Marshal.GetFunctionPointerForDelegate(threadProc);

            if (Win32Thread.CreateThread(IntPtr.Zero, UIntPtr.Zero, th, (IntPtr)handle, 0, out int _).IsInvalid)
            {
                throw new Win32Exception();
            }
            WaitForSingleObject(breakpoint.CompletionEvent, Constants.INFINITE);
            breakpoint.CompletionEvent.Dispose();
            if (isSelf)
            {
                breakpoint.ThreadHandle.Dispose();
            }

            handle.Free();
        }
        public IDisposable Set(int threadID, IntPtr address, UIntPtr size, DataBreakpointTrigger trigger)
        {
            if (address == IntPtr.Zero)
            {
                throw new ArgumentNullException(nameof(address));
            }
            if (size == UIntPtr.Zero)
            {
                throw new ArgumentException("Region size must be non-zero", paramName: nameof(size));
            }
            var type = trigger switch {
                DataBreakpointTrigger.OnWrite => HardwareBreakpointType.Write,
                DataBreakpointTrigger.OnReadOrWrite => HardwareBreakpointType.ReadWrite,
                _ => throw new ArgumentException("Invalid value", paramName: nameof(trigger))
            };
            var sizeType = checked ((ulong)size) switch {
                1 => HardwareBreakpointSize.Size1,
                2 => HardwareBreakpointSize.Size2,
                4 => HardwareBreakpointSize.Size4,
                8 => HardwareBreakpointSize.Size8,
                _ => throw new NotSupportedException(
                          "Memory region size must be 1, 2, 4, or 8 bytes"),
            };
            var threadHandle = Win32Thread.OpenThread(ThreadAccess.All, inheritHandle: false, threadID);

            if (threadHandle.IsInvalid)
            {
                throw new Win32Exception();
            }
            var breakpoint = Win32HardwareBreakpoint.TrySet(threadHandle, type, sizeType, address);

            if (breakpoint == null)
            {
                throw new InvalidOperationException("Data breakpoint limit reached");
            }
            return(breakpoint);
        }