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