【问题标题】:How to use IOCTL_SCSI_MINIPORT via DeviceIoControl from C#.net?如何通过 C#.net 中的 DeviceIoControl 使用 IOCTL_SCSI_MINIPORT?
【发布时间】:2013-10-22 14:38:57
【问题描述】:

我的任务是实施一个可靠的解决方案来检索硬盘驱动器的序列号。不幸的是,the WMI method 根本不可靠。所以我正在寻找另一种解决方案。

我找到了software 中的这个小piece,它正是我想在C#.net 中实现的。幸运的是,源代码也是available

基本上我想在 C# 中实现来自 diskid32 的函数 ReadIdeDriveAsScsiDriveInNT

我如何与设备通信:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool DeviceIoControl(
    SafeFileHandle device, 
    int inputOutputControlCode, 
    [In] ref sbyte[] inputBuffer, 
    int inputBufferSize,
    [In] [Out] ref sbyte[] outputBuffer, 
    int outputBufferSize, 
    ref uint bytesCount, 
    int overlapped);

public static string GetSerialNumberUsingMiniportDriver(int deviceNumber)
{
    using (var device = OpenScsi(2))
    {
        var bytesReturned = default(uint);
        var sio = new ScsiRequestBlockInputOutputControl();
        var sop = new SendCommandOutParameters();
        var sip = new SendCommandInParameters();
        var buffer = new byte[Marshal.SizeOf(sio) + Marshal.SizeOf(sop) + IdentifyBufferSize];

        sio.HeaderLength = Marshal.SizeOf(sio);
        sio.Timeout = 10000;
        sio.Length = Marshal.SizeOf(sop) + IdentifyBufferSize;
        sio.ControlCode = InputOutputControlSCSIMiniportIdentify;
        sio.Signature = Encoding.ASCII.GetBytes("SCSIDISK".ToCharArray());

        var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sio));
        Marshal.StructureToPtr(sio, ptr, true);
        Marshal.Copy(ptr, buffer, 0, Marshal.SizeOf(sio));

        sip.DriveRegister.CommandRegister = IDEATAIdentify;
        sip.DriveNumber = 0;

        ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sip));
        Marshal.StructureToPtr(sip, ptr, true);
        Marshal.Copy(ptr, buffer, Marshal.SizeOf(sio), Marshal.SizeOf(sip));

        var signedBuffer = new sbyte[buffer.Length];
        Buffer.BlockCopy(buffer, 0, signedBuffer, 0, buffer.Length);

        if (
            !DeviceIoControl(
                device, 
                InputOutputControlSCSIMiniport,
                ref signedBuffer, 
                Marshal.SizeOf(sio) + Marshal.SizeOf(sip) - 1,
                ref signedBuffer, 
                Marshal.SizeOf(sio) + Marshal.SizeOf(sop) + IdentifyBufferSize, 
                ref bytesReturned, 
                0))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        var result = new StringBuilder();

        result.Append(buffer);

        return result.ToString();
    }
}

我如何创建句柄:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern SafeFileHandle CreateFile(
    string fileName, 
    int desiredAccess, 
    FileShare shareMode, 
    IntPtr securityAttributes, 
    FileMode creationDisposition, 
    FileAttributes flagsAndAttributes, 
    IntPtr templateFile);

