PInvoke DeviceIOControl для чтения ISRC не возвращает данных

Я использую пример класса Win32Functions от Idael Cardoso для взаимодействия с CD-Drive. Я хочу прочитать код ISRC компакт-диска в трее.

Сначала немного кода — это класс Win32Functions — я добавил, надеюсь, правильный код для чтения Q_CHANNEL компакт-диска, некоторые структуры C++, найденные здесь (http://msdn.microsoft.com/en-us/library/windows/hardware/ff567601%28v=vs.85%29.aspx) и попытался преобразовать их в C#. Наконец, я добавил вызов DeviceIOControl для чтения ISRC-кода трека:

    namespace MyISRCReader
    {
  /// <summary>
  /// Wrapper class for Win32 functions and structures needed to handle CD.
  /// </summary>
  internal class Win32Functions
  {   
    public enum DriveTypes:uint 
    {
      DRIVE_UNKNOWN = 0,
      DRIVE_NO_ROOT_DIR,
      DRIVE_REMOVABLE,
      DRIVE_FIXED,
      DRIVE_REMOTE,
      DRIVE_CDROM,
      DRIVE_RAMDISK
    };

    [System.Runtime.InteropServices.DllImport("Kernel32.dll")]
    public extern static DriveTypes GetDriveType(string drive);

    //DesiredAccess values
    public const uint GENERIC_READ      = 0x80000000;
    public const uint GENERIC_WRITE     = 0x40000000;
    public const uint GENERIC_EXECUTE   = 0x20000000;
    public const uint GENERIC_ALL       = 0x10000000;

    //Share constants
    public const uint FILE_SHARE_READ   = 0x00000001;  
    public const uint FILE_SHARE_WRITE  = 0x00000002;  
    public const uint FILE_SHARE_DELETE = 0x00000004;  

    //CreationDisposition constants
    public const uint CREATE_NEW        = 1;
    public const uint CREATE_ALWAYS     = 2;
    public const uint OPEN_EXISTING     = 3;
    public const uint OPEN_ALWAYS       = 4;
    public const uint TRUNCATE_EXISTING = 5;

    /// <summary>
    /// Win32 CreateFile function, look for complete information at Platform SDK
    /// </summary>
    /// <param name="FileName">In order to read CD data FileName must be "\\.\\D:" where D is the CDROM drive letter</param>
    /// <param name="DesiredAccess">Must be GENERIC_READ for CDROMs others access flags are not important in this case</param>
    /// <param name="ShareMode">O means exlusive access, FILE_SHARE_READ allow open the CDROM</param>
    /// <param name="lpSecurityAttributes">See Platform SDK documentation for details. NULL pointer could be enough</param>
    /// <param name="CreationDisposition">Must be OPEN_EXISTING for CDROM drives</param>
    /// <param name="dwFlagsAndAttributes">0 in fine for this case</param>
    /// <param name="hTemplateFile">NULL handle in this case</param>
    /// <returns>INVALID_HANDLE_VALUE on error or the handle to file if success</returns>
    [System.Runtime.InteropServices.DllImport("Kernel32.dll", SetLastError=true)]
    public extern static IntPtr CreateFile(string FileName, uint DesiredAccess, 
      uint ShareMode, IntPtr lpSecurityAttributes, 
      uint CreationDisposition, uint dwFlagsAndAttributes,
      IntPtr hTemplateFile);

    /// <summary>
    /// The CloseHandle function closes an open object handle.
    /// </summary>
    /// <param name="hObject">Handle to an open object.</param>
    /// <returns>If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call GetLastError.</returns>
    [System.Runtime.InteropServices.DllImport("Kernel32.dll", SetLastError=true)]
    public extern static int CloseHandle(IntPtr hObject);

    public const uint IOCTL_CDROM_READ_TOC         = 0x00024000;
    public const uint IOCTL_STORAGE_CHECK_VERIFY   = 0x002D4800;
    public const uint IOCTL_CDROM_RAW_READ         = 0x0002403E;
    public const uint IOCTL_STORAGE_MEDIA_REMOVAL  = 0x002D4804;
    public const uint IOCTL_STORAGE_EJECT_MEDIA    = 0x002D4808;
    public const uint IOCTL_STORAGE_LOAD_MEDIA     = 0x002D480C;
    public const uint IOCTL_CDROM_READ_Q_CHANNEL   = 0x0002402C;  // found here: http://mu97bot.googlecode.com/svn-history/r2/trunk/Include/WinAPIEx.au3
                                                                  // and here:   http://www.ioctls.net/


    /// <summary>
    /// Most general form of DeviceIoControl Win32 function
    /// </summary>
    /// <param name="hDevice">Handle of device opened with CreateFile, <see cref="Ripper.Win32Functions.CreateFile"/></param>
    /// <param name="IoControlCode">Code of DeviceIoControl operation</param>
    /// <param name="lpInBuffer">Pointer to a buffer that contains the data required to perform the operation.</param>
    /// <param name="InBufferSize">Size of the buffer pointed to by lpInBuffer, in bytes.</param>
    /// <param name="lpOutBuffer">Pointer to a buffer that receives the operation's output data.</param>
    /// <param name="nOutBufferSize">Size of the buffer pointed to by lpOutBuffer, in bytes.</param>
    /// <param name="lpBytesReturned">Receives the size, in bytes, of the data stored into the buffer pointed to by lpOutBuffer. </param>
    /// <param name="lpOverlapped">Pointer to an OVERLAPPED structure. Discarded for this case</param>
    /// <returns>If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.</returns>
    [System.Runtime.InteropServices.DllImport("Kernel32.dll", SetLastError=true)]
    public extern static int DeviceIoControl(IntPtr hDevice, uint IoControlCode, 
      IntPtr lpInBuffer, uint InBufferSize,
      IntPtr lpOutBuffer, uint nOutBufferSize,
      ref uint lpBytesReturned,
      IntPtr lpOverlapped);

    [ StructLayout( LayoutKind.Sequential )]
    public struct TRACK_DATA 
    {
      public  byte  Reserved;
      private byte  BitMapped;
      public  byte  Control 
      { 
        get
        {
          return (byte)(BitMapped & 0x0F);
        }
        set
        {
          BitMapped = (byte)((BitMapped & 0xF0) | (value & (byte)0x0F));
        }
      }
      public byte Adr
      {
        get
        {
          return (byte)((BitMapped & (byte)0xF0) >> 4);
        }
        set
        {
          BitMapped = (byte)((BitMapped & (byte)0x0F) | (value << 4));
        }
      }
      public byte  TrackNumber;
      public byte  Reserved1;
      /// <summary>
      /// Don't use array to avoid array creation
      /// </summary>
      public byte  Address_0;
      public byte  Address_1;
      public byte  Address_2;
      public byte  Address_3;
    };

    public const int MAXIMUM_NUMBER_TRACKS = 100;

    [ StructLayout( LayoutKind.Sequential )]
    public class TrackDataList
    {
      [MarshalAs(UnmanagedType.ByValArray, SizeConst=MAXIMUM_NUMBER_TRACKS*8)]
      private byte[] Data;
      public TRACK_DATA this [int Index]
      {
        get
        {
          if ( (Index < 0) | (Index >= MAXIMUM_NUMBER_TRACKS))
          {
            throw new IndexOutOfRangeException();
          }
          TRACK_DATA res;
          GCHandle handle = GCHandle.Alloc(Data, GCHandleType.Pinned);
          try
          {
            IntPtr buffer = handle.AddrOfPinnedObject();
            buffer = (IntPtr)(buffer.ToInt32() + (Index*Marshal.SizeOf(typeof(TRACK_DATA))));
            res = (TRACK_DATA)Marshal.PtrToStructure(buffer, typeof(TRACK_DATA));
          }
          finally
          {
            handle.Free();
          }
          return res;
        }
      }
      public TrackDataList()
      {
        Data = new byte[MAXIMUM_NUMBER_TRACKS*Marshal.SizeOf(typeof(TRACK_DATA))];
      }
    }

    //Struktur für den Input Parameter:
    public const uint IOCTL_CDROM_TRACK_ISRC = 0x3;

    [StructLayout(LayoutKind.Sequential)]
    public class _CDROM_SUB_Q_DATA_FORMAT
    {
        public byte Format;
        public byte Track;
    }
    //Strukturen für den Output Parameter
    [StructLayout(LayoutKind.Explicit)]
    public class _SUB_Q_CHANNEL_DATA
    {
        [FieldOffset(0)]
        public _SUB_Q_CURRENT_POSITION     CurrentPosition;
        [FieldOffset(0)]
        public _SUB_Q_MEDIA_CATALOG_NUMBER MediaCatalog;
        [FieldOffset(0)]
        public _SUB_Q_TRACK_ISRC TrackIsrc;
    }


    [StructLayout(LayoutKind.Sequential)]
    public class _SUB_Q_CURRENT_POSITION
    {
        public _SUB_Q_HEADER Header;
        public byte          FormatCode;
        //private byte        BitMapped;
        public byte BitMapped;
        //public byte Control
        //{
        //    get
        //    {
        //        return (byte)(BitMapped & 0x0F);
        //    }
        //    set
        //    {
        //        BitMapped = (byte)((BitMapped & 0xF0) | (value & (byte)0x0F));
        //    }
        //}
        //public byte Adr
        //{
        //    get
        //    {
        //        return (byte)((BitMapped & (byte)0xF0) >> 4);
        //    }
        //    set
        //    {
        //        BitMapped = (byte)((BitMapped & (byte)0x0F) | (value << 4));
        //    }
        //}
        public byte          TrackNumber;
        public byte          IndexNumber;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
        public byte[]        AbsoluteAddress;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
        public byte[]        TrackRelativeAddress;
    }

    [StructLayout(LayoutKind.Sequential)]
    public class _SUB_Q_MEDIA_CATALOG_NUMBER
    {
        _SUB_Q_HEADER Header;
        byte        FormatCode;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
        public byte[]        Reserved;
        //private byte BitMapped;
        public byte BitMapped;
        //public byte Reserved1
        //{
        //    get
        //    {
        //        return (byte)(BitMapped & 0xFE);
        //    }
        //    set
        //    {
        //        BitMapped = (byte)((BitMapped & 0x01) | (value & (byte)0xFE));
        //    }
        //}
        //public byte Mcval
        //{
        //    get
        //    {
        //        return (byte)((BitMapped & (byte)0x01) >> 7);
        //    }
        //    set
        //    {
        //        BitMapped = (byte)((BitMapped & (byte)0xFE) | (value << 7));
        //    }
        //}
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=15)]
        public byte[]  MediaCatalog;
    }

    [StructLayout(LayoutKind.Sequential)]
    public class _SUB_Q_TRACK_ISRC
    {

        public _SUB_Q_HEADER Header;
        public byte FormatCode;
        public byte Reserved0;
        public byte Track;
        public byte Reserved1;
        //private byte BitMapped;
        public byte BitMapped;
        //public byte Reserved2
        //{
        //    get
        //    {
        //        return (byte)(BitMapped & 0xFE);
        //    }
        //    set
        //    {
        //        BitMapped = (byte)((BitMapped & 0x01) | (value & (byte)0xFE));
        //    }
        //}
        //public byte Tcval
        //{
        //    get
        //    {
        //        return (byte)((BitMapped & (byte)0x01) >> 7);
        //    }
        //    set
        //    {
        //        BitMapped = (byte)((BitMapped & (byte)0xFE) | (value << 7));
        //    }
        //}
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=15)]
        public byte[]       TrackIsrc;

    }

    [StructLayout(LayoutKind.Sequential)]
    public class _SUB_Q_HEADER
    {
        public byte Reserved;
        public byte AudioStatus;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=2)]
        public byte[] DataLength;
    }



    [StructLayout( LayoutKind.Sequential )]
    public class CDROM_TOC 
    {
      public ushort Length;
      public byte FirstTrack = 0;
      public byte LastTrack = 0;

      public TrackDataList TrackData;

      public CDROM_TOC()
      {
        TrackData = new TrackDataList();
        Length = (ushort)Marshal.SizeOf(this);
      }
    } 

    [ StructLayout( LayoutKind.Sequential )]
    public class PREVENT_MEDIA_REMOVAL 
    {
      public byte PreventMediaRemoval = 0;
    }



    public enum TRACK_MODE_TYPE { YellowMode2, XAForm2, CDDA }
    [ StructLayout( LayoutKind.Sequential )]
      public class RAW_READ_INFO 
    {
      public long  DiskOffset = 0;
      public uint  SectorCount = 0;
      public TRACK_MODE_TYPE  TrackMode = TRACK_MODE_TYPE.CDDA;
    }

    /// <summary>
    /// Overload version of DeviceIOControl to read the TOC (Table of contents)
    /// </summary>
    /// <param name="hDevice">Handle of device opened with CreateFile, <see cref="Ripper.Win32Functions.CreateFile"/></param>
    /// <param name="IoControlCode">Must be IOCTL_CDROM_READ_TOC for this overload version</param>
    /// <param name="InBuffer">Must be <code>IntPtr.Zero</code> for this overload version </param>
    /// <param name="InBufferSize">Must be 0 for this overload version</param>
    /// <param name="OutTOC">TOC object that receive the CDROM TOC</param>
    /// <param name="OutBufferSize">Must be <code>(UInt32)Marshal.SizeOf(CDROM_TOC)</code> for this overload version</param>
    /// <param name="BytesReturned">Receives the size, in bytes, of the data stored into OutTOC</param>
    /// <param name="Overlapped">Pointer to an OVERLAPPED structure. Discarded for this case</param>
    /// <returns>If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.</returns>
    [System.Runtime.InteropServices.DllImport("Kernel32.dll", SetLastError=true)]
    public extern static int DeviceIoControl(IntPtr hDevice, uint IoControlCode, 
      IntPtr InBuffer, uint InBufferSize,
      [Out] CDROM_TOC OutTOC, uint OutBufferSize,
      ref uint BytesReturned,
      IntPtr Overlapped);

    /// <summary>
    /// Overload version of DeviceIOControl to lock/unlock the CD
    /// </summary>
    /// <param name="hDevice">Handle of device opened with CreateFile, <see cref="Ripper.Win32Functions.CreateFile"/></param>
    /// <param name="IoControlCode">Must be IOCTL_STORAGE_MEDIA_REMOVAL for this overload version</param>
    /// <param name="InMediaRemoval">Set the lock/unlock state</param>
    /// <param name="InBufferSize">Must be <code>(UInt32)Marshal.SizeOf(PREVENT_MEDIA_REMOVAL)</code> for this overload version</param>
    /// <param name="OutBuffer">Must be <code>IntPtr.Zero</code> for this overload version </param>
    /// <param name="OutBufferSize">Must be 0 for this overload version</param>
    /// <param name="BytesReturned">A "dummy" varible in this case</param>
    /// <param name="Overlapped">Pointer to an OVERLAPPED structure. Discarded for this case</param>
    /// <returns>If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.</returns>
    [System.Runtime.InteropServices.DllImport("Kernel32.dll", SetLastError=true)]
    public extern static int DeviceIoControl(IntPtr hDevice, uint IoControlCode, 
      [In] PREVENT_MEDIA_REMOVAL InMediaRemoval, uint InBufferSize,
      IntPtr OutBuffer, uint OutBufferSize,
      ref uint BytesReturned,
      IntPtr Overlapped);

    /// <summary>
    /// Overload version of DeviceIOControl to read digital data
    /// </summary>
    /// <param name="hDevice">Handle of device opened with CreateFile, <see cref="Ripper.Win32Functions.CreateFile"</param>
    /// <param name="IoControlCode">Must be IOCTL_CDROM_RAW_READ for this overload version</param>
    /// <param name="rri">RAW_READ_INFO structure</param>
    /// <param name="InBufferSize">Size of RAW_READ_INFO structure</param>
    /// <param name="OutBuffer">Buffer that will receive the data to be read</param>
    /// <param name="OutBufferSize">Size of the buffer</param>
    /// <param name="BytesReturned">Receives the size, in bytes, of the data stored into OutBuffer</param>
    /// <param name="Overlapped">Pointer to an OVERLAPPED structure. Discarded for this case</param>
    /// <returns>If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.</returns>
    [System.Runtime.InteropServices.DllImport("Kernel32.dll", SetLastError=true)]
    public extern static int DeviceIoControl(IntPtr hDevice, uint IoControlCode, 
      [In] RAW_READ_INFO rri, uint InBufferSize,
      [In, Out] byte[] OutBuffer, uint OutBufferSize,
      ref uint BytesReturned,
      IntPtr Overlapped);

    /// <summary>
    /// Overload Version of DeviceIOControl to read ISRC Code from CD for special track
    /// </summary>
    /// <param name="hDevice">Handle of device opened with CreateFile, <see cref="MyISRCReader.Win32Functions.CreateFile"</param>
    /// <param name="IoControlCode">Must be IOCTL_CDROM_READ_Q_CHANNEL</param>
    /// <param name="fmt">_CDROM_SUB_Q_DATA_FORMAT structure </param>
    /// <param name="InBufferSize">Size of _CDROM_SUB_Q_DATA_FORMAT</param>
    /// <param name="OutData">_SUB_Q_CHANNEL_DATA structure</param>
    /// <param name="OutBufferSize">Size of _SUB_Q_CHANNEL_DATA buffer</param>
    /// <param name="BytesReturned">Receives the size, in bytes, of the data stored into OutBuffer</param>
    /// <param name="Overlapped">Pointer to an OVERLAPPED structure. Discarded for this case</param>
    /// <returns>0 if the function fails and nonzero if the function succeeded</returns>
    [System.Runtime.InteropServices.DllImport("Kernel32.dll", SetLastError = true)]
    public extern static int DeviceIoControl(IntPtr hDevice, uint IoControlCode,
      [In] _CDROM_SUB_Q_DATA_FORMAT fmt, uint InBufferSize,
      [In, Out] _SUB_Q_CHANNEL_DATA OutData, uint OutBufferSize,
      ref uint BytesReturned,
      IntPtr Overlapped);
  }
}

