C# .NET - DeviceIoControl return incorrect parameter

Asked By Scott Grosch on 07-Jul-04 01:08 PM
I'm trying to use DeviceIoControl to talk though a SCSI device.  I'm getting an error 87 back though, which is 'The parameter is incorrect.'  I'm not sure if it's telling me the call to DeviceIoControl() itself is wrong, or if it's the device on the other end telling me the SCSI command I sent is incorrect.  
If it's the DeviceIoControl() itself, I have no idea what I'm doing wrong, and hope someone here can help.  
I'm importing the routine like this, based on posts I've found on the net:
		[DllImport("kernel32.dll", ExactSpelling=true, SetLastError=true, CharSet=CharSet.Auto) ]
		internal static extern bool DeviceIoControl(
			IntPtr hDevice, int dwIoControlCode, 
			IntPtr lpInBuffer, int nInBufferSize,
			IntPtr lpOutBuffer, int nOutBufferSize,
			out int lpBytesReturned, IntPtr lpOverlapped );
And then I'm calling like this:
			byte outputSize = (byte) (Marshal.SizeOf(typeof(byte)) * 8);
			byte[] inputBuffer = new byte[] {
												0xB8,			// Operation code
												(1 << 4) | 3,	// Show vol tags and use import slots
												0,				// Starting element address MSB
												1,				// Starting element address LSB
												0,				// Number of Elements MSB
												1,				// Number of elements LSB
												0,				// reserved
												0,				// allocation length MSB
												0,				// allocation length middle part
												outputSize,		// allocation length LSB
												0,				// reserved
												0				// reserved
											};
			int inputSize = Marshal.SizeOf(typeof(byte)) * inputBuffer.Length;
			IntPtr input = Marshal.AllocHGlobal(inputSize);
			Marshal.Copy(inputBuffer, 0, input, inputSize);
			IntPtr output = Marshal.AllocHGlobal(outputSize);
			int bytesReturned = 0;
			bool ok = false;
			try {
				ok = DeviceIoControl(handle, IOCTL_SCSI_PASS_THROUGH,
					input, inputSize,
					output, outputSize,
					out bytesReturned,
					IntPtr.Zero);
			} catch (Exception ex) {
				Console.WriteLine(ex.Message);
				Marshal.FreeHGlobal(input);
				Marshal.FreeHGlobal(output);
				input = IntPtr.Zero;
				output = IntPtr.Zero;
			}

DeviceIoControl return incorrect parameter - Asked By John Atwood on 12-Oct-05 06:20 AM

I'm trying to communicate with a device as well....
Is SCSI Pass Through preferred over ASPI? Some posts elsewhere indicate that SPTI requires admin privileges?
How are you declaring handle?  I'd think it's a FileStream, but DeviceIOControl wants an IntPtr.  Do I have to do this in unsafe code?
And finally (for now), where is IOCTL_SCSI_PASS_THROUGH def'ed?
Thanks,

Here's some code - Asked By Scott Grosch on 12-Oct-05 12:46 PM

