如何异步过程调用封送处理时,代表你的P / Invoke从C#? [英] How do Asynchronous Procedure Calls handle marshaled delegates when you P/Invoke from C#?

查看:476
本文介绍了如何异步过程调用封送处理时,代表你的P / Invoke从C#?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道是否有可能牺牲品问题围绕管理的线程在本土世界的管理,当你在我的具体情况到P / Invoke的名帅下方回调委托给一个DLL(参见例如code )。

在Windows 国家对托管和非托管线程这MSDN文章:

的操作系统的ThreadId没有固定的关系提高到一个托管线程,因为非托管主机可以控制托管和非托管线程之间的关系,特别是一个复杂的主机可以使用光纤的API来调度很多托管线程针对相同的操作系统线程,或移动至不同的操作系统线程之间托管线程。

首先,谁或什么的非托管主机的这篇文章介绍?如果您使用编组比如上例中code我在下面,那么谁或什么非托管主机呢?

此外,<一个href="http://stackoverflow.com/questions/5624128/how-do-i-get-the-real-thread-id-in-a-clr-friendly-way">this StackOverflow的问题的接受的答案状态:

这是从CLR的角度为一个单一的管理线程完全合法的由几个不同的本地线程在它的生命周期进行备份,这意味着整个线程的一生中GetCurrentThreadId的结果可以(会)的变化。

那么,这是否意味着我的APC将排队,在本地线程,或封送处理层,因为直接委托给我的托管线程?

下面是例子。比方说,我用下面的类的P / Invoke的管理code中的NotifyServiceStatusChange功能来检查当服务被停止:

 使用系统;
使用了System.Runtime.InteropServices;

