Зачем мне нужен DangerousAddRef/DangerousRelease рядом с DangerousGetHandle?

Я изучал пакет 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.

Есть идеи, почему нам нужно увеличить количество ссылок здесь?

Нет, SafeHandle предназначен не для предотвращения изменений, а для предотвращения случайного закрытия дескриптора. Конкретная проблема с классами, которые имеют дело с дескрипторами: у них всегда есть финализатор, и сборщик мусора не может видеть, что дескриптор используется функцией winapi. Кстати, это некрасивый код.

Hans Passant 29.06.2024 04:44

Использование COM-объекта с дополнительными AddRef и Release гарантирует, что ваш код не выйдет из строя или не выйдет из строя, если какая-то другая часть кода (например, в сценариях многопоточности) вызывает для него последний необходимый Release и таким образом освобождает объект из памяти, пока вы его используете. используешь его. Обычно в этом нет необходимости, поскольку, когда вы получили ссылку COM, у вас уже был неявный AddRef, выполненный вызываемым кодом, но генераторы тупы и не имеют подробной информации об условиях, которые привели сюда.

Simon Mourier 29.06.2024 07:10

Да, я определенно согласен в случае с COM — сам факт наличия SafeHandle означает, что мы храним ссылку COM на объект, поэтому нам не нужно увеличивать/уменьшать счетчик для этой цели. Меня больше интересует ситуация, не связанная с COM. В этом примере это функция Win32.

Matt Tsōnto 30.06.2024 23:11

Думаю, меня смущает то, что я не понимаю предполагаемую модель обмена SafeHandle. Поскольку это IDisposable, я предположил, что у него есть четкий владелец, и что владелец несет ответственность за то, чтобы не утилизировать его, пока он не перестанет использоваться. Но DangerousAddRef/DangerousRelease подразумевают своего рода обмен без координации.

Matt Tsōnto 30.06.2024 23:30
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Из официальных документов:

SafeHandle.DangerousGetHandle

Использование метода DangerousGetHandle может представлять угрозу безопасности, поскольку: если дескриптор помечен как недействительный с помощью SetHandleAsInvalid, DangerousGetHandle по-прежнему возвращает оригинал, возможно, устаревший значение ручки. Возвращенный дескриптор также может быть переработан в любой момент. В лучшем случае это означает, что ручка может внезапно перестать работать. В худшем случае, если дескриптор или ресурс, который представляет дескриптор, подвергается воздействию ненадежный код, это может привести к повторной атаке безопасности на повторно использованная или возвращенная ручка. Например, вызывающий абонент, которому не доверяют, может запросить данные о только что возвращенном дескрипторе и получить информацию для совершенно не связанный ресурс.

[курсив мой:] Посмотрите на DangerousAddRef и DangerousRelease методы для получения дополнительной информации об использовании DangerousGetHandle метод безопасный.

SafeHandle.DangerousAddRef

Метод DangerousAddRef предотвращает освобождение памяти, используемой дескриптором (что происходит, когда среда выполнения вызывает метод ReleaseHandle). Вы можете использовать этот метод, чтобы вручную увеличить счетчик ссылок на экземпляре SafeHandle. [...].

Похоже, это сделано для того, чтобы дескриптор не стал недействительным во время его использования.

Другие вопросы по теме