Marshal NULL for a pointer to a struct
Памаретрами многих функций WinAPI являются указатели на структуры или простые интегральные типы. Часто функция
позволяет опускать такие параметры путем передачи NULL. Как тогда быть с .NET маршалингом если
структура не является указателем? Два решения далее...
Прототип тестируемой функции следующий:
HANDLE WINAPI CreateSemaphore( __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, __in LONG lInitialCount, __in LONG lMaximumCount, __in_opt LPCTSTR lpName );
NULL попытаемся передавать в lpSemaphoreAttributes.
Решение 1 - передача NULL в лоб
Известно, что размер указателя фиксирован для определенной архитектуры. На x86 он равен 4-м байтам. Грубо говоря,
указатель - это число. В .NET существует специальный тип, представляющий машинный указатель, - IntPtr.
Кстати, его ввели потому, что размер int (он же Int32) фиксирован и на любой платформе равен
4-м байтам, а размер IntPtr зависит от платформы. Этим мы и воспользуемся для передачи NULL:
[DllImport("kernel32.dll", SetLastError = true)] static public extern IntPtr CreateSemaphore(IntPtr lpSemaphoreAttributes, Int32 lInitialCount, Int32 lMaximumCount, String lpName); static private void Main(string[] args) { IntPtr semaphore = CreateSemaphore(IntPtr.Zero, 0, 10, "TestSemaphore"); if (semaphore == IntPtr.Zero) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } }
На низком уровне типы LPSECURITY_ATTRIBUTES и IntPtr одинаковы и представляют собой
машинный указатель. Поэтому можно смело передавать машинный NULL, которым IntPtr.Zero и
является.
Решение 2 - unsafe код
[StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public Int32 nLength; public IntPtr lpSecurityDescriptor; public Int32 bInheritHandle; } [DllImport("kernel32.dll", SetLastError = true)] static public extern unsafe IntPtr CreateSemaphore(SECURITY_ATTRIBUTES* lpSemaphoreAttributes, Int32 lInitialCount, Int32 lMaximumCount, String lpName); static private unsafe void Main(string[] args) { IntPtr semaphore = CreateSemaphore(null, 0, 10, "TestSemaphore"); if (semaphore == IntPtr.Zero) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } }
Этот вариант отличается типом параметра lpSemaphoreAttributes - SECURITY_ATTRIBUTES*,
который теперь в точности совпадает с объявлением WinAPI. Соответственно, можно использовать null в
качестве значения этого параметра.
Преимущества и недостатки
Первое, что мне захотелось сделать - объявить lpSemaphoreAttributes, как
Nullable<SECURITY_ATTRIBUTES>. Это ведь чертовски логично! Но, нет, не судьба - Cannot marshal
'parameter #1': Generic types cannot be marshaled.
Далее, я попытался использовать следующую сигнатуру:
[DllImport("kernel32.dll", SetLastError = true)] static public extern IntPtr CreateSemaphore(ref SECURITY_ATTRIBUTES lpSemaphoreAttributes, Int32 lInitialCount, Int32 lMaximumCount, String lpName);
Вот только код, инициализирующий структуру lpSemaphoreAttributes в пустой набор, анологичный передаче
NULL, получился большой и некрасивый, а структура, заполненная нулями, не годится. null,
понятное дело, передать нельзя.
В результате остановился на двух вышеописанных решениях. Правда, они хороши лишь для случая, когда нужно передать
NULL. Второй вариант, к тому же, напрягает использованием
unsafe кода, хотя, фактически, меняется
только сигнатура метода. Оба варианта вносят непростительную сложность при передаче не-NULL значения.
Получается, для удобного использования функции с множеством параметров нужно создавать множество функций с
подходящими для конкретного случая сигнатурами! И банальная поддержка маршалинга Nullable<T> решила
бы эту проблему.
2 коммент.:
Как насчет использование вместо структур классов?
Если все используемые структуры передаются по указателю и только в Unmanaged код (без возврата заполненной структуры)то решение с классами гарантированно работает даже в delphi.net
Хорошее замечание! Пожалуй, я обновлю статью.
Уверен, многие даже не догадываются, что StructLayoutAttribute можно применять к классам.
Отправить комментарий