private static SafeFileHandle OpenScsi(int scsiNumber)
{
    var device = CreateFile(string.Format(@"\\.\Scsi{0}:", scsiNumber), 0, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
    if (device.IsInvalid)
    {
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    return device;
}

signedBuffer 包含与 diskid32 示例中的缓冲区完全相同的字节! Diskid32 返回那个句柄 \\.\Scsi2:DriveNumber = 0 一个结果,所以我使用相同的参数。

创建句柄时有所不同。我也尝试过 diskid32 中的内容。没有任何成功。

当我在 C# 中调用 DeviceIoControl 时,我总是得到一个 Win32Exception,即 Access denied。有人有想法吗?

【问题讨论】:

    标签: c# c++ pinvoke deviceiocontrol


    【解决方案1】:

    主要问题是我以错误的权限访问了设备,并且其中一个结构的布局错误。

    首先我下载了​​WinDDK 以了解这些结构的真实外观。我将使用过的结构翻译成C#

    这里有一个来自WinDDK\7600.16385.1\inc\api\ntddscsi.h的例子:

    /// <summary>
    /// The SRB_IO_CONTROL.
    /// Define header for I/O control SRB.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    internal struct SRB_IO_CONTROL
    {
        /// <summary>
        /// The HeaderLength.
        /// </summary>
        public uint HeaderLength;
    
        /// <summary>
        /// The Signature.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] Signature;
    
        /// <summary>
        /// The Timeout.
        /// </summary>
        public uint Timeout;
    
        /// <summary>
        /// The ControlCode.
        /// </summary>
        public uint ControlCode;
    
        /// <summary>
        /// The ReturnCode.
        /// </summary>
        public uint ReturnCode;
    
        /// <summary>
        /// The Length.
        /// </summary>
        public uint Length;
    }
    

    我已将访问设备的权限值更改为0x80000000 | 0x40000000。这是我打开设备句柄的方法:

    /// <summary>
    /// The open scsi.
    /// </summary>
    /// <param name="scsiNumber">
    /// The scsi number.
    /// </param>
    /// <returns>
    /// The <see cref="SafeFileHandle"/>.
    /// </returns>
    /// <exception cref="Win32Exception">
    /// Will be thrown, when the safe file handle is not valid.
    /// </exception>
    private static SafeFileHandle OpenScsi(int scsiNumber)
    {
        var device = FileManagement.CreateFile(
            string.Format(@"\\.\Scsi{0}:", scsiNumber), 
            WinNT.GENERIC_READ | WinNT.GENERIC_WRITE, 
            FileShare.ReadWrite, 
            IntPtr.Zero, 
            FileMode.Open, 
            0, 
            IntPtr.Zero);
        if (device.IsInvalid)
        {
            throw new NativeException(string.Format(@"Error during the creation of a safe file handle for \\.\Scsi{0}:", scsiNumber));
        }
    
        return device;
    }
    

    最后,我的代码如何收集设备序列号:

    /// <summary>
    /// The get serial number using miniport driver.
    /// </summary>
    /// <param name="busNumber">
    /// The bus number.
    /// </param>
    /// <param name="deviceNumber">
    /// The device number.
    /// </param>
    /// <returns>
    /// The <see cref="string"/>.
    /// </returns>
    /// <exception cref="NativeException">
    /// Throws an excpetion, if the device io control couldn't execute successfully!
    /// </exception>
    internal static string GetSerialNumberUsingMiniportDriver(int busNumber, byte deviceNumber = 0)
    {
        using (var device = OpenScsi(busNumber))
        {
            var bytesReturned = default(uint);
            var sic = new SCSI.SRB_IO_CONTROL();
            var sop = new Disk.SENDCMDOUTPARAMS();
            var sip = new Disk.SENDCMDINPARAMS();
            var id = new ATA.IDENTIFY_DEVICE_DATA();
            var buffer = new byte[Marshal.SizeOf(sic) + Marshal.SizeOf(sop) + Marshal.SizeOf(id)];
    
            sic.HeaderLength = (uint)Marshal.SizeOf(sic);
            sic.Timeout = 10000;
            sic.Length = (uint)(Marshal.SizeOf(sop) + Marshal.SizeOf(id));
            sic.ControlCode = SCSI.IOCTL_SCSI_MINIPORT_IDENTIFY;
    
            // disk access signature
            sic.Signature = Encoding.ASCII.GetBytes("SCSIDISK".ToCharArray());
    
            var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sic));
            Marshal.StructureToPtr(sic, ptr, true);
            Marshal.Copy(ptr, buffer, 0, Marshal.SizeOf(sic));
    
            sip.irDriveRegs.bCommandReg = (byte)ATA.CTRL_CMDS.ATA_IDENTIFY;
            sip.bDriveNumber = deviceNumber;
    
            ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sip));
            Marshal.StructureToPtr(sip, ptr, true);
            Marshal.Copy(ptr, buffer, Marshal.SizeOf(sic), Marshal.SizeOf(sip));
    
            if (
                !FileManagement.DeviceIoControl(
                    device, 
                    SCSI.IOCTL_SCSI_MINIPORT, 
                    buffer, 
                    (uint)(Marshal.SizeOf(sic) + Marshal.SizeOf(sip) - 1), 
                    buffer, 
                    (uint)(Marshal.SizeOf(sic) + Marshal.SizeOf(sop) + Marshal.SizeOf(id)), 
                    ref bytesReturned, 
                    IntPtr.Zero))
            {
                throw new NativeException("P/invoke error on SCSI MINIPORT IDENTIFY");
            }
    
            var resultPtr = Marshal.AllocHGlobal(Marshal.SizeOf(id));
            Marshal.Copy(buffer, Marshal.SizeOf(sic) + Marshal.SizeOf(sop), resultPtr, Marshal.SizeOf(id));
    
            id = (ATA.IDENTIFY_DEVICE_DATA)Marshal.PtrToStructure(resultPtr, typeof(ATA.IDENTIFY_DEVICE_DATA));
    
            var model = Encoding.ASCII.GetString(id.ModelNumber).Replace('\0', ' ').Trim();
            if (!string.IsNullOrEmpty(model))
            {
                model = model.Swap();
    
                Logging.Add(
                    Message.Type.INFO, 
                    string.Format("Found model definition (via IOCTL_SCSI_MINIPORT) for storage device #{0}/{1}: {2}", busNumber, deviceNumber, model));
            }
    
            var serial = Encoding.ASCII.GetString(id.SerialNumber).Replace('\0', ' ').Trim();
            if (!string.IsNullOrEmpty(serial))
            {
                serial = serial.Swap();
    
                Logging.Add(
                    Message.Type.INFO, 
                    string.Format("Found serial number (via IOCTL_SCSI_MINIPORT) for storage device #{0}/{1}: {2}", busNumber, deviceNumber, serial));
            }
            else
            {
                Logging.Add(
                    Message.Type.INFO, 
                    string.Format("Couldn't find serial number (via IOCTL_SCSI_MINIPORT) for storage device #{0}/{1}", busNumber, deviceNumber));
            }
    
            return serial.Trim();
        }
    }
    

    如果您想使用代码,您需要执行以下操作:

    • 实现结构(您可以在 WinDDK 中找到它们):SENDCMDOUTPARAMSSENDCMDINPARAMSIDENTIFY_DEVICE_DATASRB_IO_CONTROL

    • 定义 const 值:IOCTL_SCSI_MINIPORTIOCTL_SCSI_MINIPORT_IDENTIFYATA_IDENTIFY

    • 我已经实现了一个 string 扩展来交换字符,它被使用了。

    我的字符串扩展交换:

    /// <summary>
    /// The swap.
    /// </summary>
    /// <param name="input">
    /// The input.
    /// </param>
    /// <returns>
    /// The <see cref="string"/>.
    /// </returns>
    /// <exception cref="ArgumentException">
    /// Can't swap input, which is null, empty or a white space!
    /// </exception>
    public static string Swap(this string input)
    {
        if (string.IsNullOrEmpty(input) || string.IsNullOrWhiteSpace(input))
        {
            throw new ArgumentException("Can't swap input, which is null, empty or a white space!");
        }
    
        var characters = input.ToCharArray();
        var returnValue = new StringBuilder();
    
        for (var i = 0; i < characters.Length; i++)
        {
            if (i % 2 != 0)
            {
                continue;
            }
    
            if ((i + 1) < characters.Length)
            {
                returnValue.Append(characters[i + 1]);
            }
    
            returnValue.Append(characters[i]);
        }
    
        return returnValue.ToString();
    }
    

    【讨论】:

    • 看起来很棒@Mehrdad。我会调查一下。谢谢! :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-24
    • 2012-04-03
    • 1970-01-01
    相关资源
    最近更新 更多