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:

  1. Create a delegate to the thread proc, MyThreadProc
  2. Get an IntPtr (fpProc) for that via Marshal.GetFunctionPointerForDelegate
  3. spin up a 2nd instance of its own process, and pass it the parent's process id and  fpProc on the command line.  (So we're using the command line + parent/child process relationships as our initial IPC mechanism)
  4. The child (which knows it's the child because it has command line args) then parses the command line to recover the parents's pid and fpProc. and then gets the handle for the parent process.
  5. The child then makes the call to CreateRemoteThread, and passes some arbitrary data (in this case, the number 6789) as the thread proc's data.

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: http://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 http://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
        );