Skip to content
This repository was archived by the owner on Jul 26, 2023. It is now read-only.

Commit 1be11c2

Browse files
Kernel32 Threading API (#463)
* Add `SafeThreadHandle` * Add `CreateProcessFlags.STACK_SIZE_PARAM_IS_A_RESERVATION` * Add `PInvoke.SIZE_T` * Add `CreateThread`, `CreateRemoteThread`, and `CreateRemoteThreadEx` * Add a test for `CreateThread` * Add tests for `CreateRemoteThread` and `CreateRemoteThreadEx` * Ensure SafeThreadHandle is release w/ 'using' * - Remove SafeThreadHandle - SafeObjectHandle has the same functionality. - Remove SIZE_T - use UIntPtr instead; C# 9 can leverage nuint. - Address review feedback - Replace IntPtr parameters with pointers. - Use FriendlyFlags.Optional where appropriate. - Update tests. * Add FriendlyFlags.NativeInt * Add CreateThreadFlags enum * Keep a reference to the ThreadProc delegate in a static field. Co-authored-by: Andrew Arnott <[email protected]>
1 parent 7029d7c commit 1be11c2

File tree

7 files changed

+359
-2
lines changed

7 files changed

+359
-2
lines changed

src/CodeGenerationAttributes/FriendlyFlags.cs

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ public enum FriendlyFlags
1616

1717
Optional = 0x8,
1818

19+
/// <summary>
20+
/// Represents a native integer whose size is platform specific, i.e., 32-bits on 32-bit h/w and OS, and
21+
/// 64-bits on 64-bit h/w and OS.
22+
/// </summary>
23+
/// <remarks>Intended for use on <see cref="IntPtr"/> or <see cref="UIntPtr"/></remarks>
24+
NativeInt = 0x16,
25+
1926
Bidirectional = In | Out,
2027
}
2128
}

src/Kernel32.Tests/storebanned/Kernel32Facts.cs

+119
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
public partial class Kernel32Facts
1717
{
18+
private static unsafe Kernel32.THREAD_START_ROUTINE threadProc = new THREAD_START_ROUTINE(CreateThread_Test_ThreadMain);
1819
private readonly Random random = new Random();
1920

2021
[Fact]
@@ -944,6 +945,124 @@ public void SetHandleInformation_DoesNotThrow()
944945
HandleFlags.HANDLE_FLAG_NONE));
945946
}
946947