Это моя простая тестовая программа - я открываю дескриптор компакт-диска и получаю правильное количество дорожек моего компакт-диска. Код ошибки после моего ISRC-Read равен 1, что означает «неправильная функция». Данные выходной структуры имеют все значения 0...

namespace MyISRCReader
{    
    class Program
    {
        static void Main(string[] args)
        {
            IntPtr cdHandle = IntPtr.Zero;
            char Drive = 'D';
            Win32Functions.CDROM_TOC Toc = new Win32Functions.CDROM_TOC();
            bool TocValid;

            if (Win32Functions.GetDriveType(Drive + ":\\") == Win32Functions.DriveTypes.DRIVE_CDROM)
            {
                cdHandle = Win32Functions.CreateFile("\\\\.\\" + Drive + ':', Win32Functions.GENERIC_READ, Win32Functions.FILE_SHARE_READ, IntPtr.Zero, Win32Functions.OPEN_EXISTING, 0, IntPtr.Zero);
                if (((int)cdHandle != -1) && ((int)cdHandle != 0))
                {
                    uint Dummy = 0;
                    Win32Functions.DeviceIoControl(cdHandle, Win32Functions.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, ref Dummy, IntPtr.Zero);
                    uint BytesRead = 0;
                    TocValid = Win32Functions.DeviceIoControl(cdHandle, Win32Functions.IOCTL_CDROM_READ_TOC, IntPtr.Zero, 0, Toc, (uint)Marshal.SizeOf(Toc), ref BytesRead, IntPtr.Zero) != 0;
                    if (TocValid)
                    {
                        Console.WriteLine("track count: " + (Toc.LastTrack - Toc.FirstTrack + 1));

                        Win32Functions._CDROM_SUB_Q_DATA_FORMAT format = new Win32Functions._CDROM_SUB_Q_DATA_FORMAT();
                        Win32Functions._SUB_Q_CHANNEL_DATA data = new Win32Functions._SUB_Q_CHANNEL_DATA();

                        format.Format = (byte)Win32Functions.IOCTL_CDROM_TRACK_ISRC;
                        format.Track = (byte)2;

                        Console.WriteLine("Last Win32 Error beforde ISRC read: " + Marshal.GetLastWin32Error().ToString());

                        int erg = Win32Functions.DeviceIoControl(cdHandle,
                                                       Win32Functions.IOCTL_CDROM_TRACK_ISRC,
                                                       format,
                                                       (uint)Marshal.SizeOf(format),
                                                       data,
                                                       (uint)Marshal.SizeOf(data),
                                                       ref Dummy,
                                                       IntPtr.Zero);

                        Console.WriteLine("Last Win32 error: " + Marshal.GetLastWin32Error().ToString());
                        if (erg == 0)
                        {
                            //Reading ISRC failed
                            Console.WriteLine("Could not read ISRC!");
                        }
                        else
                        {
                            //Reading ISRC succeeded
                            Console.WriteLine("Tracknumber: " + data.TrackIsrc.Track + " - ISRC: " + data.TrackIsrc.TrackIsrc[data.TrackIsrc.Track].ToString());
                        }
                    }
                    else
                    {
                        Console.WriteLine("Toc invalid");
                    }

                }
                else
                {
                    Console.WriteLine("Handle Error");
                }
            }
            Console.Write("Eject media? (y/n)");
            var ch  = Console.ReadKey();
            if (ch.KeyChar == 'y' || ch.KeyChar=='Y')
            {
                uint Dummy = 0;
                Win32Functions.DeviceIoControl(cdHandle, Win32Functions.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, ref Dummy, IntPtr.Zero);
            }
            Console.ReadKey();
        }
    }
}

