Как правильно замаршалить ptr-to-ptr, принимающий NULL
От: Mr. None Россия http://mrnone.blogspot.com
Дата: 17.02.11 15:36
Оценка: 24 (2)
Есть такая WIN API функция:

DWORD GetNamedSecurityInfo(
  LPTSTR pObjectName,
  SE_OBJECT_TYPE ObjectType,
  SECURITY_INFORMATION SecurityInfo,
  PSID* ppsidOwner,
  PSID* ppsidGroup,
  PACL* ppDacl,
  PACL* ppSacl,
  PSECURITY_DESCRIPTOR* ppSecurityDescriptor
);


В числе прочих она принимает 4 опциаональных указателя на указатель: ppsidOwner, ppsidGroup, ppDacl и ppSacl. Какждый из этих параметров может принимать NULL — это охначает, что данные тебе не нужны.

Возникла необходимость вызвать эту функцию из C# и встал вопрос, как же её корректно объявить.


Решение в лоб не работает:
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern uint
GetNamedSecurityInfo(
    string pObjectName,
    SE_OBJECT_TYPE objectType,
    SECURITY_INFORMATION securityInfo,
    ref IntPtr pSidOwner,
    ref IntPtr pSidGroup,
    ref IntPtr pDacl,
    ref IntPtr pSacl,
    ref IntPtr pSecurityDescriptor);

ибо в этом случае передать null или IntPtr.Zero в опциональные параметры не получится.


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

Итак, решение первое — ручное получение указателя на указатель с помощью AllocHGlobal / StructureToPtr
// декларация
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern uint
GetNamedSecurityInfo(
    string pObjectName,
    SE_OBJECT_TYPE objectType,
    SECURITY_INFORMATION securityInfo,
    IntPtr pSidOwner,
    IntPtr pSidGroup,
    IntPtr pDacl,
    IntPtr pSacl,
    ref IntPtr pSecurityDescriptor);

//...
// использование:

// сделали pointer-to-pointer вручную
IntPtr ppSid = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
Marshal.StructureToPtr(IntPtr.Zero, ppSid, false);

// вызвали (NULL в данном случае - это IntPtr.Zero)
uint result = NativeMethods.GetNamedSecurityInfo(name,
    NativeMethods.SE_OBJECT_TYPE.SE_FILE_OBJECT,
    NativeMethods.SECURITY_INFORMATION.Owner,
    ppSid, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, ref sd);

// "разыменовали" результат
IntPtr pSid = (System.IntPtr)Marshal.PtrToStructure(ppSid, typeof(IntPtr));

// не забыли почистить за собой
Marshal.FreeHGlobal(ppSid);



Решение второе: используем тот факт, что указатель-на-указатель — это есть массив указателей из одного элемента:
// декларация
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern uint
GetNamedSecurityInfo(
    string pObjectName,
    SE_OBJECT_TYPE objectType,
    SECURITY_INFORMATION securityInfo,

    [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)]
    IntPtr[] pSidOwner,

    [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)]
    IntPtr[] pSidGroup,

    [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)]
    IntPtr[] pDacl,

    [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)]
    IntPtr[] pSacl,

    ref IntPtr pSecurityDescriptor);

// ...
// использование
// сделали "pointer-to-pointer"
IntPtr[] ppSid = new IntPtr[1];
ppSid[0] = IntPtr.Zero;

// вызвали (NULL в данном случае - это null :) )
uint result = NativeMethods.GetNamedSecurityInfo(name,
    NativeMethods.SE_OBJECT_TYPE.SE_FILE_OBJECT,
    NativeMethods.SECURITY_INFORMATION.Owner,
    ppSid, null, null, null, ref sd);

// "разыменовали"
IntPtr pSid = ppSid[0];

// всё



Преимущества второго варианта очевидны, все операции по выделению памяти, копированию (если они есть) остаются на совести среди. Вот только чиста ли эта совесть? Нет ли здесь каких-то сркытых утечек или проблем, уже больно подозрительным решение кажется . Что скажет многоуважаемый ALL?
Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.