Параметры Pinvoke DeviceIoControl

Я работаю над проектом C#, используя 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 будет < структура href="http://msdn.microsoft.com/en-us/library/windows/desktop/ff800840.aspx" rel="nofollow noreferrer">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. (Это требуется для членов struct размера fixed.)


person Jonathon Reinhart    schedule 24.06.2013    source источник
comment
Я знаю, что @HansPassant знает ответ на этот вопрос :-)   -  person Jonathon Reinhart    schedule 25.06.2013
comment
Да, я тоже на это надеялся.   -  person David Heffernan    schedule 25.06.2013


Ответы (1)


DeviceIoControl довольно недружелюбен. Но вы можете сделать это менее болезненным, вам не нужно самостоятельно маршалировать структуры. Две вещи, которыми вы можете воспользоваться: C# поддерживает перегрузку методов, и маршаллер 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");

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

person Hans Passant    schedule 27.06.2013
comment
Это может быть мой лучший вариант. Мне все еще любопытно, как [MarshalAs(UnmanagedType.AsAny)] на [In] object InBuffer должен работать? Всегда ли типы struct будут сортироваться по значению (что, очевидно, здесь неверно), а типы class будут сортироваться по ссылке (указатель на большой двоичный объект)? - person Jonathon Reinhart; 28.06.2013
comment
Я чувствую, что все, кроме самых основных подписей Pinvoke, являются чем-то вроде черной магии, и я не нашел действительно хорошего справочного материала, который помог бы мне лучше понять его (или как его отлаживать, особенно на уровне ассемблера, где такие вещи, как вызов конвенция должна быть диагностирована.) - person Jonathon Reinhart; 28.06.2013
comment
Поскольку Any использовался во взаимодействии VB6, я не знаю его точную семантику. Ключевые слова ref и out обеспечивают создание указателя на структуру. Таким образом, вы получаете LPVOID. Вместо этого вы можете использовать класс, но затем отбросить ref/out и явно написать [Out]. Если вы хотите отладить его, вы можете просто создать C DLL с функцией, которая имеет точно такую ​​же сигнатуру. - person Hans Passant; 28.06.2013
comment
+1, написание перегрузок (каждая из которых использует свой собственный тип struct в качестве параметра) - это способ использовать DeviceIoControl. - person ken2k; 01.07.2013
comment
разве некоторые операции deviceiocontrol не требуют, чтобы буферы ввода/вывода были выровнены по секторам/страницам? Если это так, вам нужно будет выделить память самостоятельно, используя virtualalloc - person user1985513; 03.07.2013
comment
Нет, у них нет такого требования. Может быть проблема с драйвером, а не с пользовательской программой. - person Hans Passant; 03.07.2013
comment
dwIoControlCode — это DWORD, поэтому он должен быть uint IoControlCode вместо int IoControlCode . - person Daniel; 30.12.2014
comment
Это не имеет значения, это просто число. Использование enum, как показано в перегрузке, является разумным выбором. - person Hans Passant; 30.12.2014