使用 DeviceIoControl 都有哪些好的策略?

Posted

技术标签:

【中文标题】使用 DeviceIoControl 都有哪些好的策略?【英文标题】:What are some good strategies for working with DeviceIoControl?使用 DeviceIoControl 有哪些好的策略? 【发布时间】:2022-01-19 20:46:50 【问题描述】:

当我从 C# 中调用 DeviceIoControl 时,我正在寻找一些指导,因为我知道其接受指针参数的通用方面并不总是很容易在 C# 中表达。

以下是两个示例和说明。

示例 1:

这可行但很麻烦,你有一个一次性作用域,但你必须将参数传递给函数,最后将输出缓冲区值分配回变量。

var toc = new CDROM_TOC(); // non blittable

var code = NativeConstants.IOCTL_CDROM_READ_TOC;

using (var scope = new UnmanagedMemoryScope<CDROM_TOC>(toc))

    if (!UnsafeNativeMethods.DeviceIoControl(Handle, code, IntPtr.Zero, 0, scope.Memory, scope.Size, out _))
        return Array.Empty<ITrack>();

    toc = scope.Value; // this is weird

示例 1 助手:

internal struct UnmanagedMemoryScope<T> : IDisposable where T : struct

    private bool IsDisposed  get; set; 
    public uint Size  get; 
    public IntPtr Memory  get; 

    public T Value
    
        get => Marshal.PtrToStructure<T>(Memory);
        set => Marshal.StructureToPtr(value, Memory, true);
    

    public UnmanagedMemoryScope(T value)
    
        var size = Marshal.SizeOf<T>();
        Memory = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(value, Memory, false);
        Size = (uint)size;
        IsDisposed = false;
    

    public void Dispose()
    
        if (IsDisposed)
            return;

        if (Memory != default)
            Marshal.FreeHGlobal(Memory);

        IsDisposed = true;
    

示例 2:

这个已经友好多了,包装器做编组,传递的值是ref

var toc = new CDROM_TOC(); // non blittable

var code = NativeConstants.IOCTL_CDROM_READ_TOC;

var ioctl = DeviceIoControl(Handle, code, ref toc);

// ...

示例 2 助手 1:

private static bool DeviceIoControl<TTarget>(
    SafeFileHandle handle, uint code, ref TTarget target)
    where TTarget : struct

    var sizeOf = Marshal.SizeOf<TTarget>();
    var intPtr = Marshal.AllocHGlobal(sizeOf);

    Marshal.StructureToPtr(target, intPtr, false);

    var ioctl = UnsafeNativeMethods.DeviceIoControl(
        handle,
        code,
        IntPtr.Zero,
        0u,
        intPtr,
        (uint)sizeOf,
        out var lpBytesReturned
    );

    target = Marshal.PtrToStructure<TTarget>(intPtr);

    Marshal.FreeHGlobal(intPtr);

    return ioctl;

示例 2 助手 2:

private static bool DeviceIoControl<TTarget, TSource>(
    SafeFileHandle handle, uint code, ref TTarget target, ref TSource source)
    where TSource : struct 
    where TTarget : struct

    var sizeOf1 = Marshal.SizeOf(source);
    var sizeOf2 = Marshal.SizeOf(target);
    var intPtr1 = Marshal.AllocHGlobal(sizeOf1);
    var intPtr2 = Marshal.AllocHGlobal(sizeOf2);
    
    Marshal.StructureToPtr(source, intPtr1, false);
    Marshal.StructureToPtr(target, intPtr2, false);

    var ioctl = UnsafeNativeMethods.DeviceIoControl(
        handle,
        code,
        intPtr1,
        (uint)sizeOf1,
        intPtr2,
        (uint)sizeOf2,
        out var lpBytesReturned
    );

    Marshal.PtrToStructure(intPtr1, source);
    Marshal.PtrToStructure(intPtr2, target);
    
    Marshal.FreeHGlobal(intPtr1);
    Marshal.FreeHGlobal(intPtr2);

    return ioctl;

但我觉得我可能遗漏了一些东西,也许有更好的方法......

问题:

从 C# 调用 DeviceIoControl 有哪些好技巧?

知道,

想避免使用unsafe关键字 有非 blittable 类型,所以 fixed 对他们来说是不可能的 函数接受任意类型,it's only buffers for it in the end

当然有 C++/CLI 路线,但好吧,它不再是 C#...

希望这对你有意义,否则请告诉我。

【问题讨论】:

【参考方案1】:

我通常是这样做的。

参数结构:

ref struct CDROM_TOC

    const int MAXIMUM_NUMBER_TRACKS = 100;
    public const int sizeInBytes = 4 + MAXIMUM_NUMBER_TRACKS * 8;

    readonly Span<byte> buffer;

    public CDROM_TOC( Span<byte> buffer )
    
        if( buffer.Length != sizeInBytes )
            throw new ArgumentException();
        this.buffer = buffer;
    

    /// <summary>Fixed header of the structure</summary>
    public struct Header
    
        public ushort length;
        public byte firstTrack, lastTrack;
    

    /// <summary>Fixed header</summary>
    public ref Header header =>
        ref MemoryMarshal.Cast<byte, Header>( buffer.Slice( 0, 4 ) )[ 0 ];

    public struct TRACK_DATA
    
        byte reserved;
        public byte controlAndAdr;
        public byte trackNumber;
        byte reserved2;
        public uint address;
    

    /// <summary>Tracks collection</summary>
    public Span<TRACK_DATA> tracks =>
        MemoryMarshal.Cast<byte, TRACK_DATA>( buffer.Slice( 4 ) );

    // Make this structure compatible with fixed() statement
    public ref byte GetPinnableReference() => ref buffer[ 0 ];

使用示例:

CDROM_TOC toc = new CDROM_TOC( stackalloc byte[ CDROM_TOC.sizeInBytes ] );
unsafe

    fixed( byte* buffer = toc )
    
        // Here you have unmanaged pointer for that C interop.
    

// If you want to return the tracks, need to copy to managed heap:
var header = toc.header;
return toc.tracks
    .Slice( header.firstTrack, header.lastTrack - header.firstTrack + 1 )
    .ToArray();

添加更多注释。

答案假设您拥有现代 C#,即 .NET 5 或更新版本,或任何版本的 .NET Core。

该示例确实使用了unsafe,但仅在最低级别使用。如果您绝对不希望这样,请改用GCHandle。使用GCHandleType.Pinned,相当于不安全关键字,只是速度较慢。

与您的代码不同,此方法不使用任何堆内存进行互操作,既不是托管的也不是本机的。

结构的实例是堆栈分配的,它公开了更高级别的 API 来访问该结构的字段。完整的堆栈已经固定在内存中,fixed 关键字对该代码没有任何作用,只需返回地址即可。什么都不做在性能方面是免费的。

【讨论】:

这是一个非常棒的答案,一直停留在我的逻辑中,并没有考虑到这一切,这 200% 是有道理的 ;)

以上是关于使用 DeviceIoControl 都有哪些好的策略?的主要内容,如果未能解决你的问题,请参考以下文章

Python 都有哪些好的 Web 框架

Python 都有哪些好的 Web 框架

中高级 C# 开发人员在开始使用 Visual Studio 2010 C++ 时都有哪些好的参考资料?

Python 都有哪些好的 Web 框架

ASP.NET MVC 中的基本控制器类都有哪些好的候选者?

Python 都有哪些好的 Web 框架