C
C#โ€ข2y ago
JimmahDean

โ” P/Invoked DeviceIoControl() works in C++ but does nothing in C#.

Code is attached. I have scoured this for hours upon hours and cross checked the pointers with the working C++ version and everything matches. The call doesn't error, checking LastWin32Error, LastSystemError and LastPInvokeError all return "The operation completed successfully." and the return length of the data retrieved is the same in C# as it is in C++, even checked in ProcMon that both apps were making the same system calls and they are. The call happens successfully as it should, but zero data is retrieved in the C# version. I'm at my wits end here.
74 Replies
ero
eroโ€ข2y ago
are you sure the STORAGE_PROTOCOL_DATA_DESCRIPTOR_EXT definition is correct? it's defined as
typedef struct _STORAGE_PROTOCOL_DATA_DESCRIPTOR_EXT {
ULONG Version;
ULONG Size;
STORAGE_PROTOCOL_SPECIFIC_DATA_EXT ProtocolSpecificData;
} STORAGE_PROTOCOL_DATA_DESCRIPTOR_EXT;
typedef struct _STORAGE_PROTOCOL_DATA_DESCRIPTOR_EXT {
ULONG Version;
ULONG Size;
STORAGE_PROTOCOL_SPECIFIC_DATA_EXT ProtocolSpecificData;
} STORAGE_PROTOCOL_DATA_DESCRIPTOR_EXT;
and to my knowledge, ULONG is uint?
JimmahDean
JimmahDeanOPโ€ข2y ago
which should be uint ๐Ÿ‘€ fuck me if it's really that simple
JimmahDean
JimmahDeanOPโ€ข2y ago
it's not referenced ๐Ÿคจ
JimmahDean
JimmahDeanOPโ€ข2y ago
i'll try it tho
ero
eroโ€ข2y ago
the size matters still, i would assume ah the entire struct isn't
JimmahDean
JimmahDeanOPโ€ข2y ago
yeah i don't think it's used. it's used in other example code so i added the definition but it never gets touched and yeah, still no data good eye though!
ero
eroโ€ข2y ago
is there some tutorial you pulled this from?
JimmahDean
JimmahDeanOPโ€ข2y ago
ero
eroโ€ข2y ago
hm, i've been trying to get it to work myself, but i get ERROR_NOT_SUPPORTED on DeviceIoControl and i have like 2 nvme drives
JimmahDean
JimmahDeanOPโ€ข2y ago
is the drive letter right
ero
eroโ€ข2y ago
sure
JimmahDean
JimmahDeanOPโ€ข2y ago
it's hard coded to C
ero
eroโ€ข2y ago
tried both C and D
var hDevice = Win32.CreateFileW(
@"\\.\E:",
Win32.GENERIC_WRITE,
Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
null,
Win32.OPEN_EXISTING,
0,
null);
var hDevice = Win32.CreateFileW(
@"\\.\E:",
Win32.GENERIC_WRITE,
Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
null,
Win32.OPEN_EXISTING,
0,
null);
that's not c or d but you get it
JimmahDean
JimmahDeanOPโ€ข2y ago
oh, was it still on E? eh my home m.2 is E, the pc i'm on right now is C
ero
eroโ€ข2y ago
i have 3 drives in my PC, C and D are m.2s
JimmahDean
JimmahDeanOPโ€ข2y ago
what are you calling to get the error?
ero
eroโ€ข2y ago
if (protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)
|| protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))
{
throw new Win32Exception();
}
if (protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)
|| protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))
{
throw new Win32Exception();
}
JimmahDean
JimmahDeanOPโ€ข2y ago
๐Ÿง i ripped out error checking for a minimal repro, but i'm pretty sure that exact check was in there hang on
if (!result || (returnedLength == 0))
{
Console.WriteLine($"DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log failed. {new Win32Exception(Marshal.GetLastWin32Error()).Message}.\n");
//goto exit;
}
Console.WriteLine(returnedLength.ToString());
//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
(protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)))
{
Console.WriteLine("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - data descriptor header not valid.\n");
//return;
}

protocolData = &protocolDataDescr->ProtocolSpecificData;

if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
(protocolData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG)))
{
Console.WriteLine("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - ProtocolData Offset/Length not valid.\n");
//goto exit;
}

