Я изучал пакет Microsoft.Windows.CsWin32
NuGet и сравнивал код P/Invoke, который он генерирует, с тем, что я бы написал вручную. В некоторых случаях он использует SafeHandle.DangerousAddRef
/SafeHandle.DangerousRelease
, хотя я бы этого не сделал. Я пытаюсь понять почему, но ничего не придумал.
Вот пример функции Win32 SetInformationJobObject
:
[DllImport("KERNEL32.dll", ExactSpelling = true, SetLastError = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[SupportedOSPlatform("windows5.1.2600")]
internal static extern unsafe winmdroot.Foundation.BOOL SetInformationJobObject(winmdroot.Foundation.HANDLE hJob, winmdroot.System.JobObjects.JOBOBJECTINFOCLASS JobObjectInformationClass, void* lpJobObjectInformation, uint cbJobObjectInformationLength);
/// <inheritdoc cref = "AssignProcessToJobObject(winmdroot.Foundation.HANDLE, winmdroot.Foundation.HANDLE)"/>
[SupportedOSPlatform("windows5.1.2600")]
internal static unsafe winmdroot.Foundation.BOOL AssignProcessToJobObject(SafeHandle hJob, SafeHandle hProcess)
{
bool hJobAddRef = false;
bool hProcessAddRef = false;
try
{
winmdroot.Foundation.HANDLE hJobLocal;
if (hJob is object)
{
hJob.DangerousAddRef(ref hJobAddRef);
hJobLocal = (winmdroot.Foundation.HANDLE)hJob.DangerousGetHandle(); // <-- THIS
}
else
throw new ArgumentNullException(nameof(hJob));
winmdroot.Foundation.HANDLE hProcessLocal;
if (hProcess is object)
{
hProcess.DangerousAddRef(ref hProcessAddRef);
hProcessLocal = (winmdroot.Foundation.HANDLE)hProcess.DangerousGetHandle();
}
else
throw new ArgumentNullException(nameof(hProcess));
winmdroot.Foundation.BOOL __result = PInvoke.AssignProcessToJobObject(hJobLocal, hProcessLocal); // calls the `extern` overload
return __result;
}
finally
{
if (hJobAddRef)
hJob.DangerousRelease();
if (hProcessAddRef)
hProcess.DangerousRelease();
}
}
Я ожидал, что вторая функция будет выглядеть примерно так:
[DllImport("KERNEL32.dll", ExactSpelling = true, SetLastError = true)]
[SupportedOSPlatform("windows5.1.2600")]
internal static unsafe winmdroot.Foundation.BOOL AssignProcessToJobObject(SafeHandle hJob, SafeHandle hProcess)
{
var localJob = (winmdroot.Foundation.HANDLE)hJob.DangerousGetHandle();
var localProcess = (winmdroot.Foundation.HANDLE)hProcess.DangerousGetHandle();
return (winmdroot.Foundation.BOOL)PInvoke.AssignProcessToJobObject(localJob, localProcess);
}
Кажется, он создает защитную копию ручки, но я не понимаю, чем это может помочь. Даже если дескриптор, удерживаемый SafeHandle
, изменится — чего, AFAIK, не должно быть — мы все равно будем передавать некоторый дескриптор в перегрузку extern
.
Я попробовал проверить исходный код генератора на предмет подсказок и нашел коммит, который добавил вызовы, но это мне ничего не сказало. Я предполагаю, что это было основано на том, что маршалер сделал бы с SafeHandle
.
Есть идеи, почему нам нужно увеличить количество ссылок здесь?
Использование COM-объекта с дополнительными AddRef и Release гарантирует, что ваш код не выйдет из строя или не выйдет из строя, если какая-то другая часть кода (например, в сценариях многопоточности) вызывает для него последний необходимый Release и таким образом освобождает объект из памяти, пока вы его используете. используешь его. Обычно в этом нет необходимости, поскольку, когда вы получили ссылку COM, у вас уже был неявный AddRef, выполненный вызываемым кодом, но генераторы тупы и не имеют подробной информации об условиях, которые привели сюда.
Да, я определенно согласен в случае с COM — сам факт наличия SafeHandle
означает, что мы храним ссылку COM на объект, поэтому нам не нужно увеличивать/уменьшать счетчик для этой цели. Меня больше интересует ситуация, не связанная с COM. В этом примере это функция Win32.
Думаю, меня смущает то, что я не понимаю предполагаемую модель обмена SafeHandle
. Поскольку это IDisposable
, я предположил, что у него есть четкий владелец, и что владелец несет ответственность за то, чтобы не утилизировать его, пока он не перестанет использоваться. Но DangerousAddRef
/DangerousRelease
подразумевают своего рода обмен без координации.
Из официальных документов:
Использование метода DangerousGetHandle может представлять угрозу безопасности, поскольку: если дескриптор помечен как недействительный с помощью
SetHandleAsInvalid
,DangerousGetHandle
по-прежнему возвращает оригинал, возможно, устаревший значение ручки. Возвращенный дескриптор также может быть переработан в любой момент. В лучшем случае это означает, что ручка может внезапно перестать работать. В худшем случае, если дескриптор или ресурс, который представляет дескриптор, подвергается воздействию ненадежный код, это может привести к повторной атаке безопасности на повторно использованная или возвращенная ручка. Например, вызывающий абонент, которому не доверяют, может запросить данные о только что возвращенном дескрипторе и получить информацию для совершенно не связанный ресурс.[курсив мой:] Посмотрите на
DangerousAddRef
иDangerousRelease
методы для получения дополнительной информации об использованииDangerousGetHandle
метод безопасный.
Метод DangerousAddRef предотвращает освобождение памяти, используемой дескриптором (что происходит, когда среда выполнения вызывает метод ReleaseHandle). Вы можете использовать этот метод, чтобы вручную увеличить счетчик ссылок на экземпляре SafeHandle. [...].
Похоже, это сделано для того, чтобы дескриптор не стал недействительным во время его использования.
Нет, SafeHandle предназначен не для предотвращения изменений, а для предотвращения случайного закрытия дескриптора. Конкретная проблема с классами, которые имеют дело с дескрипторами: у них всегда есть финализатор, и сборщик мусора не может видеть, что дескриптор используется функцией winapi. Кстати, это некрасивый код.