命名空间ServiceStatusChecking
{
    类QueryServiceStatus
    {

        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        公共类SERVICE_NOTIFY
        {
            公共UINT dwVersion;
            公众的IntPtr pfnNotifyCallback;
            公众的IntPtr pContext;
            公共UINT dwNotificationStatus;
            公共SERVICE_STATUS_PROCESS ServiceStatus;
            公共UINT dwNotificationTriggered;
            公众的IntPtr pszServiceNames;
        };

        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        公共结构SERVICE_STATUS_PROCESS
        {
            公共UINT dwServiceType;
            公共UINT dwCurrentState;
            公共UINT dwControlsAccepted;
            公共UINT dwWin32Exit code;
            公共UINT dwServiceSpecificExit code;
            公共UINT dwCheckPoint;
            公共UINT dwWaitHint;
            公共UINT dwProcessId;
            公共UINT dwServiceFlags;
        };

        [的DllImport(advapi32.dll中)
        静态外部的IntPtr OpenService(IntPtr的hSCManager,串lpServiceName,UINT dwDesiredAccess);

        [的DllImport(advapi32.dll中)
        静态外部的IntPtr OpenSCManager(字符串机器名,字符串DATABASENAME,UINT dwAccess);

        [的DllImport(advapi32.dll中)
        静态外部UINT NotifyServiceStatusChange(IntPtr的hService,UINT dwNotifyMask,IntPtr的pNotifyBuffer);

        [DllImportAttribute(KERNEL32.DLL)
        静态外部UINT SleepEx(UINT dwMilliseconds,布尔bAlertable);

        [的DllImport(advapi32.dll中)
        静态外部布尔CloseServiceHandle(IntPtr的hSCObject);

        委托无效StatusChangedCallbackDelegate(IntPtr的参数);

        ///&LT;总结&gt;
        ///阻塞,直到服务停止或被发现已经死亡。
        ///&LT; /总结&gt;
        ///&LT; PARAM NAME =服务名&gt;该服务,你想等到名称和LT; /参数&GT;
        公共静态无效WaitForServiceToStop(字符串服务名)
        {
            IntPtr的HSCM = OpenSCManager(NULL,NULL,(UINT)0xF003F);
            如果(HSCM!= IntPtr.Zero)
            {
                IntPtr的hService = OpenService(HSCM,服务名,(UINT)0xF003F);
                如果(hService!= IntPtr.Zero)
                {
                    StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
                    SERVICE_NOTIFY通知=新SERVICE_NOTIFY();
                    notify.dwVersion = 2;
                    notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
                    notify.ServiceStatus =新SERVICE_STATUS_PROCESS();
                    的GCHandle notifyHandle = GCHandle.Alloc(通知,GCHandleType.Pinned);
                    IntPtr的pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject();
                    NotifyServiceStatusChange(hService,(UINT)00000001,pinnedNotifyStructure);
                    SleepEx(uint.MaxValue,真正的);
                    notifyHandle.Free();
                    CloseServiceHandle(hService);
                }
                CloseServiceHandle(HSCM);
            }
        }

        公共静态无效ReceivedStatusChangedEvent(IntPtr的参数)
        {

        }
    }
}
 

时的APC排队到任何本地线程正在主持我的托管线程,或者是APC直接委托给我的托管线程?我想委托在那里处理的正是这种情况下,让我们不必担心管理线程本身是如何处理的,但我可能是错的!

编辑:我想这是一个比较认同的答案

 使用系统;
使用了System.Runtime.InteropServices;
使用的System.Threading;

命名空间ServiceAssistant
{
    类ServiceHelper
    {

        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        公共类SERVICE_NOTIFY
        {
            公共UINT dwVersion;
            公众的IntPtr pfnNotifyCallback;
            公众的IntPtr pContext;
            公共UINT dwNotificationStatus;
            公共SERVICE_STATUS_PROCESS ServiceStatus;
            公共UINT dwNotificationTriggered;
            公众的IntPtr pszServiceNames;
        };

        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        公共结构SERVICE_STATUS_PROCESS
        {
            公共UINT dwServiceType;
            公共UINT dwCurrentState;
            公共UINT dwControlsAccepted;
            公共UINT dwWin32Exit code;
            公共UINT dwServiceSpecificExit code;
            公共UINT dwCheckPoint;
            公共UINT dwWaitHint;
            公共UINT dwProcessId;
            公共UINT dwServiceFlags;
        };

        [的DllImport(advapi32.dll中)
        静态外部的IntPtr OpenService(IntPtr的hSCManager,串lpServiceName,UINT dwDesiredAccess);

        [的DllImport(advapi32.dll中)
        静态外部的IntPtr OpenSCManager(字符串机器名,字符串DATABASENAME,UINT dwAccess);

        [的DllImport(advapi32.dll中)
        静态外部UINT NotifyServiceStatusChange(IntPtr的hService,UINT dwNotifyMask,IntPtr的pNotifyBuffer);

        [DllImportAttribute(KERNEL32.DLL)
        静态外部UINT SleepEx(UINT dwMilliseconds,布尔bAlertable);

        [的DllImport(advapi32.dll中)
        静态外部布尔CloseServiceHandle(IntPtr的hSCObject);

        委托无效StatusChangedCallbackDelegate(IntPtr的参数);

        ///&LT;总结&gt;
        ///阻塞,直到服务停止或被发现已经死亡。
        ///&LT; /总结&gt;
        ///&LT; PARAM NAME =服务名&gt;该服务,你想等到名称和LT; /参数&GT;
        ///&LT; PARAM NAME =超时&gt;一种你希望等待的时间量。 uint.MaxValue是默认的,这将迫使该线程无限期地等待&LT; /参数&GT;
        公共静态无效WaitForServiceToStop(字符串服务名,UINT超时= uint.MaxValue)
        {
            Thread.BeginThreadAffinity();
            的GCHandle? notifyHandle = NULL;
            StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
            IntPtr的HSCM = OpenSCManager(NULL,NULL,(UINT)0xF003F);
            如果(HSCM!= IntPtr.Zero)
            {
                IntPtr的hService = OpenService(HSCM,服务名,(UINT)0xF003F);
                如果(hService!= IntPtr.Zero)
                {
                    SERVICE_NOTIFY通知=新SERVICE_NOTIFY();
                    notify.dwVersion = 2;
                    notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
                    notify.ServiceStatus =新SERVICE_STATUS_PROCESS();
                    notifyHandle = GCHandle.Alloc(通知,GCHandleType.Pinned);
                    IntPtr的pinnedNotifyStructure =((的GCHandle)notifyHandle).AddrOfPinnedObject();
                    NotifyServiceStatusChange(hService,(UINT)00000001,pinnedNotifyStructure);
                    SleepEx(超时,真正的);
                    CloseServiceHandle(hService);
                }
                CloseServiceHandle(HSCM);
            }
            GC.KeepAlive(changeDelegate);
            如果(notifyHandle!= NULL)
            {
                ((的GCHandle)notifyHandle)。免费();
            }
            Thread.EndThreadAffinity();
        }

        公共静态无效ReceivedStatusChangedEvent(IntPtr的参数)
        {

        }
    }
}
 

修改了!我想这是一个更愉快的回答:

 使用系统;
使用了System.Runtime.InteropServices;
使用的System.Threading;

命名空间ServiceAssistant
{
    类ServiceHelper
    {

        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        公共类SERVICE_NOTIFY
        {
            公共UINT dwVersion;
            公众的IntPtr pfnNotifyCallback;
            公众的IntPtr pContext;
            公共UINT dwNotificationStatus;
            公共SERVICE_STATUS_PROCESS ServiceStatus;
            公共UINT dwNotificationTriggered;
            公众的IntPtr pszServiceNames;
        };

        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        公共结构SERVICE_STATUS_PROCESS
        {
            公共UINT dwServiceType;
            公共UINT dwCurrentState;
            公共UINT dwControlsAccepted;
            公共UINT dwWin32Exit code;
            公共UINT dwServiceSpecificExit code;
            公共UINT dwCheckPoint;
            公共UINT dwWaitHint;
            公共UINT dwProcessId;
            公共UINT dwServiceFlags;
        };

        [的DllImport(advapi32.dll中)
        静态外部的IntPtr OpenService(IntPtr的hSCManager,串lpServiceName,UINT dwDesiredAccess);

        [的DllImport(advapi32.dll中)
        静态外部的IntPtr OpenSCManager(字符串机器名,字符串DATABASENAME,UINT dwAccess);

        [的DllImport(advapi32.dll中)
        静态外部UINT NotifyServiceStatusChange(IntPtr的hService,UINT dwNotifyMask,IntPtr的pNotifyBuffer);

        [DllImportAttribute(KERNEL32.DLL)
        静态外部UINT SleepEx(UINT dwMilliseconds,布尔bAlertable);

        [的DllImport(advapi32.dll中)
        静态外部布尔CloseServiceHandle(IntPtr的hSCObject);

        委托无效StatusChangedCallbackDelegate(IntPtr的参数);

        ///&LT;总结&gt;
        ///阻塞,直到服务停止,被杀害,或者被发现已经死亡。
        ///&LT; /总结&gt;
        ///&LT; PARAM NAME =服务名&gt;该服务,你想等到名称和LT; /参数&GT;
        ///&LT; PARAM NAME =超时&gt;一种你希望等待的时间量。 uint.MaxValue是默认的,这将迫使该线程无限期地等待&LT; /参数&GT;
        公共静态无效WaitForServiceToStop(字符串服务名,UINT超时= uint.MaxValue)
        {
            //确保该线程的身份映射,1对1的,具有本地OS线程。
            Thread.BeginThreadAffinity();
            的GCHandle notifyHandle =默认(的GCHandle);
            StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
            IntPtr的HSCM = IntPtr.Zero;
            IntPtr的hService = IntPtr.Zero;
            尝试
            {
                HSCM = OpenSCManager(NULL,NULL,(UINT)0xF003F);
                如果(HSCM!= IntPtr.Zero)
                {
                    hService = OpenService(HSCM,服务名,(UINT)0xF003F);
                    如果(hService!= IntPtr.Zero)
                    {
                        SERVICE_NOTIFY通知=新SERVICE_NOTIFY();
                        notify.dwVersion = 2;
                        notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
                        notify.ServiceStatus =新SERVICE_STATUS_PROCESS();
                        notifyHandle = GCHandle.Alloc(通知,GCHandleType.Pinned);
                        IntPtr的pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject();
                        NotifyServiceStatusChange(hService,(UINT)00000001,pinnedNotifyStructure);
                        SleepEx(超时,真正的);
                    }
                }
            }
            最后
            {
                //清理我们的操作结束,如果此线程被中止。
                如果(hService!= IntPtr.Zero)
                {
                    CloseServiceHandle(hService);
                }
                如果(HSCM!= IntPtr.Zero)
                {
                    CloseServiceHandle(HSCM);
                }
                GC.KeepAlive(changeDelegate);
                如果(notifyHandle!=默认(的GCHandle))
                {
                    notifyHandle.Free();
                }
                Thread.EndThreadAffinity();
            }
        }

        公共静态无效ReceivedStatusChangedEvent(IntPtr的参数)
        {

        }
    }
}
 

解决方案

是的,这是可能的牺牲品,这些问题。在这个特殊的情况下,它是困难的。托管线程的主机不能切换到不同的操作系统线程,而本地人帧是在托管线程的堆栈,因为你马上的P / Invoke SleepEx,窗口主机切换管理线程是两个P之间/调用呼叫。不过,有时一个不愉快的可能性,当你需要的P / Invoke相同的操作系统线程和 Thread.BeginThreadAffinity()的存在是为了掩盖这种情况。

现在的APC的问题。请记住,OS一无所知管理线程。因此,APC将交付到原来的本地线程时,它做了警惕性。我不知道CLR主机会创建如何管理上下文在这些情况下,但如果管理线程是一比一与操作系统线程回调可能会使用管理线程的上下文。

更新您新的code是安全多了,但你去有点过头了另一个方向:

1)没有必要整个包住code相线程关联。总结刚才的那两个P /调用了的执行的需要将相同的OS线程(通知和休眠)上运行。这不要紧,你是否使用有限的超时时间,因为你解决与线程关联的问题是两个P /调用之间的管理到操作系统线程迁移。回调不应该假定它是在任何特定的管理线程上运行,无论如何,因为没有什么可以放心地做的,有点它应该做的:联锁操作,设置事件和完成 TaskCompletionSources 也差不多了。

2)的GCHandle 是一个简单的,的IntPtr -sized结构,并可以进行比较是否相等。相反的GCHandle?的使用,使用普通的的GCHandle 并比较默认(的GCHandle)。此外,的GCHandle?看起来腥给我的一般原则。

3)当你关闭服务句柄通知停止。单片机手柄可以保持打开状态,你可能要保持它周围进行下一步检查。

  // Thread.BeginThreadAffinity();
//的GCHandle? notifyHandle = NULL;
VAR HSCM = OpenSCManager(NULL,NULL,(UINT)0xF003F);
如果(HSCM!= IntPtr.Zero)
{
    StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
    VAR notifyHandle =默认(的GCHandle);

    VAR hService = OpenService(HSCM,服务名,(UINT)0xF003F);
    如果(hService!= IntPtr.Zero)
    {
        ...
        notifyHandle = GCHandle.Alloc(通知,GCHandleType.Pinned);
        变种ADDR = notifyHandle.AddrOfPinnedObject();
        Thread.BeginThreadAffinity();
        NotifyServiceStatusChange(hService,(UINT)00000001,地址);
        SleepEx(超时,真正的);
        Thread.EndThreadAffinity();
        CloseServiceHandle(hService);
    }

    GC.KeepAlive(changeDelegate);
    如果(notifyHandle!=默认(的GCHandle))
        notifyHandle.Free();

    CloseServiceHandle(HSCM);
}
 

此外,要尽可能安全的,如果你的code将运行很长一段时间,或者如果你正在写一个库,则必须使用受限的区域和/或SafeHandles,以确保您清除程序如果线程被中止运行,即使。看看所有的篮球BCL code在跳通过,例如,System.Threading.Mutex(使用反射镜或CLR源)。最起码,使用SafeHandles和try /最后释放的GCHandle和结束线程关联。

至于回调有关的问题,这些都是正常的多线程类的问题只是一个不好的情况下:死锁,活锁,优先级反转等有关这类APC回调的最糟糕的事情是,(除非你阻止整个线程自己,直到它发生,在这种情况下,它更容易只是在本地code阻止),当它发生了,你不控制:你的线程可能会在内心深处BCL等待I / O,一个事件的发出信号,等,这是非常困难的推理的状态方案可能是在

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).

This MSDN article on Managed and Unmanaged Threading in Windows states:

"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."

First of all, who or what is the unmanaged host this article describes? If you use marshaling like in the example code I give below, then who or what is the unmanaged host there?

Also, this StackOverflow question's accepted answer states:

"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."

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?

Here is the example. Let's say I use the following class to P/Invoke the NotifyServiceStatusChange function in managed code to check when a service is stopped:

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);

        /// <summary> 
        /// Block until a service stops or is found to be already dead.
        /// </summary> 
        /// <param name="serviceName">The name of the service you would like to wait for.</param>
        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)
        {

        }
    }
}

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!

Edit: I guess this is a more agreeable answer.

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);

        /// <summary> 
        /// Block until a service stops or is found to be already dead.
        /// </summary> 
        /// <param name="serviceName">The name of the service you would like to wait for.</param>
        /// <param name="timeout">An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.</param>
        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)
        {

        }
    }
}

Edit again! I guess THIS is an even MORE agreeable answer:

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);

        /// <summary> 
        /// Block until a service stops, is killed, or is found to be already dead.
        /// </summary> 
        /// <param name="serviceName">The name of the service you would like to wait for.</param>
        /// <param name="timeout">An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.</param>
        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)
        {

        }
    }
}

