使用 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 都有哪些好的策略?的主要内容,如果未能解决你的问题,请参考以下文章
中高级 C# 开发人员在开始使用 Visual Studio 2010 C++ 时都有哪些好的参考资料?