948+
/// <summary>
949+
/// Basic validation for <see cref="Kernel32.CreateThread(SECURITY_ATTRIBUTES*, UIntPtr, THREAD_START_ROUTINE, void*, CreateThreadFlags, uint*)"/>
950+
///
951+
/// Creates a thread by supplying <see cref="CreateThread_Test_ThreadMain(void*)"/> as its ThreadProc/<see cref="Kernel32.THREAD_START_ROUTINE"/>.
952+
/// The ThreadProc updates a bool value (supplied by the thread that created it) from false -> true. This change
953+
/// is observed by the calling thread as proof of successful thread-creation.
954+
///
955+
/// Also validates that the (native) Thread-ID for the newly created Thread is different than the (native) Thread-ID
956+
/// of that of the calling thread.
957+
/// </summary>
958+
[Fact]
959+
public unsafe void CreateThread_Test()
960+
{
961+
var result = false;
962+
var dwNewThreadId = 0u;
963+
964+
using var hThread =
965+
Kernel32.CreateThread(
966+
null,
967+
UIntPtr.Zero,
968+
Kernel32Facts.threadProc,
969+
&result,
970+
Kernel32.CreateThreadFlags.None,
971+
&dwNewThreadId);
972+
Kernel32.WaitForSingleObject(hThread, -1);
973+
974+
Assert.True(result);
975+
Assert.NotEqual((uint)Kernel32.GetCurrentThreadId(), dwNewThreadId);
976+
}
977+
978+
/// <summary>
979+
/// Basic validation for <see cref="CreateRemoteThread(IntPtr, SECURITY_ATTRIBUTES*, UIntPtr, THREAD_START_ROUTINE, void*, CreateThreadFlags, uint*)"/>
980+
/// Note that this test DOES NOT create a true REMOTE thread in a foreign process; it just leverages this function to create a thread in the current (i.e, the test) procrss.
981+
/// Nevertheless, this approach provides modest confidence that the P/Invoke definition is well-formed.
982+
///
983+
/// Creates a thread by supplying <see cref="CreateThread_Test_ThreadMain(void*)"/> as its ThreadProc/<see cref="Kernel32.THREAD_START_ROUTINE"/>.
984+
/// The ThreadProc updates a bool value (supplied by the thread that created it) from false -> true. This change
985+
/// is observed by the calling thread as proof of successful thread-creation.
986+
///
987+
/// Also validates that the (native) Thread-ID for the newly created Thread is different than the (native) Thread-ID
988+
/// of that of the calling thread.
989+
/// </summary>
990+
[Fact]
991+
public unsafe void CreateRemoteThread_PseudoTest()
992+
{
993+
var result = false;
994+
var dwNewThreadId = 0u;
995+
996+
using var hProcess = Kernel32.GetCurrentProcess();
997+
using var hThread =
998+
Kernel32.CreateRemoteThread(
999+
hProcess.DangerousGetHandle(),
1000+
null,
1001+
UIntPtr.Zero,
1002+
Kernel32Facts.threadProc,
1003+
&result,
1004+
Kernel32.CreateThreadFlags.None,
1005+
&dwNewThreadId);
1006+
Kernel32.WaitForSingleObject(hThread, -1);
1007+
1008+
Assert.True(result);
1009+
Assert.NotEqual((uint)Kernel32.GetCurrentThreadId(), dwNewThreadId);
1010+
}
1011+
1012+
/// <summary>
1013+
/// Basic validation for <see cref="CreateRemoteThreadEx(IntPtr, SECURITY_ATTRIBUTES*, UIntPtr, THREAD_START_ROUTINE, void*, CreateThreadFlags, PROC_THREAD_ATTRIBUTE_LIST*, uint*)"/>
1014+
/// Note that this test DOES NOT create a true REMOTE thread in a foreign process; it just leverages this function to create a thread in the current (i.e, the test) procrss.
1015+
/// Nevertheless, this approach provides modest confidence that the P/Invoke definition is well-formed.
1016+
///
1017+
/// Creates a thread by supplying <see cref="CreateThread_Test_ThreadMain(void*)"/> as its ThreadProc/<see cref="Kernel32.THREAD_START_ROUTINE"/>.
1018+
/// The ThreadProc updates a bool value (supplied by the thread that created it) from false -> true. This change
1019+
/// is observed by the calling thread as proof of successful thread-creation.
1020+
///
1021+
/// Also validates that the (native) Thread-ID for the newly created Thread is different than the (native) Thread-ID
1022+
/// of that of the calling thread.
1023+
/// </summary>
1024+
[Fact]
1025+
public unsafe void CreateRemoteThreadEx_PseudoTest()
1026+
{
1027+
var result = false;
1028+
var dwNewThreadId = 0u;
1029+
1030+
using var hProcess = Kernel32.GetCurrentProcess();
1031+
using var hThread =
1032+
Kernel32.CreateRemoteThreadEx(
1033+
hProcess.DangerousGetHandle(),
1034+
null,
1035+
UIntPtr.Zero,
1036+
Kernel32Facts.threadProc,
1037+
&result,
1038+
Kernel32.CreateThreadFlags.None,
1039+
null,
1040+
&dwNewThreadId);
1041+
Kernel32.WaitForSingleObject(hThread, -1);
1042+
1043+
Assert.True(result);
1044+
Assert.NotEqual((uint)Kernel32.GetCurrentThreadId(), dwNewThreadId);
1045+
}
1046+
1047+
/// <summary>
1048+
/// Helper for <see cref="CreateThread_Test"/>, <see cref="CreateRemoteThread_PseudoTest"/> and
1049+
/// <see cref="CreateRemoteThreadEx_PseudoTest"/> tests.
1050+
///
1051+
/// Updates the boolean data pasesed in to true.
1052+
/// </summary>
1053+
/// <param name="data">
1054+
/// Data passed by the test. Contains a pointer to a boolean value.
1055+
/// </param>
1056+
/// <returns>
1057+
/// Returns 1.
1058+
/// </returns>
1059+
/// <remarks>See <see cref=" Kernel32.THREAD_START_ROUTINE"/> for general documentation</remarks>
1060+
private static unsafe uint CreateThread_Test_ThreadMain(void* data)
1061+
{
1062+
*(bool*)data = true;
1063+
return 1;
1064+
}
1065+
9471066
private ArraySegment<byte> GetRandomSegment(int size)
9481067
{
9491068
var result = new ArraySegment<byte>(new byte[size]);

src/Kernel32/PublicAPI.Unshipped.txt

+17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
PInvoke.Kernel32.CreatePseudoConsoleFlags
22
PInvoke.Kernel32.CreatePseudoConsoleFlags.None = 0 -> PInvoke.Kernel32.CreatePseudoConsoleFlags
33
PInvoke.Kernel32.CreatePseudoConsoleFlags.PSEUDOCONSOLE_INHERIT_CURSOR = 1 -> PInvoke.Kernel32.CreatePseudoConsoleFlags
4+
PInvoke.Kernel32.CreateThreadFlags
5+
PInvoke.Kernel32.CreateThreadFlags.CREATE_SUSPENDED = 4 -> PInvoke.Kernel32.CreateThreadFlags
6+
PInvoke.Kernel32.CreateThreadFlags.None = 0 -> PInvoke.Kernel32.CreateThreadFlags
7+
PInvoke.Kernel32.CreateThreadFlags.STACK_SIZE_PARAM_IS_A_RESERVATION = 65536 -> PInvoke.Kernel32.CreateThreadFlags
48
PInvoke.Kernel32.ErrorModes
59
PInvoke.Kernel32.ErrorModes.SEM_DEFAULT = 0 -> PInvoke.Kernel32.ErrorModes
610
PInvoke.Kernel32.ErrorModes.SEM_FAILCRITICALERRORS = 1 -> PInvoke.Kernel32.ErrorModes
@@ -81,6 +85,7 @@ PInvoke.Kernel32.STARTUPINFO.lpTitle_IntPtr.set -> void
8185
PInvoke.Kernel32.SafePseudoConsoleHandle
8286
PInvoke.Kernel32.SafePseudoConsoleHandle.SafePseudoConsoleHandle() -> void
8387
PInvoke.Kernel32.SafePseudoConsoleHandle.SafePseudoConsoleHandle(System.IntPtr preexistingHandle, bool ownsHandle = true) -> void
88+
PInvoke.Kernel32.THREAD_START_ROUTINE
8489
PInvoke.Kernel32.VER_CONDITION
8590
PInvoke.Kernel32.VER_CONDITION.VER_AND = 6 -> PInvoke.Kernel32.VER_CONDITION
8691
PInvoke.Kernel32.VER_CONDITION.VER_EQUAL = 1 -> PInvoke.Kernel32.VER_CONDITION
@@ -102,6 +107,15 @@ override PInvoke.Kernel32.SafePseudoConsoleHandle.IsInvalid.get -> bool
102107
override PInvoke.Kernel32.SafePseudoConsoleHandle.ReleaseHandle() -> bool
103108
static PInvoke.Kernel32.CompareFileTime(PInvoke.Kernel32.FILETIME lpFileTime1, PInvoke.Kernel32.FILETIME lpFileTime2) -> int
104109
static PInvoke.Kernel32.CompareFileTime(System.IntPtr lpFileTime1, System.IntPtr lpFileTime2) -> int
110+
static PInvoke.Kernel32.CreateRemoteThread(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES? lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, ref uint? lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
111+
static PInvoke.Kernel32.CreateRemoteThread(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES? lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, void* lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, ref uint? lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
112+
static PInvoke.Kernel32.CreateRemoteThread(System.IntPtr hProcess, System.IntPtr lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, System.IntPtr lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
113+
static PInvoke.Kernel32.CreateRemoteThreadEx(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, System.IntPtr lpAttributeList, ref uint? lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
114+
static PInvoke.Kernel32.CreateRemoteThreadEx(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, void* lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, PInvoke.Kernel32.PROC_THREAD_ATTRIBUTE_LIST* lpAttributeList, ref uint? lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
115+
static PInvoke.Kernel32.CreateRemoteThreadEx(System.IntPtr hProcess, System.IntPtr lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, System.IntPtr lpAttributeList, System.IntPtr lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
116+
static PInvoke.Kernel32.CreateThread(PInvoke.Kernel32.SECURITY_ATTRIBUTES? lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, ref uint? lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
117+
static PInvoke.Kernel32.CreateThread(PInvoke.Kernel32.SECURITY_ATTRIBUTES? lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, void* lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, ref uint? lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
118+
static PInvoke.Kernel32.CreateThread(System.IntPtr lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, System.IntPtr lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
105119
static PInvoke.Kernel32.DosDateTimeToFileTime(ushort wFatDate, ushort wFatTime, System.IntPtr lpFileTime) -> bool
106120
static PInvoke.Kernel32.DosDateTimeToFileTime(ushort wFatDate, ushort wFatTime, out PInvoke.Kernel32.FILETIME lpFileTime) -> bool
107121
static PInvoke.Kernel32.FILETIME.explicit operator System.DateTime(PInvoke.Kernel32.FILETIME fileTime) -> System.DateTime
@@ -126,6 +140,9 @@ static PInvoke.Kernel32.VerifyVersionInfo(ref PInvoke.Kernel32.OSVERSIONINFOEX l
126140
static PInvoke.Kernel32.WriteProcessMemory(System.IntPtr hProcess, System.IntPtr lpBaseAddress, System.IntPtr lpBuffer, System.UIntPtr nSize, out System.UIntPtr lpNumberOfBytesWritten) -> bool
127141
static extern PInvoke.Kernel32.CompareFileTime(PInvoke.Kernel32.FILETIME* lpFileTime1, PInvoke.Kernel32.FILETIME* lpFileTime2) -> int
128142
static extern PInvoke.Kernel32.CreatePseudoConsole(PInvoke.COORD size, PInvoke.Kernel32.SafeObjectHandle hInput, PInvoke.Kernel32.SafeObjectHandle hOutput, PInvoke.Kernel32.CreatePseudoConsoleFlags dwFlags, out PInvoke.Kernel32.SafePseudoConsoleHandle phPC) -> PInvoke.HResult
143+
static extern PInvoke.Kernel32.CreateRemoteThread(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES* lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, void* lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, uint* lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
144+
static extern PInvoke.Kernel32.CreateRemoteThreadEx(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES* lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, void* lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, PInvoke.Kernel32.PROC_THREAD_ATTRIBUTE_LIST* lpAttributeList, uint* lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
145+
static extern PInvoke.Kernel32.CreateThread(PInvoke.Kernel32.SECURITY_ATTRIBUTES* lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, void* lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, uint* lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
129146
static extern PInvoke.Kernel32.DosDateTimeToFileTime(ushort wFatDate, ushort wFatTime, PInvoke.Kernel32.FILETIME* lpFileTime) -> bool
130147
static extern PInvoke.Kernel32.FileTimeToSystemTime(PInvoke.Kernel32.FILETIME* lpFileTime, PInvoke.Kernel32.SYSTEMTIME* lpSystemTime) -> bool
131148
static extern PInvoke.Kernel32.GetHandleInformation(System.Runtime.InteropServices.SafeHandle hObject, PInvoke.Kernel32.HandleFlags* lpdwFlags) -> bool

src/Kernel32/storebanned/Kernel32+CreateProcessFlags.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public enum CreateProcessFlags
125125
PROCESS_MODE_BACKGROUND_END = 0x00200000,
126126
PROFILE_USER = 0x10000000,
127127
PROFILE_KERNEL = 0x20000000,
128-
PROFILE_SERVER = 0x40000000
128+
PROFILE_SERVER = 0x40000000,
129129
}
130130
}
131131
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright © .NET Foundation and Contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace PInvoke
5+
{
6+
using System;
7+
8+
/// <content>
9+
/// Contains the <see cref="CreateThreadFlags"/> nested type.
10+
/// </content>
11+
public partial class Kernel32
12+
{
13+
/// <summary>
14+
/// Flags that may be passed to the <see cref="CreateThread(SECURITY_ATTRIBUTES*, UIntPtr, THREAD_START_ROUTINE, void*, CreateThreadFlags, uint*)"/> function
15+
/// </summary>
16+
[Flags]
17+
public enum CreateThreadFlags
18+
{
19+
None = 0x0,
20+
21+
/// <summary>
22+
/// The primary thread of the new process is created in a suspended state, and does not run until the <see cref="ResumeThread"/> function is called.
23+
/// </summary>
24+
CREATE_SUSPENDED = 0x00000004,
25+
26+
/// <summary>
27+
/// The dwStackSize parameter in <see cref="CreateThread(SECURITY_ATTRIBUTES*, UIntPtr, THREAD_START_ROUTINE, void*, CreateThreadFlags, uint*)"/>
28+
/// specifies the initial reserve size of the stack. If this flag is not specified,
29+
/// dwStackSize specifies the commit size.
30+
/// </summary>
31+
STACK_SIZE_PARAM_IS_A_RESERVATION = 0x00010000,
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)