Ask Learn
Preview
Please sign in to use this experience.
Sign inThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
If you want to create a new thread in your process in C#, you can use Thread.Start. But things are a little harder if you want to create a thread in another process, ala kernel32!CreateRemoteThread. Disclaimer: CreateRemoteThread is evil, could dead-lock, doesn't always work (particularly across-sessions), and should be avoided in favor or friendlier inter-process communication libraries unless you're a rocket scientist.
Dangers and evils aside, here's how you can use it from C#.
The challenge with CreateRemoteThread is to to pass a thread proc that's a valid function pointer in the target process. In C#, we use delegates instead of function-pointers, but delegates are only valid in the current process and don't serialize. The basic trick is to use Marshal.GetFunctionPointerForDelegate to get an IntPtr to the method that will serve as the thread proc. While delegates can't be serialized across a process boundary, IntPtrs can.
Here's a snippet that demos CreateRemoteThread in C#. When invoked with no command line args, it will:
Here's the output. Both apps print to the same console so that their text is interweaved to display causality:
Pid 5944:Started Parent process Pid 5796:Started Child process Pid 5944: Inside my new thread!. Param=6789 Pid 5796: Thread exited with code: 1
Here's the interesting code snippet (built with VS2005):
// stdcall
static int MyThreadProc(IntPtr param)
{
int pid = Process.GetCurrentProcess().Id;
Console.WriteLine("Pid {0}: Inside my new thread!. Param={1}", pid, param.ToInt32());
return 1;
}
// Helper to wait for a thread to exit and print its exit code
static void WaitForThreadToExit(IntPtr hThread)
{
WaitForSingleObject(hThread, unchecked((uint)-1));
uint exitCode;
GetExitCodeThread(hThread, out exitCode);
int pid = Process.GetCurrentProcess().Id;
Console.WriteLine("Pid {0}: Thread exited with code: {1}", pid, exitCode);
}
// Main function
static void Main(string[] args)
{
int pid = Process.GetCurrentProcess().Id;
if (args.Length == 0)
{
Console.WriteLine("Pid {0}:Started Parent process", pid);
// Spawn the child
string fileName = Process.GetCurrentProcess().MainModule.FileName.Replace(".vshost", "");
// Get thread proc as an IntPtr, which we can then pass to the 2nd-process.
// We must keep the delegate alive so that fpProc remains valid
ThreadProc proc = new ThreadProc(MyThreadProc);
IntPtr fpProc = Marshal.GetFunctionPointerForDelegate(proc);
// Spin up the other process, and pass our pid and function pointer so that it can
// use that to call CreateRemoteThraed
string arg = String.Format("{0} {1}", pid, fpProc);
ProcessStartInfo info = new ProcessStartInfo(fileName, arg);
info.UseShellExecute = false; // share console, output is interlaces.
Process processChild = Process.Start(info);
processChild.WaitForExit();
GC.KeepAlive(proc); // keep the delegate from being collected
return;
}
else
{
Console.WriteLine("Pid {0}:Started Child process", pid);
uint pidParent = uint.Parse(args[0]);
IntPtr fpProc = new IntPtr(int.Parse(args[1]));
IntPtr hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pidParent);
uint dwThreadId;
// Create a thread in the first process.
IntPtr hThread = CreateRemoteThread(
hProcess,
IntPtr.Zero,
0,
fpProc, new IntPtr(6789),
0,
out dwThreadId);
WaitForThreadToExit(hThread);
return;
}
}
The full sample code is available at: https://blogs.msdn.com/jmstall/articles/sample_create_remote_thread.aspx.
More on the marshalling:
The simple way to do call CreateThread (the local version) from managed code would be something like this:
// Thread proc, to be used with Create*Thread
public delegate int ThreadProc(IntPtr param);
// Friendly version, marshals thread-proc as friendly delegate
[DllImport("kernel32")]
public static extern IntPtr CreateThread(
IntPtr lpThreadAttributes,
uint dwStackSize,
ThreadProc lpStartAddress, // ThreadProc as friendly delegate
IntPtr lpParameter,
uint dwCreationFlags,
out uint dwThreadId);
Notice that the CLR's marshaller is smart enough to marshal the friendly type-safe ThreadProc delegate as an evil unsafe function pointer. At this pointer, unless we needed some very fine control over thread-creation (eg specific flags), we're probably still better off using Thread.Start. The other IntPtrs (like lpThreadAttributes) could also be marshaled as type-safe structures instead of intptrs (see on https://pinvoke.net for more examples). We could have marshaled it like:
// Marshal with ThreadProc's function pointer as a raw IntPtr.
[DllImport("kernel32", EntryPoint="CreateThread")]
public static extern IntPtr CreateThreadRaw(
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress, // ThreadProc as raw IntPtr
IntPtr lpParameter,
uint dwCreationFlags,
out uint dwThreadId);
The raw function pointer doesn't really buy us much in the local case. But once we view it like that, the leap to CreateRemoteThread is smaller:
// CreateRemoteThread, since ThreadProc is in remote process, we must use a raw function-pointer.
[DllImport("kernel32")]
public static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress, // raw Pointer into remote process
IntPtr lpParameter,
uint dwCreationFlags,
out uint lpThreadId
);
Anonymous
September 29, 2006
Will this work on a 64 bit machine ? WaitForSingleObject(hThread, unchecked((uint)-1));
Anonymous
September 30, 2006
Pour ceux qui auraient un jour besoin d'utiliser l'API CreateRemoteThread en C#, Mike Stall nous livre...
Anonymous
September 30, 2006
Blandest - it should still work, though it's not the cleanest.
INFINITE is defined in the windows headers as:
#define INFINITE 0xFFFFFFFF // Infinite timeout
and 'uint' in C# is 32-bits on both 32 and 64 machines.
If you have a C# app print
(unchecked((uint)-1).ToString("x"))
it will print the same thing on both 32 and 64-bit.
It would have been cleaner if I defined a constant and used that, but this was just a quick demo of CreateRemoteThread.
Anonymous
October 03, 2006
How would make sure that the delegate wasn't collected. Would you make it static?
Anonymous
October 05, 2006
Dmitriy - Ah! You got me.
I updated the sample with a GC.KeepAlive to address that.
Anonymous
April 22, 2008
In the previous article I discussed a few of the benefits of stack allocation as well as a couple of
Please sign in to use this experience.
Sign in