//
// SMART/Health Information Log Data
//
{
//var foo2 = (sbyte*)protocolData;
//var foo3 = protocolData->ProtocolDataOffset;
NVME_HEALTH_INFO_LOG* smartInfo = (NVME_HEALTH_INFO_LOG*)((sbyte*)protocolData + protocolData->ProtocolDataOffset);
//NVME_HEALTH_INFO_LOG* smartInfo = (NVME_HEALTH_INFO_LOG*)(foo2 + foo3);
//Console.WriteLine($"Foo2: {(uint)foo2} {foo3}");
//Console.WriteLine($"{buffer}\n{bufferLength},{((uint)protocolData).ToString("x8")},{((uint)smartInfo).ToString("x8")}");

Console.WriteLine($"Win32: {new Win32Exception(Marshal.GetLastWin32Error()).Message}");
Console.WriteLine($"System: {new Win32Exception(Marshal.GetLastSystemError()).Message}");
Console.WriteLine($"P/Invoke: {new Win32Exception(Marshal.GetLastPInvokeError()).Message}");
if (!result || (returnedLength == 0))
{
Console.WriteLine($"DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log failed. {new Win32Exception(Marshal.GetLastWin32Error()).Message}.\n");
//goto exit;
}
Console.WriteLine(returnedLength.ToString());
//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
(protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)))
{
Console.WriteLine("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - data descriptor header not valid.\n");
//return;
}

protocolData = &protocolDataDescr->ProtocolSpecificData;

if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
(protocolData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG)))
{
Console.WriteLine("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - ProtocolData Offset/Length not valid.\n");
//goto exit;
}

//
// SMART/Health Information Log Data
//
{
//var foo2 = (sbyte*)protocolData;
//var foo3 = protocolData->ProtocolDataOffset;
NVME_HEALTH_INFO_LOG* smartInfo = (NVME_HEALTH_INFO_LOG*)((sbyte*)protocolData + protocolData->ProtocolDataOffset);
//NVME_HEALTH_INFO_LOG* smartInfo = (NVME_HEALTH_INFO_LOG*)(foo2 + foo3);
//Console.WriteLine($"Foo2: {(uint)foo2} {foo3}");
//Console.WriteLine($"{buffer}\n{bufferLength},{((uint)protocolData).ToString("x8")},{((uint)smartInfo).ToString("x8")}");

Console.WriteLine($"Win32: {new Win32Exception(Marshal.GetLastWin32Error()).Message}");
Console.WriteLine($"System: {new Win32Exception(Marshal.GetLastSystemError()).Message}");
Console.WriteLine($"P/Invoke: {new Win32Exception(Marshal.GetLastPInvokeError()).Message}");
this is how i had it, and it doesn't hit the two "not valid" writes and the 3 calls at the end are all "The operation completed successfully" do you have crystal disk info?
ero
eroโ€ข2y ago
i guess i can get it real quick
JimmahDean
JimmahDeanOPโ€ข2y ago
JimmahDean
JimmahDeanOPโ€ข2y ago
i'm curious if your drives don't show that feature
ero
eroโ€ข2y ago
thank god it's on winget
ero
eroโ€ข2y ago
ero
eroโ€ข2y ago
ero
eroโ€ข2y ago
C and D whoops, that's E and C why is it sorted like that
JimmahDean
JimmahDeanOPโ€ข2y ago
who knows lol windows probably reports them backwards
ero
eroโ€ข2y ago
c and d are the same
ero
eroโ€ข2y ago
JimmahDean
JimmahDeanOPโ€ข2y ago
so it's supported what's the device handle?
ero
eroโ€ข2y ago
how do you mean? if it's valid or not?
JimmahDean
JimmahDeanOPโ€ข2y ago
ye
ero
eroโ€ข2y ago
var hDevice = Win32.CreateFileW(
@"\\.\F:",
Win32.GENERIC_WRITE,
Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
null,
Win32.OPEN_EXISTING,
0,
null);

if (hDevice == Win32.NULL || hDevice == Win32.INVALID_HANDLE_VALUE)
{
throw new Win32Exception();
}
var hDevice = Win32.CreateFileW(
@"\\.\F:",
Win32.GENERIC_WRITE,
Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
null,
Win32.OPEN_EXISTING,
0,
null);

if (hDevice == Win32.NULL || hDevice == Win32.INVALID_HANDLE_VALUE)
{
throw new Win32Exception();
}
sure is
JimmahDean
JimmahDeanOPโ€ข2y ago
JimmahDean
JimmahDeanOPโ€ข2y ago
not sure why yours errors and mine doesn't
ero
eroโ€ข2y ago
JimmahDean
JimmahDeanOPโ€ข2y ago
let me just toss these GetLastError calls everywhere rq
ero
eroโ€ข2y ago
why bother, Win32Exception does that lol
JimmahDean
JimmahDeanOPโ€ข2y ago
it's just a few copy pastes
JimmahDean
JimmahDeanOPโ€ข2y ago
still no errors
JimmahDean
JimmahDeanOPโ€ข2y ago
but no data
ero
eroโ€ข2y ago
var hDevice = Win32.CreateFileW(
@"\\.\F:",
Win32.GENERIC_WRITE,
Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
null,
Win32.OPEN_EXISTING,
0,
null);

if (hDevice == Win32.NULL || hDevice == Win32.INVALID_HANDLE_VALUE)
{
throw new Win32Exception();
}

Console.WriteLine($"Device handle: 0x{(nint)hDevice:X}");

var len = (int)(
Marshal.OffsetOf<STORAGE_PROPERTY_SET>(nameof(STORAGE_PROPERTY_SET.AdditionalParameters))
+ sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA_EXT));