解决方案

Yes, it is possible to fall victim to these issues. In this particular case it is difficult. The host can't switch the managed thread to a different OS thread while a native frame is on the managed thread's stack, and since you immediately p/invoke SleepEx, the window for the host to switch the managed thread is between the two p/invoke calls. Still, it is sometimes a disagreeable possibility when you need to p/invoke on the same OS thread and Thread.BeginThreadAffinity() exists to cover this scenario.

Now for the APC question. Remember that the OS knows nothing about managed threads. So the APC will be delivered into the original native thread when it does something alertable. I don't know how the CLR host creates managed contexts in these cases, but if managed threads are one-to-one with OS threads the callback will probably use the managed thread's context.

UPDATE Your new code is much safer now, but you went a bit too far in the other direction:

1) There is no need to wrap the whole code with thread affinity. Wrap just the two p/invokes that do need to run on the same OS thread (Notify and Sleep). It does not matter whether you use a finite timeout, because the problem you're solving with thread affinity is a managed-to-OS thread migration between the two p/invokes. The callback should not assume it is running on any particular managed thread anyway, because there is little it can safely do, and little it should do: interlocked operations, setting events and completing TaskCompletionSources is about it.

2) GCHandle is a simple, IntPtr-sized struct, and can be compared for equality. Instead of using GCHandle?, use plain GCHandle and compare to default(GCHandle). Besides, GCHandle? looks fishy to me on general principles.

