Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

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

Unknown комментирует...

Хорошее замечание! Пожалуй, я обновлю статью.

Уверен, многие даже не догадываются, что StructLayoutAttribute можно применять к классам.

Отправить комментарий

Copyright 2007-2011 Chabster