For reading CD-ROMs using DeviceIoControl and SPTD, I upgraded the code to be asynchronous.
It works but I’m not totally sure on how correct translation from OVERLAPPED to Task actually is…
Here is the relevant code,
ReadSectorAsync:
public override unsafe Task<ISector> ReadSectorAsync(in int index)
{
using var manualResetEvent = new ManualResetEvent(true);
var overlapped = new Overlapped
{
EventHandleIntPtr = manualResetEvent.SafeWaitHandle.DangerousGetHandle(),
};
var nativeOverlapped = overlapped.Pack(null, null);
var memory = Disc.GetDeviceAlignedBuffer(2352, Handle);
var buffer = memory.Span;
const int timeOutValue = 3;
fixed (byte* data = &MemoryMarshal.GetReference(buffer))
{
var inBufferSize = (uint)Marshal.SizeOf<NativeTypes.SCSI_PASS_THROUGH_DIRECT>();
var inBuffer = Marshal.AllocHGlobal((int)inBufferSize);
var position = index;
// ReSharper disable once ConvertToConstant.Local
var transfer = 1u; // sectors
var sptd = new NativeTypes.SCSI_PASS_THROUGH_DIRECT(12)
{
Length = (ushort)inBufferSize,
DataIn = NativeConstants.SCSI_IOCTL_DATA_IN,
DataTransferLength = (uint)buffer.Length,
DataBuffer = (nint)data,
TimeOutValue = timeOutValue,
Cdb =
{
[00] = 0xBE, // operation code: READ CD
[01] = 0, // expected sector type: any
[02] = (byte)(position >> 24 & 0xFF), // starting LBA
[03] = (byte)(position >> 16 & 0xFF), // starting LBA
[04] = (byte)(position >> 08 & 0xFF), // starting LBA
[05] = (byte)(position >> 00 & 0xFF), // starting LBA
[06] = (byte)(transfer >> 16 & 0xFF), // transfer length
[07] = (byte)(transfer >> 08 & 0xFF), // transfer length
[08] = (byte)(transfer >> 00 & 0xFF), // transfer length
[09] = 0xF8, // sync, header, sub-header, user data, EDC, ECC
[10] = 0, // sub-channel data: none
[11] = 0, // control
},
};
Marshal.StructureToPtr(sptd, inBuffer, false);
var ioctl = NativeMethods.DeviceIoControl(
Handle,
NativeConstants.IOCTL_SCSI_PASS_THROUGH_DIRECT,
inBuffer,
inBufferSize,
inBuffer,
inBufferSize,
out _,
nativeOverlapped
);
Marshal.FreeHGlobal(inBuffer);
if (ioctl is false && Marshal.GetLastPInvokeError() is not NativeConstants.ERROR_IO_PENDING)
{
throw new Win32Exception();
}
}
var tcs = new TaskCompletionSource<ISector>();
var rwh = ThreadPool.RegisterWaitForSingleObject(
manualResetEvent,
ReadSectorAsyncCallBack,
(tcs, memory),
TimeSpan.FromSeconds(timeOutValue),
true
);
tcs.Task.ContinueWith(_ => rwh.Unregister(null), TaskScheduler.Current);
return tcs.Task;
}
ReadSectorAsyncCallBack:
private void ReadSectorAsyncCallBack(object? state, bool timedOut)
{
var (source, memory) = ((TaskCompletionSource<ISector>, NativeMemory<byte>))state!;
try
{
if (timedOut)
{
source.SetCanceled();
}
else
{
ISector sector = Sector switch
{
SectorCooked2048 => throw new NotSupportedException(Sector.GetType().Name),
SectorCooked2324 => throw new NotSupportedException(Sector.GetType().Name),
SectorCooked2336 => throw new NotSupportedException(Sector.GetType().Name),
SectorRawAudio => MemoryMarshal.Read<SectorRawAudio>(memory.Span),
SectorRawMode0 => MemoryMarshal.Read<SectorRawMode0>(memory.Span),
SectorRawMode1 => MemoryMarshal.Read<SectorRawMode1>(memory.Span),
SectorRawMode2Form1 => MemoryMarshal.Read<SectorRawMode2Form1>(memory.Span),
SectorRawMode2Form2 => MemoryMarshal.Read<SectorRawMode2Form2>(memory.Span),
SectorRawMode2FormLess => MemoryMarshal.Read<SectorRawMode2FormLess>(memory.Span),
_ => throw new NotSupportedException(Sector.GetType().Name),
};
source.SetResult(sector);
}
}
catch (Exception e)
{
source.SetException(e);
}
finally
{
memory.Dispose();
}
}