len += Win32.NVME_MAX_LOG_SIZE;
len -= 24;

var buffer = stackalloc byte[len];

var query = (STORAGE_PROPERTY_QUERY*)buffer;
var protocolData = (STORAGE_PROTOCOL_SPECIFIC_DATA*)buffer;
var protocolDataDescr = (STORAGE_PROTOCOL_DATA_DESCRIPTOR*)query->AdditionalParameters;

query->PropertyId = STORAGE_PROPERTY_ID.StorageDeviceProtocolSpecificProperty;
query->QueryType = STORAGE_QUERY_TYPE.PropertyStandardQuery;

protocolData->ProtocolType = STORAGE_PROTOCOL_TYPE.ProtocolTypeNvme;
protocolData->DataType = (uint)STORAGE_PROTOCOL_NVME_DATA_TYPE.NVMeDataTypeLogPage;
protocolData->ProtocolDataRequestValue = (uint)NVME_LOG_PAGES.NVME_LOG_PAGE_HEALTH_INFO;
protocolData->ProtocolDataRequestSubValue = 0;
protocolData->ProtocolDataRequestSubValue2 = 0;
protocolData->ProtocolDataRequestSubValue3 = 0;
protocolData->ProtocolDataRequestSubValue4 = 0;

protocolData->ProtocolDataOffset = (uint)sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = (uint)sizeof(NVME_HEALTH_INFO_LOG);
var hDevice = Win32.CreateFileW(
@"\\.\F:",
Win32.GENERIC_WRITE,
Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
null,
Win32.OPEN_EXISTING,
0,
null);

if (hDevice == Win32.NULL || hDevice == Win32.INVALID_HANDLE_VALUE)
{
throw new Win32Exception();
}

Console.WriteLine($"Device handle: 0x{(nint)hDevice:X}");

var len = (int)(
Marshal.OffsetOf<STORAGE_PROPERTY_SET>(nameof(STORAGE_PROPERTY_SET.AdditionalParameters))
+ sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA_EXT));

len += Win32.NVME_MAX_LOG_SIZE;
len -= 24;

var buffer = stackalloc byte[len];

var query = (STORAGE_PROPERTY_QUERY*)buffer;
var protocolData = (STORAGE_PROTOCOL_SPECIFIC_DATA*)buffer;
var protocolDataDescr = (STORAGE_PROTOCOL_DATA_DESCRIPTOR*)query->AdditionalParameters;

query->PropertyId = STORAGE_PROPERTY_ID.StorageDeviceProtocolSpecificProperty;
query->QueryType = STORAGE_QUERY_TYPE.PropertyStandardQuery;

protocolData->ProtocolType = STORAGE_PROTOCOL_TYPE.ProtocolTypeNvme;
protocolData->DataType = (uint)STORAGE_PROTOCOL_NVME_DATA_TYPE.NVMeDataTypeLogPage;
protocolData->ProtocolDataRequestValue = (uint)NVME_LOG_PAGES.NVME_LOG_PAGE_HEALTH_INFO;
protocolData->ProtocolDataRequestSubValue = 0;
protocolData->ProtocolDataRequestSubValue2 = 0;
protocolData->ProtocolDataRequestSubValue3 = 0;
protocolData->ProtocolDataRequestSubValue4 = 0;

protocolData->ProtocolDataOffset = (uint)sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = (uint)sizeof(NVME_HEALTH_INFO_LOG);
var success = Win32.DeviceIoControl(
hDevice,
Win32.IOCTL_STORAGE_QUERY_PROPERTY,
buffer,
(uint)len,
buffer,
(uint)len,
out uint bytesReturned,
null);

if (!success || bytesReturned == 0)
{
throw new Win32Exception();
}

if (protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)
|| protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))
{
throw new Win32Exception();
}

protocolData = &protocolDataDescr->ProtocolSpecificData;

if (protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)
|| protocolData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG))
{
throw new Win32Exception();
}

var smartInfo = (NVME_HEALTH_INFO_LOG*)(protocolData + protocolData->ProtocolDataOffset);
var temp = (((uint)smartInfo->Temperature[1] << 8) | smartInfo->Temperature[0]) - 273;

