8

デバイスの通知をリッスンするサービスを作成したい(USBメディアが接続されている、取り外されている)。C#サービスでデバイス通知をリッスンする問題はSystem.Windows.Forms.Control.WndProc、Windowsサービスにウィンドウがないために利用できないことです。このようなサービスの書き方について、このハウツー
を見つけました。その記事の作成者は、サービスがサービス制御メッセージの代わりにデバイス通知をリッスンできるようにする回避策を見つけました。そのため、サービスはOnStop()をサポートしなくなりました。

(更新26.01.13 :)
残念ながら、サービスコントロールマネージャーとWindowsAPIをよく理解していません。サービス制御メッセージとUSBデバイス通知の両方に登録できるのか、それともサービスがデバイス通知をリッスンする唯一のオプションであるのか、疑問に思っています。問題を解決する(私にとって理解できる)情報はまだ見つかりません。
ウィンドウを生成せずに使用できる可能性がSystem.Windows.Forms.Control.WndProcあります(System.Windows.Formsアセンブリを追加するだけですよね?)。

(更新27.01.13 :)
私はちょうどこの質問を見つけました:Windows 7のWindowsサービスからデスクトップアプリケーションを開始することはできません
そこで の2番目の答えは、WindowsサービスがWindows Vistaでセキュリティ中心の改造を受け、GUI要素がセッション0で作成されることを示しています「サービスがデスクトップと対話することを許可する」がチェックされている場合でも。これは、USBデバイスイベントを受信するWindowsフォームを作成できることを意味します(したがって、これを行うためのServiceControlHandler警告や問題はありますか?をいじる必要はありませんか?

つまり、次のいずれかを実行するソリューションが必要です。

  1. OnStopを再び利用可能にする、または
  2. Windows C#サービスでUSBデバイス通知をリッスンする別の方法を提供する

私のソースコードは現在次のとおりです。これは、最初の段落でリンクしたHowToによって提供されたソースコードとほぼ同じです。私が行った唯一の違いは、FileSystemWatcherが必要ないため、FileSystemWatcherプライベートフィールドとそのすべての使用法を削除することです。

USBBackup.cs(サービス自体-除外されたステートメントを使用していますが、私のソースコードでは完全です):

namespace USBBackup
{
    public partial class USBBackup : ServiceBase
    {

        private IntPtr deviceNotifyHandle;
        private IntPtr deviceEventHandle;
        private IntPtr directoryHandle;
        private Win32.ServiceControlHandlerEx myCallback;

        private int ServiceControlHandler(int control, int eventType, IntPtr eventData, IntPtr context)
        {
            if (control == Win32.SERVICE_CONTROL_STOP || control == Win32.SERVICE_CONTROL_SHUTDOWN)
            {
                UnregisterHandles();
                Win32.UnregisterDeviceNotification(deviceEventHandle);

                base.Stop();
            }
            else if (control == Win32.SERVICE_CONTROL_DEVICEEVENT)
            {
                switch (eventType)
                {
                    case Win32.DBT_DEVICEARRIVAL:
                        Win32.DEV_BROADCAST_HDR hdr;
                        hdr = (Win32.DEV_BROADCAST_HDR)Marshal.PtrToStructure(eventData, typeof(Win32.DEV_BROADCAST_HDR));
                        if (hdr.dbcc_devicetype == Win32.DBT_DEVTYP_DEVICEINTERFACE)
                        {
                            Win32.DEV_BROADCAST_DEVICEINTERFACE deviceInterface;
                            deviceInterface = (Win32.DEV_BROADCAST_DEVICEINTERFACE)Marshal.PtrToStructure(eventData, typeof(Win32.DEV_BROADCAST_DEVICEINTERFACE));
                            string name = new string(deviceInterface.dbcc_name);
                            name = name.Substring(0, name.IndexOf('\0')) + "\\";

                            StringBuilder stringBuilder = new StringBuilder();
                            Win32.GetVolumeNameForVolumeMountPoint(name, stringBuilder, 100);

                            uint stringReturnLength = 0;
                            string driveLetter = "";

                            Win32.GetVolumePathNamesForVolumeNameW(stringBuilder.ToString(), driveLetter, (uint)driveLetter.Length, ref stringReturnLength);
                            if (stringReturnLength == 0)
                            {
                                // TODO handle error
                            }

                            driveLetter = new string(new char[stringReturnLength]);

                            if (!Win32.GetVolumePathNamesForVolumeNameW(stringBuilder.ToString(), driveLetter, stringReturnLength, ref stringReturnLength))
                            {
                                // TODO handle error
                            }

                            RegisterForHandle(driveLetter[0]);
                        }
                        break;
                    case Win32.DBT_DEVICEQUERYREMOVE:
                        UnregisterHandles();
                        break;
                }
            }

            return 0;
        }

        private void UnregisterHandles()
        {
            if (directoryHandle != IntPtr.Zero)
            {
                Win32.CloseHandle(directoryHandle);
                directoryHandle = IntPtr.Zero;
            }
            if (deviceNotifyHandle != IntPtr.Zero)
            {
                Win32.UnregisterDeviceNotification(deviceNotifyHandle);
                deviceNotifyHandle = IntPtr.Zero;
            }
        }

        private void RegisterForHandle(char c)
        {
            Win32.DEV_BROADCAST_HANDLE deviceHandle = new Win32.DEV_BROADCAST_HANDLE();
            int size = Marshal.SizeOf(deviceHandle);
            deviceHandle.dbch_size = size;
            deviceHandle.dbch_devicetype = Win32.DBT_DEVTYP_HANDLE;
            directoryHandle = CreateFileHandle(c + ":\\");
            deviceHandle.dbch_handle = directoryHandle;
            IntPtr buffer = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(deviceHandle, buffer, true);
            deviceNotifyHandle = Win32.RegisterDeviceNotification(this.ServiceHandle, buffer, Win32.DEVICE_NOTIFY_SERVICE_HANDLE);
            if (deviceNotifyHandle == IntPtr.Zero)
            {
                // TODO handle error
            }
        }

        private void RegisterDeviceNotification()
        {
            myCallback = new Win32.ServiceControlHandlerEx(ServiceControlHandler);
            Win32.RegisterServiceCtrlHandlerEx(this.ServiceName, myCallback, IntPtr.Zero);

            if (this.ServiceHandle == IntPtr.Zero)
            {
                // TODO handle error
            }

            Win32.DEV_BROADCAST_DEVICEINTERFACE deviceInterface = new Win32.DEV_BROADCAST_DEVICEINTERFACE();
            int size = Marshal.SizeOf(deviceInterface);
            deviceInterface.dbcc_size = size;
            deviceInterface.dbcc_devicetype = Win32.DBT_DEVTYP_DEVICEINTERFACE;
            IntPtr buffer = default(IntPtr);
            buffer = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(deviceInterface, buffer, true);
            deviceEventHandle = Win32.RegisterDeviceNotification(this.ServiceHandle, buffer, Win32.DEVICE_NOTIFY_SERVICE_HANDLE | Win32.DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
            if (deviceEventHandle == IntPtr.Zero)
            {
                // TODO handle error
            }
        }

        public USBBackup()
        {
            InitializeComponent();
        }

        public static IntPtr CreateFileHandle(string driveLetter)
        {
            // open the existing file for reading
            IntPtr handle = Win32.CreateFile(
                  driveLetter,
                  Win32.GENERIC_READ,
                  Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
                  0,
                  Win32.OPEN_EXISTING,
                  Win32.FILE_FLAG_BACKUP_SEMANTICS | Win32.FILE_ATTRIBUTE_NORMAL,
                  0);

            if (handle == Win32.INVALID_HANDLE_VALUE)
            {
                return IntPtr.Zero;
            }
            else
            {
                return handle;
            }
        }

        protected override void OnStart(string[] args)
        {
            base.OnStart(args);

            RegisterDeviceNotification();
        }
    }
}

Win32.cs:

namespace USBBackup
{
    public class Win32
    {
        public const int DEVICE_NOTIFY_SERVICE_HANDLE = 1;
        public const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4;

        public const int SERVICE_CONTROL_STOP = 1;
        public const int SERVICE_CONTROL_DEVICEEVENT = 11;
        public const int SERVICE_CONTROL_SHUTDOWN = 5;

        public const uint GENERIC_READ = 0x80000000;
        public const uint OPEN_EXISTING = 3;
        public const uint FILE_SHARE_READ = 1;
        public const uint FILE_SHARE_WRITE = 2;
        public const uint FILE_SHARE_DELETE = 4;
        public const uint FILE_ATTRIBUTE_NORMAL = 128;
        public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
        public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

        public const int DBT_DEVTYP_DEVICEINTERFACE = 5;
        public const int DBT_DEVTYP_HANDLE = 6;

        public const int DBT_DEVICEARRIVAL = 0x8000;
        public const int DBT_DEVICEQUERYREMOVE = 0x8001;
        public const int DBT_DEVICEREMOVECOMPLETE = 0x8004;

        public const int WM_DEVICECHANGE = 0x219;

        public delegate int ServiceControlHandlerEx(int control, int eventType, IntPtr eventData, IntPtr context);

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern IntPtr RegisterServiceCtrlHandlerEx(string lpServiceName, ServiceControlHandlerEx cbex, IntPtr context);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetVolumePathNamesForVolumeNameW(
                [MarshalAs(UnmanagedType.LPWStr)]
                    string lpszVolumeName,
                [MarshalAs(UnmanagedType.LPWStr)]
                    string lpszVolumePathNames,
                uint cchBuferLength,
                ref UInt32 lpcchReturnLength);

        [DllImport("kernel32.dll")]
        public static extern bool GetVolumeNameForVolumeMountPoint(string
           lpszVolumeMountPoint, [Out] StringBuilder lpszVolumeName,
           uint cchBufferLength);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr RegisterDeviceNotification(IntPtr IntPtr, IntPtr NotificationFilter, Int32 Flags);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern uint UnregisterDeviceNotification(IntPtr hHandle);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr CreateFile(
              string FileName,                    // file name
              uint DesiredAccess,                 // access mode
              uint ShareMode,                     // share mode
              uint SecurityAttributes,            // Security Attributes
              uint CreationDisposition,           // how to create
              uint FlagsAndAttributes,            // file attributes
              int hTemplateFile                   // handle to template file
              );

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(IntPtr hObject);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct DEV_BROADCAST_DEVICEINTERFACE
        {
            public int dbcc_size;
            public int dbcc_devicetype;
            public int dbcc_reserved;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)]
            public byte[] dbcc_classguid;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
            public char[] dbcc_name;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct DEV_BROADCAST_HDR
        {
            public int dbcc_size;
            public int dbcc_devicetype;
            public int dbcc_reserved;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct DEV_BROADCAST_HANDLE
        {
            public int dbch_size;
            public int dbch_devicetype;
            public int dbch_reserved;
            public IntPtr dbch_handle;
            public IntPtr dbch_hdevnotify;
            public Guid dbch_eventguid;
            public long dbch_nameoffset;
            public byte dbch_data;
            public byte dbch_data1;
        }
    }
}
4

2 に答える 2

1

私は以前に同じテーマに取り組んだことがあり、最終的に行ったルートは、単純Windowにメッセージを作成して転送することでした。2013年にこの質問にコメントしたので、現在は無効になっているリンクを参照して、関連するコードをサードパーティから入手したと確信しています。

それでは、コードを見てみましょう。

まず第一に、これは完全なMessageWindow実装です:

using System;
using System.Threading;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections.Generic;

namespace Foo.Windows {
  public class MessageReceivedEventArgs : EventArgs {
    private readonly Message _message;

    public MessageReceivedEventArgs( Message message ) {
      _message = message;
    }

    public Message Message {
      get { return _message; }
    }
  }

  public static class MessageEvents {
    private static object _lock = new object();
    private static MessageWindow _window;
    private static IntPtr _windowHandle;
    private static SynchronizationContext _context;

    public static event EventHandler<MessageReceivedEventArgs> MessageReceived;

    public static void WatchMessage( int message ) {
      EnsureInitialized();
      _window.RegisterEventForMessage( message );
    }

    public static IntPtr WindowHandle {
      get {
        EnsureInitialized();
        return _windowHandle;
      }
    }

    private static void EnsureInitialized() {
      lock( _lock ) {
        if( _window == null ) {
          _context = AsyncOperationManager.SynchronizationContext;
          using( ManualResetEvent mre = new ManualResetEvent( false ) ) {
            Thread t = new Thread( (ThreadStart) delegate {
                                                   _window = new MessageWindow();
                                                   _windowHandle = _window.Handle;
                                                   mre.Set();
                                                   Application.Run();
                                                 } );
            t.Name = "MessageEvents message loop";
            t.IsBackground = true;
            t.Start();

            mre.WaitOne();
          }
        }
      }
    }

    private class MessageWindow : Form {
      private ReaderWriterLock _lock = new ReaderWriterLock();
      private Dictionary<int, bool> _messageSet = new Dictionary<int, bool>();

      public void RegisterEventForMessage( int messageID ) {
        _lock.AcquireWriterLock( Timeout.Infinite );
        _messageSet[ messageID ] = true;
        _lock.ReleaseWriterLock();
      }

      protected override void WndProc( ref Message m ) {
        _lock.AcquireReaderLock( Timeout.Infinite );
        bool handleMessage = _messageSet.ContainsKey( m.Msg );
        _lock.ReleaseReaderLock();

        if( handleMessage ) {
          MessageEvents._context.Send( delegate( object state ) {
            EventHandler<MessageReceivedEventArgs> handler = MessageEvents.MessageReceived;
            if( handler != null )
              handler( null, new MessageReceivedEventArgs( (Message)state ) );
          }, m );
        }

        base.WndProc( ref m );
      }
    }
  }
}

完全を期すために、これらはデバイス変更検出プロセスに関連する定数です。

using System;
using System.Runtime.InteropServices;

namespace Foo.Windows {
  internal class NativeMethods {
    /// <summary>
    /// Notifies an application of a change to the hardware configuration of a device or the computer.
    /// </summary>
    public static Int32 WM_DEVICECHANGE = 0x0219;

    /// <summary>
    /// The system broadcasts the DBT_DEVICEARRIVAL device event when a device or piece of media has been inserted and becomes available.
    /// </summary>
    public static Int32 DBT_DEVICEARRIVAL = 0x8000;

    /// <summary>
    /// Serves as a standard header for information related to a device event reported through the WM_DEVICECHANGE message.
    /// </summary>
    [StructLayout( LayoutKind.Sequential )]
    public struct DEV_BROADCAST_HDR {
      public Int32 dbch_size;
      public Int32 dbch_devicetype;
      public Int32 dbch_reserved;
    }

    public enum DBT_DEVTYP : uint {
      /// <summary>
      /// OEM- or IHV-defined device type.
      /// </summary>
      DBT_DEVTYP_OEM = 0x0000,

      /// <summary>
      /// Logical volume.
      /// </summary>
      DBT_DEVTYP_VOLUME = 0x0002,

      /// <summary>
      /// Port device (serial or parallel).
      /// </summary>
      DBT_DEVTYP_PORT = 0x0003,

      /// <summary>
      /// Class of devices.
      /// </summary>
      DBT_DEVTYP_DEVICEINTERFACE = 0x0005,

      /// <summary>
      /// File system handle.
      /// </summary>
      DBT_DEVTYP_HANDLE = 0x0006
    }

    /// <summary>
    /// Contains information about a OEM-defined device type.
    /// </summary>
    [StructLayout( LayoutKind.Sequential )]
    public struct DEV_BROADCAST_VOLUME {
      public Int32 dbcv_size;
      public Int32 dbcv_devicetype;
      public Int32 dbcv_reserved;
      public Int32 dbcv_unitmask;
      public Int16 dbcv_flags;
    }
  }
}

今、あなたがしなければならないのは、あなたが興味を持っているメッセージを登録し、それが起こったときにイベントを処理することです。これらは、そのプロセスに関連する部分である必要があります。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Threading;
using Foo.Windows;

namespace Foo.Core {
  class Daemon {

    private static void InternalRun() {
      MessageEvents.WatchMessage( NativeMethods.WM_DEVICECHANGE );
      MessageEvents.MessageReceived += MessageEventsMessageReceived;
    }

    private static void MessageEventsMessageReceived( object sender, MessageReceivedEventArgs e ) {
      // Check if this is a notification regarding a new device.);
      if( e.Message.WParam == (IntPtr)NativeMethods.DBT_DEVICEARRIVAL ) {
        Log.Info( "New device has arrived" );

        // Retrieve the device broadcast header
        NativeMethods.DEV_BROADCAST_HDR deviceBroadcastHeader =
          (NativeMethods.DEV_BROADCAST_HDR)
          Marshal.PtrToStructure( e.Message.LParam, typeof( NativeMethods.DEV_BROADCAST_HDR ) );

        if( (int)NativeMethods.DBT_DEVTYP.DBT_DEVTYP_VOLUME == deviceBroadcastHeader.dbch_devicetype ) {
          Log.Info( "Device type is a volume (good)." );

          NativeMethods.DEV_BROADCAST_VOLUME volumeBroadcast =
            (NativeMethods.DEV_BROADCAST_VOLUME)
            Marshal.PtrToStructure( e.Message.LParam, typeof( NativeMethods.DEV_BROADCAST_VOLUME ) );

          Log.InfoFormat( "Unit masked for new device is: {0}", volumeBroadcast.dbcv_unitmask );

          int driveIndex = 1;
          int bitCount = 1;
          while( bitCount <= 0x2000000 ) {
            driveIndex++;
            bitCount *= 2;

            if( ( bitCount & volumeBroadcast.dbcv_unitmask ) != 0 ) {
              Log.InfoFormat( "Drive index {0} is set in unit mask.", driveIndex );
              Log.InfoFormat( "Device provides drive: {0}:", (char)( driveIndex + 64 ) );

              int index = driveIndex;

              Thread spawnProcessThread = new Thread( () => SpawnDeviceProcess( string.Format( "{0}", (char)( index + 64 ) ) ) );
              spawnProcessThread.Start();
            }
          }

        } else {
          Log.InfoFormat( "Device type is {0} (ignored).", Enum.GetName( typeof( NativeMethods.DBT_DEVTYP ), deviceBroadcastHeader.dbch_devicetype ) );
        }
      }
    }
  }
}

私のプロジェクトでは、挿入されたUSBキーのドライブ文字を取得することだけに興味がありました。このコードは、そのドライブ文字を取得してから、デバイス専用のハンドラープロセスを生成します。

これはC#サービスで実装されました。System.Windows.Formsを参照する必要があります。正常に動作するはずです。

プロジェクト全体をGitHubに取り込むことはできるかもしれませんが、プロジェクトを適切にクリーンアップするには非常に時間がかかるようです。これが結果を再現するのに十分な情報であることを願っています。

于 2019-08-10T18:07:31.687 に答える
0

問題は、「優秀な」Microsoftソフトウェアエンジニアによって行われた「独創的な」.NetFramework API設計のためにOnCustomCommand()、クラスのメソッドServiceBase(理論的にはコードをオーバーライドしてサービス制御コードを処理できる)のみが渡されることです。dwControlパラメータ-受け継がれずdwEventTypelpEventDataネイティブコールバックからのパラメータ。ServiceControlHandlerEx()どちらも、、、、およびサービス制御コードを適切に処理するために必要ですSERVICE_CONTROL_DEVICEEVENTSERVICE_CONTROL_POWEREVENTSERVICE_CONTROL_SESSIONCHANGESERVICE_CONTROL_TIMECHANGE

他の回答ですでに示されている回避策は、非表示のウィンドウを作成し、それを特定の通知に登録してから、サービスコードに転送することです。ただし、これは恐ろしい問題であり、まったく不要な複雑さと追加の障害点が大量に追加されます。

Winodws API、P / Invoke、およびマーシャリングを使用して独自のサービスクラスを忘れServiceBaseて実装するのがより良いオプションですが、おそらく「最初にフレームワークを持つことのポイントは何ですか?」と自問するでしょう。

どうやって.NetFrameworkバージョン4.8に到達し、次に.Net Core / Standardに到達し、この露骨なAPI設計の監視を修正せずに、バージョン6.0に至るまで、キャリアをより遅れの少ないものに変更することを検討しているときに、反省する必要があります。ソフトウェア開発よりもストレスが少ない。

このようなAPI設計の明白な省略(そしてこれが唯一のものではない)が、ネイティブWindowsAPIと比較して.NetFramework / Core/StandardをおもちゃのAPIにしている理由です。

于 2022-02-01T16:46:53.050 に答える