I'd like to make a Win32 SCSI Passthrough Interface call from C# and am struggling to set up the proper declarations to make the call.
I am successful in coding a simple console program in C++, calling the DeviceIoControl API and commanding a SCSI tape drive.
Here's what my C# code looks like:
First, I define my SCSI Passthrough classes/structures in my Program.cs file:
[StructLayout(LayoutKind.Sequential, Pack=1)]
public class ScsiPassThrough
{
public ushort Length;
public byte ScsiStatus;
public byte PathId;
public byte TargetId;
public byte Lun;
public byte CdbLength;
public byte SenseInfoLength;
public byte DataIn;
public UInt32 DataTransferLength;
public UInt32 TimeOutValue;
public IntPtr DataBufferOffset; // UInt32 does not work on 64-bit
public UInt32 SenseInfoOffset;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] Cdb;
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 = 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};
}
}
[StructLayout(LayoutKind.Sequential, Pack=1)]
public class ScsiPassThroughWithBuffers
{
public ScsiPassThrough spt;
public UInt32 Filler;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public byte[] ucSenseBuf;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
public byte[] ucDataBuf;
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];
}
}
Then I create a couple classes (also in Program.cs) to make the DeviceIoControl and CreateFile calls. These classes are peers to Program:
public class DevIo
{
[DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
public static extern bool DeviceIoControl(
IntPtr hDevice,
uint IoControlCode,
[MarshalAs(UnmanagedType.AsAny)]
[In] object InBuffer, // should this be byte[]? but how to pass sptwb
uint nInBufferSize,
[MarshalAs(UnmanagedType.LPArray)]
byte[] OutBuffer,
uint nOutBufferSize,
ref uint pBytesReturned,
IntPtr Overlapped
);
}
public class FileIo
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes, IntPtr templateFile);
}
Finally, in my form (I only have one form right now), I have this code in my button click:
{
// rewind button
// get file handle
IntPtr x = FileIo.CreateFile("\\\\.\\Scsi6:",
System.IO.FileAccess.ReadWrite,
System.IO.FileShare.ReadWrite,
IntPtr.Zero,
System.IO.FileMode.OpenOrCreate,
0,
IntPtr.Zero);
if (x.ToInt32() == -1)
{
/* ask the framework to marshall the win32 error code to an exception */
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
//
uint BytesReturned = 0;
ScsiPassThroughWithBuffers sptwb = new ScsiPassThroughWithBuffers(0, 0, 0, 16, 512);
sptwb.ucDataBuf.Initialize(); //ZeroMemory(&ucDataBuf, sizeof(ucDataBuf));
sptwb.spt.PathId = Convert.ToByte(maskedTextBox1.Text);
sptwb.spt.TargetId = Convert.ToByte(maskedTextBox2.Text);
sptwb.spt.Lun = Convert.ToByte(maskedTextBox3.Text);
sptwb.spt.Length = Convert.ToUInt16(Marshal.SizeOf(typeof(ScsiPassThroughWithBuffers))); //sptdwb.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
sptwb.spt.CdbLength = 16;
sptwb.spt.SenseInfoLength = 32;
sptwb.spt.DataTransferLength = 512;
sptwb.spt.TimeOutValue = 60; // seconds
sptwb.spt.DataIn = 0x00; // sptwb.spt.DataIn = SCSI_IOCTL_DATA_OUT;
sptwb.spt.Cdb[0] = 0x01; // sptwb.spt.Cdb[0] = SCSIOP_REWIND;
sptwb.spt.DataBufferOffset = Marshal.OffsetOf(typeof(ScsiPassThroughWithBuffers), "ucDataBuf");
sptwb.spt.SenseInfoOffset = (uint) Marshal.OffsetOf(typeof(ScsiPassThroughWithBuffers), "ucSenseBuf");
// DevIo call
//
// need to confirm the IOCTL code for IOCTL_SCSI_PASS_THROUGH_DIRECT
//
bool ok = DevIo.DeviceIoControl(x, 0x10540003,
sptwb.spt,
(uint) Marshal.SizeOf(typeof(ScsiPassThrough)),
sptwb.ucDataBuf,
512,
ref BytesReturned,
IntPtr.Zero);
}
My result is that the DeviceIoControl call returns false and the BytesReturned value is 0, whereas when I make the same call in my C++ program, the BytesReturned value is 0x2C.
I'm just not a C# programmer and can't find a good reference to teach me marshalling, pinvoke, and interop services!
The closest example appears to be here:
C#/Native: Reading HDD Serial Using SCSI PassThrough
but, it's missing the declarations of CreateFile and DeviceIoControl....
HELP!