【问题标题】: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 有哪些好技巧?

知道,

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

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

【问题讨论】:

    标签: c# design-patterns pinvoke marshalling deviceiocontrol


    【解决方案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% 是有道理的 ;)
    猜你喜欢
    • 1970-01-01
    • 2012-01-26
    • 2011-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-25
    • 1970-01-01
    相关资源
    最近更新 更多