3) Notification stops when you close the service handle. The SCM handle can stay open, you might want to keep it around for the next check.

// Thread.BeginThreadAffinity();
// GCHandle? notifyHandle = null;
var hSCM  = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
    StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
    var notifyHandle = default(GCHandle);

    var hService  = OpenService(hSCM, serviceName, (uint)0xF003F);
    if (hService != IntPtr.Zero)
    {
        ...
        notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
        var addr = notifyHandle.AddrOfPinnedObject();
        Thread.BeginThreadAffinity();
        NotifyServiceStatusChange(hService, (uint)0x00000001, addr);
        SleepEx(timeout, true);
        Thread.EndThreadAffinity();
        CloseServiceHandle(hService);
    }

    GC.KeepAlive(changeDelegate);
    if (notifyHandle != default(GCHandle))
        notifyHandle.Free();

    CloseServiceHandle(hSCM);
}

Also, to be as safe as possible, if your code is going to run for a long time, or if you're writing a library, you must use constrained regions and/or SafeHandles to ensure your cleanup routines run even if the thread is aborted. Look at all the hoops BCL code jumps through in, e.g., System.Threading.Mutex (use Reflector or the CLR source). At the very least, use SafeHandles and try/finally to free the GCHandle and end thread affinity.

As for callback-related problems, these are just a bad case of normal multi-threading sort of problems: deadlocks, livelocks, priority inversion etc. The worst thing about this sort of APC callback is that (unless you block the whole thread yourself until it happens, in which case it's easier just to block in native code) you don't control when it happens: your thread might be deep inside BCL waiting for I/O, for an event to be signaled, etc., and it is very difficult to reason about the state the program might be in.

这篇关于如何异步过程调用封送处理时,代表你的P / Invoke从C#?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