3

我正在尝试从 C# 调用 DeviceIoControl 以获取IOCTL_DVD_*控制代码。阅读了大量信息并尝试了许多示例后,我没有取得太大进展。

我最终要做的是获得DVD_LAYER_DESCRIPTOR有关 DVD 驱动器中当前媒体的结构。我可以CreateFile在 DVD 设备上成功调用,但是当我尝试DeviceIoControl使用控制代码调用时,IOCTL_DVD_START_SESSION它返回一个成功代码,但我似乎没有成功返回 sessionId 值,总是返回 0。(然后我所做的任何尝试尝试获取失败的层描述IOCTL_DVD_READ_STRUCTURE,即函数失败或返回成功但给出空白输出结构。)

在找到一些进行类似调用的 C 代码后,我能够编译此代码(使用 Visual C++ 2008 速成版)并且它成功地能够启动会话、读取DVD_LAYER_DESCRIPTOR并关闭会话而没有问题,所以我知道这是可行的。

C# 问题似乎与外部函数的定义方式和参数的编组方式有关。以及如何定义传递和返回的各种结构。

我查看了 www.pinvoke.net 以了解他们如何定义它,并使用了一些示例代码和给出的定义,但仍然存在与上述相同的问题。部分问题似乎是每个 IOCTL 控制代码的参数都不同,主要是结构,但IOCTL_DVD_START_SESSION输出值是 32 位整数。如何定义 C# 中的外部方法来处理这些不同的情况?此外,使用正确大小的成员类型定义的各种结构显示它们在 C 和 C# 代码之间的大小不同,但各个成员的大小相同???

如果我使用类似的程序DeviceIOView并观察 C 代码和 C# 代码对IOCTL_DVD_START_SESSIONC 版本的调用返回 3 的 sessionid 并且 DeviceIOView 显示运行 C# 代码时发送回的数据也是 3 所以似乎有一些返回参数的编组问题,因为我们在 C# 代码中只看到 0

有人对如何从 C# 调用 DeviceIoControl 以访问 DVD 信息有任何想法或工作示例代码吗?(显示应该如何定义和使用结构和功能。)任何指向有用网站或其他建议的链接将不胜感激。