I can't answer which is better, or if it requires admin or not.  My project runs as a local service as admin, so I'm not sure what the security implications are.
               [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool DeviceIoControl(
            IntPtr hDevice, uint dwIoControlCode,
            IntPtr lpInBuffer, uint nInBufferSize,
            IntPtr lpOutBuffer, uint nOutBufferSize,
            out uint lpBytesReturned, IntPtr lpOverlapped);
You get the device handle via CreateFile(), which you have to P/Invoke as well.  
        /// <summary>
        /// This is the value that is defined in the Platform SDK for this variable
        /// </summary>
        private const int IOCTL_SCSI_PASS_THROUGH = 0x4D004;
	[StructLayout(LayoutKind.Sequential)]
	public class ScsiPassThrough {
		/// <summary>The size of the item passed to DeviceIoControl</summary>
		public UInt16 Length;
		/// <summary>The status of the SCSI command</summary>
		public byte ScsiStatus;
		/// <summary>The SCSI bus</summary>
		public byte PathId;
		/// <summary>The SCSI target</summary>
		public byte TargetId;
		/// <summary>The SCSI lun</summary>
		public byte Lun;
		/// <summary>The number of bytes in the CDB field</summary>
		public byte CdbLength;
		/// <summary>How long the sense info structure is</summary>
		public byte SenseInfoLength;
		/// <summary>Always set to SCSI_IOCTL_DATA_IN</summary>
		public byte DataIn;
		/// <summary>How many bytes to transfer</summary>
		public UInt32 DataTransferLength;
		/// <summary>How long to wait before timing out</summary>
		public UInt32 TimeOutValue;
		/// <summary>The offset of the data buffer area returned</summary>
		public UInt32 DataBufferOffset;
		/// <summary>The offset of the sense info returned</summary>
		public UInt32 SenseInfoOffset;
		/// <summary>The command code to send to the robot</summary>
		[MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
		public byte[] Cdb;
		/// <summary>
		/// Constructor for the command to send to the robot
		/// </summary>
		/// <param name="target">The SCSI target</param>
		/// <param name="bus">The SCSI bus</param>
		/// <param name="lun">The SCSI lun</param>
		/// <param name="cdbLen">The length of the CDB to be sent</param>
		/// <param name="dataTransferLength">The length to use</param>
		public ScsiPassThrough(byte target, byte bus, byte lun, byte cdbLen, UInt32 dataTransferLength) {
			Length = (ushort) Marshal.SizeOf(typeof(ScsiPassThrough));
			ScsiStatus = 0;
			PathId = bus;
			TargetId = target;
			Lun = lun;
			CdbLength = cdbLen;
			SenseInfoLength = 24;
			DataIn = 1;	// SCSI_IOCTL_DATA_IN
			DataTransferLength = dataTransferLength;
			TimeOutValue = 200;
			DataBufferOffset = (uint) Marshal.OffsetOf(typeof(ScsiPassThroughWithBuffers), "ucDataBuf");
			SenseInfoOffset = (uint) Marshal.OffsetOf(typeof(ScsiPassThroughWithBuffers), "ucSenseBuf");
			Cdb = new byte[16] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
		}
	}
	/// <summary>
	/// Wrapper for the ScsiPassThrough command
	/// </summary>
	[StructLayout(LayoutKind.Sequential)]
	public class ScsiPassThroughWithBuffers {
		/// <summary>The SPT we're wrapping</summary>
		public ScsiPassThrough spt;
		/// <summary>Used to align the size of the structure</summary>
		public UInt32 Filler;
		/// <summary>The buffer to write sense info into</summary>
		[MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]
		public byte[] ucSenseBuf;
		/// <summary>The buffer to write output data into</summary>
		[MarshalAs(UnmanagedType.ByValArray, SizeConst=512)]
		public byte[] ucDataBuf;
		/// <summary>
		/// Constructor for the command to send to the robot
		/// </summary>
		/// <param name="target">The SCSI target</param>
		/// <param name="bus">The SCSI bus</param>
		/// <param name="lun">The SCSI lun</param>
		/// <param name="cdbLen">The length of the CDB to be sent</param>
		/// <param name="dataTransferLength">The length to use</param>
		public ScsiPassThroughWithBuffers(byte target, byte bus, byte lun, byte cdbLen, UInt32 dataTransferLength) {
			spt = new ScsiPassThrough(target, bus, lun, cdbLen, dataTransferLength);
			Filler = 0;
			ucSenseBuf = new byte[32];
			ucDataBuf = new byte[512];
		}
	}
Here's an example of calling it
        private bool ioctl(ref ScsiPassThroughWithBuffers sptwb, uint outputSize, out byte[] buffer, out string SenseData) {
            bool ok = false;
            uint inputSize = (uint) Marshal.SizeOf(typeof(ScsiPassThroughWithBuffers));
            outputSize += inputSize - 512;
            IntPtr input = IntPtr.Zero, output = IntPtr.Zero;
            try {
                input = Marshal.AllocHGlobal((int) inputSize);
                Marshal.StructureToPtr(sptwb, input, true);
                output = Marshal.AllocHGlobal((int) outputSize);
                SenseData = null;
                buffer = null;
                uint returned = 0;
                Trace.WriteLine("Opening robot", "ioctl");
                // You'll use an IntPtr here.  SafeRobotHandle is my wrapper
                // around the IntPtr returned from CreateFile()
                using (SafeRobotHandle handle = SafeRobotHandle.Create(this.robotDescription.Path)) {
                    if ((ok = DeviceIoControl(handle, IOCTL_SCSI_PASS_THROUGH, input, inputSize, output, outputSize, out returned, IntPtr.Zero)) == true) {
                        Marshal.PtrToStructure(output, sptwb);
                        buffer = new byte[returned];
                        Marshal.Copy(output, buffer, 0, (int) returned);
                    } else {
                        int err = Marshal.GetLastWin32Error();
                        throw new Win32Exception(err);
                    }
                }
            } finally {
                Marshal.FreeHGlobal(input);
                Marshal.FreeHGlobal(output);
            }
            return ok;
        }