0

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!

Community
  • 1
  • 1
MattCarp
  • 21
  • 4
  • Force your .NET app to build only as a 32bit app. Not as an "Any" or 64 bit app. This can help get rid of a few issues to help you on your way. You are a brave person. – Sql Surfer Apr 10 '16 at 15:25
  • Thanks for the quick response. I'm only using VS Express, so I only have the choice of "Any", which I believe is 64-bit? This may explain some of my problem, but I'm not sure - I'd really prefer someone to share a declaration and call that actually works. When I wrote the code, I was just picking things from pinvoke.net and stackoverflow, not a full example. – MattCarp Apr 11 '16 at 23:42

0 Answers0