Верны ли вызовы PInvoke и структуры (классы)? Закомментированные части классов в Win32Functions (которые имитируют структуры C++) являются своего рода альтернативой для имитации битовых полей C++. Я не знаю, как отладить это или где я могу найти ошибку... У кого-то есть идея?


person sebastian87    schedule 22.08.2012    source источник


Ответы (1)


Ну, одна из возможностей заключается в том, что когда вы вызываете следующую строку:

int erg = Win32Functions.DeviceIoControl(cdHandle, 
                                         Win32Functions.IOCTL_CDROM_TRACK_ISRC,
                                         format,
                                         (uint)Marshal.SizeOf(format),
                                         data,
                                         (uint)Marshal.SizeOf(data),
                                         ref Dummy,
                                         IntPtr.Zero);

вы передаете формат и объекты данных напрямую, когда, согласно оператору dllimport, он ожидает для них IntPtrs.

[System.Runtime.InteropServices.DllImport("Kernel32.dll", SetLastError=true)]
public extern static int DeviceIoControl(IntPtr hDevice, 
                                         uint IoControlCode, 
                                         IntPtr lpInBuffer,
                                         uint InBufferSize,
                                         IntPtr lpOutBuffer,
                                         uint nOutBufferSize,
                                         ref uint lpBytesReturned,
                                         IntPtr lpOverlapped);

Теперь я, вероятно, ожидал бы какое-то исключение нарушения доступа, а не то, что вы получаете, но это место для начала.

Кроме того, знаете ли вы наверняка, что когда вы получаете размеры формата и данных, они возвращают правильные размеры? Я вижу, вы определили один из них как

[StructLayout(LayoutKind.Sequential)]
public class _CDROM_SUB_Q_DATA_FORMAT
{
    public byte Format;
    public byte Track;
}

Теперь вы ожидаете, что размер будет 2 байта, но на самом деле он может быть равен 8, потому что по умолчанию он упаковывает размер так, что каждое поле начинается с 4-байтовой границы. Вы можете добавить Pack = 1 к атрибуту, чтобы убедиться, что он использует минимальное количество байтов. Но вы должны дважды проверить размеры и убедиться, что они соответствуют тому, что определено в коде C++.

person Nanhydrin    schedule 24.08.2012