(在 Visual C# 2008 Express Edition,.NET 3.5 中开发。)

N约翰斯

示例代码(添加)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;

namespace Example
{
    class Program
    {
        static void Main(string[] args)
        {
            string driveLetter = args[0].Substring(0, 1).ToUpper() + ":";

            SafeFileHandle _hdev = CreateFileR(driveLetter);
            if (_hdev.IsClosed | _hdev.IsInvalid)
            {
                Console.WriteLine("Error opening device");
                return;
            }

            Console.WriteLine("DeviceIoControl - Version One");

            Console.WriteLine("IOCTL_DVD_START_SESSION");

            bool result = false;
            int bytesReturned = 0;
            int sessionId = 0;

            result = DeviceIoControl(_hdev, CTL_CODE(0x00000033, 0x0400, 0, 1), IntPtr.Zero, 0, (IntPtr)sessionId, Marshal.SizeOf(sessionId), out bytesReturned, IntPtr.Zero);

            if (result == false)
            {
                int error_code = Marshal.GetLastWin32Error();
                Console.WriteLine("Result: " + result);
                Console.WriteLine("error code: " + error_code);
            }
            else
            {
                Console.WriteLine("Result: " + result);
                Console.WriteLine("BytesReturned: " + bytesReturned);
                Console.WriteLine("SessionId: " + sessionId);
                Console.WriteLine("sizeof(SessionId): " + Marshal.SizeOf(sessionId));
            }

            Console.WriteLine("IOCTL_DVD_READ_STRUCTURE");
            Console.WriteLine("Skipping...");

            Console.WriteLine("IOCTL_DVD_END_SESSION");
            bytesReturned = 0;

            result = DeviceIoControl(_hdev, CTL_CODE(0x00000033, 0x0403, 0, 1), new IntPtr(sessionId), Marshal.SizeOf(sessionId), IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);

            if (result == false)
            {
                int error_code = Marshal.GetLastWin32Error();
                Console.WriteLine("error code: " + error_code);
                Console.WriteLine("Result: " + result);
            }
            else
            {
                Console.WriteLine("Result: " + result);
                Console.WriteLine("BytesReturned: " + bytesReturned);
            }

            Console.WriteLine("\nDeviceIoControl - Version Two");

            Console.WriteLine("IOCTL_DVD_START_SESSION");

            result = false;
            uint bytesReturned2 = 0;
            sessionId = -10;

            NativeOverlapped nativeOverlapped = new NativeOverlapped();

            result = DeviceIoControlAlt(_hdev, EIOControlCode.DvdStartSession, 0, 0, sessionId, (uint)Marshal.SizeOf(sessionId), ref bytesReturned2, ref nativeOverlapped);

            if (result == false)
            {
                int error_code = Marshal.GetLastWin32Error();
                Console.WriteLine("Result: " + result);
                Console.WriteLine("error code: " + error_code);
            }
            else
            {
                Console.WriteLine("Result: " + result);
                Console.WriteLine("BytesReturned: " + bytesReturned2);
                Console.WriteLine("SessionId: " + sessionId);
                Console.WriteLine("sizeof(SessionId): " + Marshal.SizeOf(sessionId));
            }

            Console.WriteLine("IOCTL_DVD_READ_STRUCTURE");
            Console.WriteLine("Skipping...");

            Console.WriteLine("IOCTL_DVD_END_SESSION");
            bytesReturned2 = 0;

            result = DeviceIoControlAlt(_hdev, EIOControlCode.DvdEndSession, sessionId, (uint)Marshal.SizeOf(sessionId), 0, 0, ref bytesReturned2, ref nativeOverlapped);

            if (result == false)
            {
                int error_code = Marshal.GetLastWin32Error();
                Console.WriteLine("Result: " + result);
                Console.WriteLine("error code: " + error_code);
            }
            else
            {
                Console.WriteLine("Result: " + result);
                Console.WriteLine("BytesReturned: " + bytesReturned2);
            }

            _hdev.Close();
        }

        public static int CTL_CODE(int DeviceType, int Function, int Method, int Access)
        {
            return (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2)
              | (Method));
        } 

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
        public static SafeFileHandle CreateFileR(string device)
        {
            string str = device.EndsWith(@"\") ? device.Substring(0, device.Length - 1) : device;
            return new SafeFileHandle(CreateFile(@"\\.\" + str, WinntConst.GENERIC_READ, WinntConst.FILE_SHARE_READ, IntPtr.Zero, WinntConst.OPEN_EXISTING, WinntConst.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
        }

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool DeviceIoControl([In] SafeFileHandle hDevice,
            [In] int dwIoControlCode, [In] IntPtr lpInBuffer,
            [In] int nInBufferSize, [Out] IntPtr lpOutBuffer,
            [In] int nOutBufferSize, out int lpBytesReturned,
            [In] IntPtr lpOverlapped);

        internal class WinntConst
        {
            // Fields
            internal static uint FILE_ATTRIBUTE_NORMAL = 0x80;
            internal static uint FILE_SHARE_READ = 1;
            internal static uint GENERIC_READ = 0x80000000;
            internal static uint OPEN_EXISTING = 3;
        }

        // Other code for DeviceIoControl from pinvoke.net
        [Flags]
        public enum EIOControlCode : uint
        {
            // DVD
            DvdReadStructure = (EFileDevice.Dvd << 16) | (0x0450 << 2) | EMethod.Buffered | (FileAccess.Read << 14),
            DvdStartSession = (EFileDevice.Dvd << 16) | (0x0400 << 2) | EMethod.Buffered | (FileAccess.Read << 14),
            DvdEndSession = (EFileDevice.Dvd << 16) | (0x0403 << 2) | EMethod.Buffered | (FileAccess.Read << 14)
        };

        [Flags]
        public enum EFileDevice : uint
        {
            Dvd = 0x00000033,
        }

        [Flags]
        public enum EMethod : uint
        {
            Buffered = 0,
            InDirect = 1,
            OutDirect = 2,
            Neither = 3
        }

        [DllImport("Kernel32.dll", EntryPoint="DeviceIoControl", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool DeviceIoControlAlt(
            Microsoft.Win32.SafeHandles.SafeFileHandle hDevice,
            EIOControlCode IoControlCode,
            [MarshalAs(UnmanagedType.AsAny)][In] object InBuffer,
            uint nInBufferSize,
            [MarshalAs(UnmanagedType.AsAny)][Out] object OutBuffer,
            uint nOutBufferSize,
            ref uint pBytesReturned,
            [In] ref System.Threading.NativeOverlapped Overlapped
        );
    }
}

要运行此代码,您需要在命令行上指定 DVD 驱动器的驱动器号。

输出

DeviceIoControl - Version One
IOCTL_DVD_START_SESSION
Result: False
error code: 122
IOCTL_DVD_READ_STRUCTURE
Skipping...
IOCTL_DVD_END_SESSION
error code: 87
Result: False

DeviceIoControl - Version Two
IOCTL_DVD_START_SESSION
Result: True
BytesReturned: 4
SessionId: -10
sizeof(SessionId): 4
IOCTL_DVD_READ_STRUCTURE
Skipping...
IOCTL_DVD_END_SESSION
Result: True
BytesReturned: 0 

第一个版本在两次调用中都失败,并带有给定的错误代码:

122 - ERROR_INSUFFICIENT_BUFFER

87 - ERROR_INVALID_PARAMETER

第二个版本似乎成功了,但 SessionId 的值为 -10,即初始化值。(从 MSDN 这个值应该在 -1 和 3 之间?)结束会话也成功了。

[注意:第二个版本的启动会话似乎只在其他所有调用中成功,不知道为什么,但这似乎也是我拥有的 C 代码中的一个问题,因为它的错误处理是重试。]

4

1 回答 1

4

问题出在这里:

result = DeviceIoControl(_hdev, CTL_CODE(0x00000033, 0x0400, 0, 1),
   IntPtr.Zero, 0, (IntPtr)sessionId, Marshal.SizeOf(sessionId),
   out bytesReturned, IntPtr.Zero);

驱动程序需要指向 lpOutBuffer 中缓冲区的指针,但您却提供了 sessionId 本身(为零)。当然,这是行不通的。

这里你需要做的:

IntPtr buffer = Marshal.AllocHGlobal(sizeof(int));
result = DeviceIoControl(_hdev, CTL_CODE(0x00000033, 0x0400, 0, 1),
    IntPtr.Zero, 0, buffer, sizeof(int), out bytesReturned, IntPtr.Zero);
int sessionId = Marshal.ReadInt32(buffer);
Marshal.FreeHGlobal(buffer);

顺便说一句,这同样适用于所有以下 DeviceIoControl 调用,当您需要提供指向值的指针时,您再次提供值。您还需要检查您的 CTL_CODE 函数是否构建了有效的 io 代码。

同样,DeviceIoControl 需要指向 in 和 out 结构的缓冲区的指针。

于 2009-07-01T07:40:42.713 回答