Console.WriteLine($"DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log Data - Temperature {temp}.");
Console.WriteLine("DeviceNVMeQueryProtocolDataTest: ***SMART/Health Information Log succeeded***.");
var success = Win32.DeviceIoControl(
hDevice,
Win32.IOCTL_STORAGE_QUERY_PROPERTY,
buffer,
(uint)len,
buffer,
(uint)len,
out uint bytesReturned,
null);

if (!success || bytesReturned == 0)
{
throw new Win32Exception();
}

if (protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)
|| protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))
{
throw new Win32Exception();
}

protocolData = &protocolDataDescr->ProtocolSpecificData;

if (protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)
|| protocolData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG))
{
throw new Win32Exception();
}

var smartInfo = (NVME_HEALTH_INFO_LOG*)(protocolData + protocolData->ProtocolDataOffset);
var temp = (((uint)smartInfo->Temperature[1] << 8) | smartInfo->Temperature[0]) - 273;

Console.WriteLine($"DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log Data - Temperature {temp}.");
Console.WriteLine("DeviceNVMeQueryProtocolDataTest: ***SMART/Health Information Log succeeded***.");
not sure if there's somethinng wrong
JimmahDean
JimmahDeanOPโ€ข2y ago
what if you yeet the exception calls the c++ version i have hits those errors but still gets the right data
ero
eroโ€ข2y ago
SMART/Health Information Log Data - Temperature 4294967023 about right
JimmahDean
JimmahDeanOPโ€ข2y ago
damn that just means both are zero it should be in the realm 40 is your F drive an nvme?
ero
eroโ€ข2y ago
doesn't work with C or D either F is an external ssd also has SMART
JimmahDean
JimmahDeanOPโ€ข2y ago
an external wouldn't be nvme though
ero
eroโ€ข2y ago
i don't even know what nvme means hoenstly lol
JimmahDean
JimmahDeanOPโ€ข2y ago
this is a command specific to nvme drives. smart data on other drives is elsewhere
JimmahDean
JimmahDeanOPโ€ข2y ago
JimmahDean
JimmahDeanOPโ€ข2y ago
this is an nvme drive
ero
eroโ€ข2y ago
yeah i got a 980pro i think?
JimmahDean
JimmahDeanOPโ€ข2y ago
๐Ÿ‘ cdi would say if it's nvme in the interface
JimmahDean
JimmahDeanOPโ€ข2y ago
ero
eroโ€ข2y ago
says nvme on my external lol
ero
eroโ€ข2y ago
JimmahDean
JimmahDeanOPโ€ข2y ago
๐Ÿ‘€ wild idk then
ero
eroโ€ข2y ago
weird!
JimmahDean
JimmahDeanOPโ€ข2y ago
i can't even get it to work on mine, no chance i can get it to work on yours ๐Ÿ˜„ if i sent the compiled exe would you mind running it? it needs to be run as admin and it might get flagged as a trojan (it did when i downloaded it at home yesterday)
ero
eroโ€ข2y ago
i'm not running someone else's exe lol upload the source code and i'll build it
JimmahDean
JimmahDeanOPโ€ข2y ago
completely understandable
JimmahDean
JimmahDeanOPโ€ข2y ago
ero
eroโ€ข2y ago
same thing er, sorry, same thing as you
ero
eroโ€ข2y ago
JimmahDean
JimmahDeanOPโ€ข2y ago
gotcha i'm chalking this whole thing up to a windows bug but mtreit disagrees despite not wanting to help ๐Ÿ˜…
ero
eroโ€ข2y ago
is the goal of the code just to get the drive's temperature?
JimmahDean
JimmahDeanOPโ€ข2y ago
it's to get all of this
JimmahDean
JimmahDeanOPโ€ข2y ago
the temp's just what's in the example and if it gets the temp right, it gets everything right, in theory hypothetically it can be extrapolated to get detailed error logs as well, but i tried using a precompiled c++ app that supposedly retrieved them using these calls and not even that worked so idk
mtreit
mtreitโ€ข2y ago
If it was a Windows bug your C++ code would not work.
JimmahDean
JimmahDeanOPโ€ข2y ago
you don't think there could be an interop bug going on?
mtreit
mtreitโ€ข2y ago
What feature of Windows do you think is involved there? If there was some kind of interop bug that would be a .NET issue. Not a Windows issue.
JimmahDean
JimmahDeanOPโ€ข2y ago
distinction without a difference imo
sealsrock
sealsrockโ€ข2y ago
.NET is a feature of Windows, it is built-in
mtreit
mtreitโ€ข2y ago
.NET Framework is. .NET is not.
Accord
Accordโ€ข2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server