title: How do Asynchronous Procedure Calls handle marshaled delegates when you P/Invoke from C#?
body: <p>I am wondering if it is possible to fall victim to issues around the management of managed threads in the native world when you marshal a callback delegate to a DLL through P/Invoke in my particular case below (see example code).</p>
  
  <p><a href="http://msdn.microsoft.com/en-us/library/74169f59.aspx" rel="nofollow noreferrer">This MSDN article on Managed and Unmanaged Threading in Windows</a> states:</p>
  
  <p><em>"An operating-system ThreadId has no fixed relationship to a managed thread, because an unmanaged host can control the relationship between managed and unmanaged threads. Specifically, a sophisticated host can use the Fiber API to schedule many managed threads against the same operating system thread, or to move a managed thread among different operating system threads."</em></p>
  
  <p>First of all, who or what is the <em>unmanaged host</em> this article describes? If you use marshaling like in the example code I give below, then who or what is the unmanaged host there?</p>
  
  <p>Also, <a href="https://stackoverflow.com/questions/5624128/how-do-i-get-the-real-thread-id-in-a-clr-friendly-way">this StackOverflow question</a>'s accepted answer states:</p>
  
  <p><em>"It's perfectly legal from a CLR perspective for a single managed thread to be backed by several different native threads during it's lifetime. This means the result of GetCurrentThreadId can (and will) change throughout the course of a thread's lifetime."</em></p>
  
  <p>So, does this mean my APC will be queued in a native thread, or delegated directly to my managed thread because of the marshaling layer?</p>
  
  <p><strong>Here is the example</strong>. Let's say I use the following class to P/Invoke the NotifyServiceStatusChange function in managed code to check when a service is stopped:</p>
  
  <pre><code>using System;
  using System.Runtime.InteropServices;
  
  namespace ServiceStatusChecking
  {
      class QueryServiceStatus
      {
  
          [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
          public class SERVICE_NOTIFY
          {
              public uint dwVersion;
              public IntPtr pfnNotifyCallback;
              public IntPtr pContext;
              public uint dwNotificationStatus;
              public SERVICE_STATUS_PROCESS ServiceStatus;
              public uint dwNotificationTriggered;
              public IntPtr pszServiceNames;
          };
  
          [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
          public struct SERVICE_STATUS_PROCESS
          {
              public uint dwServiceType;
              public uint dwCurrentState;
              public uint dwControlsAccepted;
              public uint dwWin32ExitCode;
              public uint dwServiceSpecificExitCode;
              public uint dwCheckPoint;
              public uint dwWaitHint;
              public uint dwProcessId;
              public uint dwServiceFlags;
          };
  
          [DllImport("advapi32.dll")]
          static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
  
          [DllImport("advapi32.dll")]
          static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
  
          [DllImport("advapi32.dll")]
          static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
  
          [DllImportAttribute("kernel32.dll")]
          static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
  
          [DllImport("advapi32.dll")]
          static extern bool CloseServiceHandle(IntPtr hSCObject);
  
          delegate void StatusChangedCallbackDelegate(IntPtr parameter);
  
          /// &lt;summary&gt; 
          /// Block until a service stops or is found to be already dead.
          /// &lt;/summary&gt; 
          /// &lt;param name="serviceName"&gt;The name of the service you would like to wait for.&lt;/param&gt;
          public static void WaitForServiceToStop(string serviceName)
          {
              IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
              if (hSCM != IntPtr.Zero)
              {
                  IntPtr hService = OpenService(hSCM, serviceName, (uint)0xF003F);
                  if (hService != IntPtr.Zero)
                  {
                      StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
                      SERVICE_NOTIFY notify = new SERVICE_NOTIFY();
                      notify.dwVersion = 2;
                      notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
                      notify.ServiceStatus = new SERVICE_STATUS_PROCESS();
                      GCHandle notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
                      IntPtr pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject();
                      NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure);
                      SleepEx(uint.MaxValue, true);
                      notifyHandle.Free();
                      CloseServiceHandle(hService);
                  }
                  CloseServiceHandle(hSCM);
              }
          }
  
          public static void ReceivedStatusChangedEvent(IntPtr parameter)
          {
  
          }
      }
  }
  </code></pre>
  
  <p>Is the APC queued onto whichever native thread was hosting my managed thread, or is the APC delegated directly to my managed thread? I thought the delegate was there to handle exactly this case, so that we don't need to worry about how managed threads are handled natively, but I could be wrong!</p>
  
  <p><strong>Edit: I guess this is a more agreeable answer.</strong></p>
  
  <pre><code>using System;
  using System.Runtime.InteropServices;
  using System.Threading;
  
  namespace ServiceAssistant
  {
      class ServiceHelper
      {
  
          [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
          public class SERVICE_NOTIFY
          {
              public uint dwVersion;
              public IntPtr pfnNotifyCallback;
              public IntPtr pContext;
              public uint dwNotificationStatus;
              public SERVICE_STATUS_PROCESS ServiceStatus;
              public uint dwNotificationTriggered;
              public IntPtr pszServiceNames;
          };
  
          [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
          public struct SERVICE_STATUS_PROCESS
          {
              public uint dwServiceType;
              public uint dwCurrentState;
              public uint dwControlsAccepted;
              public uint dwWin32ExitCode;
              public uint dwServiceSpecificExitCode;
              public uint dwCheckPoint;
              public uint dwWaitHint;
              public uint dwProcessId;
              public uint dwServiceFlags;
          };
  
          [DllImport("advapi32.dll")]
          static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
  
          [DllImport("advapi32.dll")]
          static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
  
          [DllImport("advapi32.dll")]
          static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
  
          [DllImportAttribute("kernel32.dll")]
          static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
  
          [DllImport("advapi32.dll")]
          static extern bool CloseServiceHandle(IntPtr hSCObject);
  
          delegate void StatusChangedCallbackDelegate(IntPtr parameter);
  
          /// &lt;summary&gt; 
          /// Block until a service stops or is found to be already dead.
          /// &lt;/summary&gt; 
          /// &lt;param name="serviceName"&gt;The name of the service you would like to wait for.&lt;/param&gt;
          /// &lt;param name="timeout"&gt;An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.&lt;/param&gt;
          public static void WaitForServiceToStop(string serviceName, uint timeout = uint.MaxValue)
          {
              Thread.BeginThreadAffinity();
              GCHandle? notifyHandle = null;
              StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
              IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
              if (hSCM != IntPtr.Zero)
              {
                  IntPtr hService = OpenService(hSCM, serviceName, (uint)0xF003F);
                  if (hService != IntPtr.Zero)
                  {
                      SERVICE_NOTIFY notify = new SERVICE_NOTIFY();
                      notify.dwVersion = 2;
                      notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
                      notify.ServiceStatus = new SERVICE_STATUS_PROCESS();
                      notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
                      IntPtr pinnedNotifyStructure = ((GCHandle)notifyHandle).AddrOfPinnedObject();
                      NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure);
                      SleepEx(timeout, true);
                      CloseServiceHandle(hService);
                  }
                  CloseServiceHandle(hSCM);
              }
              GC.KeepAlive(changeDelegate);
              if (notifyHandle != null)
              {
                  ((GCHandle)notifyHandle).Free();
              }
              Thread.EndThreadAffinity();
          }
  
          public static void ReceivedStatusChangedEvent(IntPtr parameter)
          {
  
          }
      }
  }
  </code></pre>
  
  <p><strong>Edit again! I guess THIS is an even MORE agreeable answer:</strong></p>
  
  <pre><code>using System;
  using System.Runtime.InteropServices;
  using System.Threading;
  
  namespace ServiceAssistant
  {
      class ServiceHelper
      {
  
          [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
          public class SERVICE_NOTIFY
          {
              public uint dwVersion;
              public IntPtr pfnNotifyCallback;
              public IntPtr pContext;
              public uint dwNotificationStatus;
              public SERVICE_STATUS_PROCESS ServiceStatus;
              public uint dwNotificationTriggered;
              public IntPtr pszServiceNames;
          };
  
          [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
          public struct SERVICE_STATUS_PROCESS
          {
              public uint dwServiceType;
              public uint dwCurrentState;
              public uint dwControlsAccepted;
              public uint dwWin32ExitCode;
              public uint dwServiceSpecificExitCode;
              public uint dwCheckPoint;
              public uint dwWaitHint;
              public uint dwProcessId;
              public uint dwServiceFlags;
          };
  
          [DllImport("advapi32.dll")]
          static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
  
          [DllImport("advapi32.dll")]
          static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
  
          [DllImport("advapi32.dll")]
          static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
  
          [DllImportAttribute("kernel32.dll")]
          static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
  
          [DllImport("advapi32.dll")]
          static extern bool CloseServiceHandle(IntPtr hSCObject);
  
          delegate void StatusChangedCallbackDelegate(IntPtr parameter);
  
          /// &lt;summary&gt; 
          /// Block until a service stops, is killed, or is found to be already dead.
          /// &lt;/summary&gt; 
          /// &lt;param name="serviceName"&gt;The name of the service you would like to wait for.&lt;/param&gt;
          /// &lt;param name="timeout"&gt;An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.&lt;/param&gt;
          public static void WaitForServiceToStop(string serviceName, uint timeout = uint.MaxValue)
          {
              // Ensure that this thread's identity is mapped, 1-to-1, with a native OS thread.
              Thread.BeginThreadAffinity();
              GCHandle notifyHandle = default(GCHandle);
              StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
              IntPtr hSCM = IntPtr.Zero;
              IntPtr hService = IntPtr.Zero;
              try
              {
                  hSCM = OpenSCManager(null, null, (uint)0xF003F);
                  if (hSCM != IntPtr.Zero)
                  {
                      hService = OpenService(hSCM, serviceName, (uint)0xF003F);
                      if (hService != IntPtr.Zero)
                      {
                          SERVICE_NOTIFY notify = new SERVICE_NOTIFY();
                          notify.dwVersion = 2;
                          notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
                          notify.ServiceStatus = new SERVICE_STATUS_PROCESS();
                          notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
                          IntPtr pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject();
                          NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure);
                          SleepEx(timeout, true);
                      }
                  }
              }
              finally
              {
                  // Clean up at the end of our operation, or if this thread is aborted.
                  if (hService != IntPtr.Zero)
                  {
                      CloseServiceHandle(hService);
                  }
                  if (hSCM != IntPtr.Zero)
                  {
                      CloseServiceHandle(hSCM);
                  }
                  GC.KeepAlive(changeDelegate);
                  if (notifyHandle != default(GCHandle))
                  {
                      notifyHandle.Free();
                  }
                  Thread.EndThreadAffinity();
              }
          }
  
          public static void ReceivedStatusChangedEvent(IntPtr parameter)
          {
  
          }
      }
  }
  </code></pre>