使用 IOCTL_DVD_* 控制代码从 C# 调用 DeviceIoControl

发布于 2024-07-25 16:44:41 字数 9359 浏览 7 评论 0原文

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

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

在找到一些进行类似调用的 C 代码后,我能够编译此代码(使用 Visual C++ 2008 Express Edition)并且它能够成功启动会话,读取 DVD_LAYER_DESCRIPTOR,并毫无问题地关闭会话,这样我就知道这是可行的。

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

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

如果我使用像 DeviceIOView 这样的程序,并观察 C 代码和 C# 代码对 IOCTL_DVD_START_SESSION 进行的调用,则 C 版本将返回 sessionid 3,并且 DeviceIOView 显示的数据是运行 C# 代码时发回的值也是 3,因此返回参数似乎存在某种编组问题,因为我们在 C# 代码中只看到 0

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

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

N Johns

示例代码(已添加)

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 代码中的一个问题,因为它的错误处理是重试。 ]

I am trying to call DeviceIoControl from C# for IOCTL_DVD_* control codes. Having read a lot of information and trying a number of examples I have not made much progress.

What I am trying to eventually do is get a DVD_LAYER_DESCRIPTOR structure about the media currently in the DVD drive. I can call CreateFile successfully on the DVD device, but when I try to call DeviceIoControl with the control code IOCTL_DVD_START_SESSION it returns a success code but I don't seem to get the sessionId value back successfully, always returns 0. (And any attempt I have then made to try getting the layer description with IOCTL_DVD_READ_STRUCTURE fails, i.e. function fails or returns success but gives a blank output structure.)

After finding some C code which makes similar calls I was able to compile this code (using Visual C++ 2008 Express Edition) and it successfully is able to start a session, read the DVD_LAYER_DESCRIPTOR, and close the session without problem so I know this works.

The C# issues appear to relate to how the external function is defined and the parameters marshalled. And how the various structures that are passed and returned are defined.

I have looked at www.pinvoke.net for how they define it, and have used some of the example code and definitions given but still have the same issues as outlined above. Part of the problem seems to be that for each IOCTL control code the parameters are different, mostly structs, but for IOCTL_DVD_START_SESSION the output value is a 32 bit integer. How can an extern method in C# be defined to handle these different cases? Also various structs, defined with the right sized member types, show they are different sizes between the C and C# code, but the individual members are the same sizes???

If I use a program like DeviceIOView and watch the calls made by both the C code and C# code for IOCTL_DVD_START_SESSION the C version returns a sessionid of 3 and DeviceIOView shows the data being sent back when running the C# code is also 3 so there seems to be some sort of Marshalling issue of the returned parameters as we only see 0 in the C# code

Does anyone have any ideas or working example code on how to call DeviceIoControl from C# to access DVD information? (Showing how the structures and function should be defined and used.) Any links to useful websites or other advice would be much appreciated.

(Being developed in Visual C# 2008 Express Edition, .NET 3.5.)

N Johns

Example Code (Added)

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

To run this code you need to specify the drive letter of a DVD drive on the command line.

Output

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 

The first version fails on both calls with the given error codes:

122 - ERROR_INSUFFICIENT_BUFFER

87 - ERROR_INVALID_PARAMETER

The second version seems to succeed but the value of SessionId is -10, the initialised value. (From MSDN this value should be between -1 and 3?) The end session also succeeds.

[ Note: the second version start session only seems to succeed on every other invocation, not sure why but this also appears to be an issue in the C code I have as it's error handling is to retry again. ]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

梦回梦里 2024-08-01 16:44:41

问题在于:

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 需要指向输入和输出结构缓冲区的指针。

The problem lies here:

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

Driver expects pointer to buffer in lpOutBuffer, but you instead provide sessionId itself (which is zero). Of course this will not work.

Here what you need to do:

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

BTW, the same applies to all following DeviceIoControl calls, you again provide value, when you need provide pointer to value. And you also need to check if your CTL_CODE function builds valid io code.

Again, DeviceIoControl expects pointers to buffers for in and out structures.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文