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 можно применять к классам.
Отправить комментарий