QA@IT

SSDかどうかをC#から判別する方法

4697 PV

現在のドライブがSSDかどうかを判別する方法について、NyaRuRuさんの以下の記事を拝見して、自分の勉強を兼ねて同じことをC#からできないかと考えています。

SSDなら動作を変えるアプリケーションを作る

Win32についてはほとんど素人ながらHasNoSeekPenalty関数の部分を書いてみたのですが、実行したところ、DeviceIoControlで(正確にはMarshal.ThrowExceptionForHRの行)で以下のエラーが出ます。

{"プログラムはコマンドを発行しましたが、コマンドの長さが間違っています。 (HRESULT からの例外: 0x80070018)"}
System.Exception {System.Runtime.InteropServices.COMException}

自力ではかなり詰まってしまったのですが、よろしければアドバイスをいただけないでしょうか。なお、Visual Studio 2012で対象のフレームワークを.NET Framework 4.0として作成し、Windows 8 Pro上で実行しています。

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

namespace DetectSsd
{
    class Program
    {
        private const uint FILE_SHARE_READ = 1;
        private const uint FILE_SHARE_WRITE = 2;
        private const uint OPEN_EXISTING = 3;
        private const uint FILE_ATTRIBUTE_NORMAL = 0x80;

        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern SafeFileHandle CreateFileW(
            [MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            IntPtr lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile);

        private const uint FILE_DEVICE_MASS_STORAGE = 0x2d;
        private const uint IOCTL_STORAGE_BASE = FILE_DEVICE_MASS_STORAGE;
        private const uint METHOD_BUFFERED = 0;
        private const uint FILE_ANY_ACCESS = 0;

        public static uint CTL_CODE(uint DeviceType, uint Function, uint Method, uint Access)
        {
            return ((DeviceType << 16) | (Access << 14) | (Function << 2) | Method);
        }

        private const uint StorageDeviceSeekPenaltyProperty = 7;
        private const uint PropertyStandardQuery = 0;

        [StructLayout(LayoutKind.Sequential)]
        private struct STORAGE_PROPERTY_QUERY
        {
            public uint PropertyId;
            public uint QueryType;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct DEVICE_SEEK_PENALTY_DESCRIPTOR
        {
            public uint Version;
            public uint Size;
            public bool IncursSeekPenalty;
        }

        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeviceIoControl(
            SafeFileHandle hDevice,
            uint dwIoControlCode,
            ref STORAGE_PROPERTY_QUERY lpInBuffer,
            uint nInBufferSize,
            ref DEVICE_SEEK_PENALTY_DESCRIPTOR lpOutBuffer,
            uint nOutBufferSize,
            ref uint lpBytesReturned,
            IntPtr lpOverlapped);

        [DllImport("kernel32", SetLastError = true)]
        private static extern bool CloseHandle(SafeFileHandle hDevice);

        static void Main(string[] args)
        {
            HasNoSeekPenalty("\\\\.\\PhysicalDrive0");
            Console.ReadLine();
        }

        private static void HasNoSeekPenalty(string sDrive)
        {
            SafeFileHandle hDrive = CreateFileW(
                sDrive,
                0,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                IntPtr.Zero);

            if (hDrive == null || hDrive.IsInvalid)
            {
                Console.WriteLine("CreateFile failed.");
            }
            else
            {
                Console.WriteLine("CreateFile suceeded.");
            };

            uint IOCTL_STORAGE_QUERY_PROPERTY = CTL_CODE(IOCTL_STORAGE_BASE, 0x500,
                METHOD_BUFFERED, FILE_ANY_ACCESS);

            STORAGE_PROPERTY_QUERY query_seek_penalty = new STORAGE_PROPERTY_QUERY()
            {
                PropertyId = StorageDeviceSeekPenaltyProperty,
                QueryType = PropertyStandardQuery
            };

            DEVICE_SEEK_PENALTY_DESCRIPTOR query_seek_penalty_desc = new DEVICE_SEEK_PENALTY_DESCRIPTOR();

            uint returned_query_seek_penalty_size = 0;

            bool query_seek_penalty_result = DeviceIoControl(
                hDrive,
                IOCTL_STORAGE_QUERY_PROPERTY,
                ref query_seek_penalty,
                (uint)Marshal.SizeOf(query_seek_penalty),
                ref query_seek_penalty_desc,
                (uint)Marshal.SizeOf(query_seek_penalty_desc),
                ref returned_query_seek_penalty_size,
                IntPtr.Zero);

            CloseHandle(hDrive);

            if (query_seek_penalty_result == false)
            {
                Console.WriteLine("DeviceIoControl failed.");
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
            else
            {
                if (query_seek_penalty_desc.IncursSeekPenalty == false)
                {
                    Console.WriteLine("No seek penalty exists.");
                }
                else
                {
                    Console.WriteLine("Seek penalty exists.");
                };
            };
        }
    }
}

回答

直接アドバイスをいただいて解決しました。

修正点は2箇所あり、1つ目は、DEVICE_SEEK_PENALTY_DESCRIPTORのIncursSeekPenaltyはWin32ではBOOLEANなので、C#のboolからの変換のために[MarshalAs(UnmanagedType.U1)]を付ける。

2つ目は、STORAGE_PROPERTY_QUERYのメンバにAdditionalParametersが抜けていたので、これを[MarshalAs(UnmanagedType.ByValArray)] public byte[] AdditionalParametersとして追加する。

修正後のコードは以下のようになります。これで手元のSSDとHDDでテストして、判別できることを確認しました。

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

namespace DetectSsd
{
    class Program
    {
        private const uint FILE_SHARE_READ = 1;
        private const uint FILE_SHARE_WRITE = 2;
        private const uint OPEN_EXISTING = 3;
        private const uint FILE_ATTRIBUTE_NORMAL = 0x80;

        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern SafeFileHandle CreateFileW(
            [MarshalAs(UnmanagedType.LPWStr)]
            string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            IntPtr lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile);

        private const uint FILE_DEVICE_MASS_STORAGE = 0x2d;
        private const uint IOCTL_STORAGE_BASE = FILE_DEVICE_MASS_STORAGE;
        private const uint METHOD_BUFFERED = 0;
        private const uint FILE_ANY_ACCESS = 0;

        public static uint CTL_CODE(uint DeviceType, uint Function, 
            uint Method, uint Access)
        {
            return ((DeviceType << 16) | (Access << 14) | (Function << 2) | Method);
        }

        private const uint StorageDeviceSeekPenaltyProperty = 7;
        private const uint PropertyStandardQuery = 0;

        [StructLayout(LayoutKind.Sequential)]
        private struct STORAGE_PROPERTY_QUERY
        {
            public uint PropertyId;
            public uint QueryType;
            [MarshalAs(UnmanagedType.ByValArray)]
            public byte[] AdditionalParameters;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct DEVICE_SEEK_PENALTY_DESCRIPTOR
        {
            public uint Version;
            public uint Size;
            [MarshalAs(UnmanagedType.U1)]
            public bool IncursSeekPenalty;
        }

        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeviceIoControl(
            SafeFileHandle hDevice,
            uint dwIoControlCode,
            ref STORAGE_PROPERTY_QUERY lpInBuffer,
            uint nInBufferSize,
            ref DEVICE_SEEK_PENALTY_DESCRIPTOR lpOutBuffer,
            uint nOutBufferSize,
            ref uint lpBytesReturned,
            IntPtr lpOverlapped);

        [DllImport("kernel32", SetLastError = true)]
        private static extern bool CloseHandle(SafeFileHandle hDevice);

        static void Main(string[] args)
        {
            HasNoSeekPenalty("\\\\.\\PhysicalDrive0");
            Console.ReadLine();
        }

        private static void HasNoSeekPenalty(string sDrive)
        {
            SafeFileHandle hDrive = CreateFileW(
                sDrive,
                0,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                IntPtr.Zero);

            if (hDrive == null || hDrive.IsInvalid)
            {
                Console.WriteLine("CreateFile failed.");
            }
            else
            {
                Console.WriteLine("CreateFile suceeded.");
            };

            uint IOCTL_STORAGE_QUERY_PROPERTY = CTL_CODE(IOCTL_STORAGE_BASE, 0x500,
                METHOD_BUFFERED, FILE_ANY_ACCESS);

            STORAGE_PROPERTY_QUERY query_seek_penalty = new STORAGE_PROPERTY_QUERY()
            {
                PropertyId = StorageDeviceSeekPenaltyProperty,
                QueryType = PropertyStandardQuery
            };

            DEVICE_SEEK_PENALTY_DESCRIPTOR query_seek_penalty_desc = 
                new DEVICE_SEEK_PENALTY_DESCRIPTOR();

            uint returned_query_seek_penalty_size = 0;

            bool query_seek_penalty_result = DeviceIoControl(
                hDrive,
                IOCTL_STORAGE_QUERY_PROPERTY,
                ref query_seek_penalty,
                (uint)Marshal.SizeOf(query_seek_penalty),
                ref query_seek_penalty_desc,
                (uint)Marshal.SizeOf(query_seek_penalty_desc),
                ref returned_query_seek_penalty_size,
                IntPtr.Zero);

            CloseHandle(hDrive);

            if (query_seek_penalty_result == false)
            {
                Console.WriteLine("DeviceIoControl failed.");
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
            else
            {
                if (query_seek_penalty_desc.IncursSeekPenalty == false)
                {
                    Console.WriteLine("No seek penalty exists.");
                }
                else
                {
                    Console.WriteLine("Seek penalty exists.");
                };
            };
        }
    }
}
編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。