Параметры Pinvoke DeviceIoControl

Я работаю над проектом С#, используя DeviceIoControl. Я просил связать страницу Pinvoke.net для моей подписи:

[DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
public static extern bool DeviceIoControl(
    SafeFileHandle hDevice,
    EIOControlCode IoControlCode,

    [MarshalAs(UnmanagedType.AsAny)]
    [In] object InBuffer,
    uint nInBufferSize,

    [MarshalAs(UnmanagedType.AsAny)]
    [Out] object OutBuffer,
    uint nOutBufferSize,

    out uint pBytesReturned,
    [In] IntPtr Overlapped
    );

Я раньше не видел object и [MarshalAs( UnmanagedType.AsAny )], но Документация MSDN выглядела многообещающе:

Динамический тип, определяющий тип объекта во время выполнения и маршалирует объект как этот тип. Этот член действителен только для методов вызова платформы.

Мой вопрос: что такое "лучший" и/или "правильный" способ использования этой подписи?

Например, IOCTL_STORAGE_QUERY_PROPERTY ожидает, что InBuffer будет STORAGE_PROPERTY_QUERY. Похоже, я должен был бы определить эту структуру, создать экземпляр new и передать его моей подписке Pinvoke:

var query = new STORAGE_PROPERTY_QUERY { PropertyId = 0, QueryType = 0 };
DeviceIoControl(..., query, Marshal.SizeOf(query), ...);

Тем не менее, я просто сделал System.ExecutionEngineException, так что я изменил на что-то вроде:

int cb = Marshal.SizeOf(typeof(...));
IntPtr query = Marshal.AllocHGlobal(cb);
...
Marshal.PtrToStructure(...);
Marshal.FreeHGlobal(query);

и он, по крайней мере, не выдавал никаких исключений, когда я его вызывал. Это просто очень уродливо, и огромная боль в прикладе. Не удается ли маршаллеру копировать данные в/из моих локальных структур, как я надеялся?

Выходные данные иногда могут быть сложными, поскольку они не являются структурами фиксированного размера. Я понимаю, что маршаллер не может справиться с этим автоматически, и я в порядке с HGlobal и копией бизнеса, где мне нужно.

Дополнительно:

Этот вопрос сначала выглядел полезным, но он оказался просто неправильной константой.

Я не против использования конструкций unsafe. (Члены fixed -size struct требуют этого.)

Ответ 1

DeviceIoControl довольно недружелюбен. Но вы можете сделать это менее болезненным, вам не нужно самостоятельно создавать структуры. Две вещи, которые вы можете использовать: С# поддерживает перегрузки методов, и маркерщик pinvoke поверит вам, даже если вы лежите сквозь зубы в декларации. Что идеально подходит для структур, они уже маршалируются как капля байтов. Только то, что требуется DeviceIoControl().

Итак, общая декларация будет выглядеть так:

[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool DeviceIoControl(
    SafeFileHandle hDevice,
    int IoControlCode,
    byte[] InBuffer,
    int nInBufferSize,
    byte[] OutBuffer,
    int nOutBufferSize,
    out int pBytesReturned,
    IntPtr Overlapped
);

И вы добавили бы перегрузку, идеально подходящую для IOCTL_STORAGE_QUERY_PROPERTY, если вы заинтересованы в ее возврате STORAGE_DEVICE_DESCRIPTOR:

[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool DeviceIoControl(
    SafeFileHandle hDevice,
    EIOControlCode IoControlCode,
    ref STORAGE_PROPERTY_QUERY InBuffer,
    int nInBufferSize,
    out STORAGE_DEVICE_DESCRIPTOR OutBuffer,
    int nOutBufferSize,
    out int pBytesReturned,
    IntPtr Overlapped
);

И вы бы назвали это следующим образом:

var query = new STORAGE_PROPERTY_QUERY { PropertyId = 0, QueryType = 0 };
var qsize = Marshal.SizeOf(query);
STORAGE_DEVICE_DESCRIPTOR result;
var rsize = Marshal.SizeOf(result);
int written;
bool ok = DeviceIoControl(handle, EIOControlCode.QueryProperty, 
             ref query, qsize, out result, rsize, out written, IntPtr.Zero);
if (!ok) throw new Win32Exception();
if (written != rsize) throw new InvalidOperationException("Bad structure declaration");

Что должно выглядеть красивее и гораздо более диагностируемым, чем то, что у вас есть. Непроверенный, должен быть близок.