Объясните, какие проблемы могут быть у этой функции (если есть)

СЦЕНАРИЙ

При P/Invoking я подумал, что было бы неплохо упростить/сократить тонны кода, разработав универсальную функцию, которая вызывает функцию, а затем проверяет GetLastWin32Error

Я использую этот код:

''' <summary>
''' Invokes the specified encapsulated function, trying to provide a higher safety level for error-handling.
''' If the function that was called using platform invoke has the <see cref="DllImportAttribute.SetLastError"/>,
''' then it checks the exit code returned by the function, and, if is not a success code, throws a <see cref="Win32Exception"/>.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <typeparam name="T"></typeparam>
''' 
''' <param name="expr">
''' The encapsulated function.
''' </param>
''' ----------------------------------------------------------------------------------------------------
''' <returns>
''' The type of the return value depends on the function definition.
''' </returns>
''' ----------------------------------------------------------------------------------------------------
''' <exception cref="Win32Exception">
''' Function 'X' thrown an unmanaged Win32 exception with error code 'X'.
''' </exception>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Private Shared Function SafePInvoke(Of T)(ByVal expr As Expression(Of Func(Of T))) As T

    Dim result As T = expr.Compile.Invoke()

    Dim method As MethodInfo =
        CType(expr.Body, MethodCallExpression).Method

    Dim isSetLastError As Boolean =
        method.GetCustomAttributes(inherit:=False).
               OfType(Of DllImportAttribute)().FirstOrDefault.SetLastError

    If isSetLastError Then

        Dim exitCode As Integer = Marshal.GetLastWin32Error

        If exitCode <> 0 Then
            Throw New Win32Exception([error]:=exitCode,
                                     message:=String.Format("Function '{0}' thrown an unmanaged Win32 exception with error code '{1}'.",
                                                            method.Name, CStr(exitCode)))
        End If

    End If

    Return result

End Function

Я думаю, что для повышения эффективности это должно заключаться в том, что функция API должна иметь возможность установить последнюю ошибку, и, когда функция может это сделать, я должен установить для атрибута SetLastError значение True. , конечно, SetLastError=True будет проигнорирован, если функция, естественно, не установит последнюю ошибку, поэтому в любом случае, независимо от того, какую функцию я перейду к этой общей функции SafePinvoke, она не даст «ложное срабатывание» или по крайней мере я так думаю.

Тогда пример использования должен быть таким:

  • Во-первых, мы ищем функцию API, которая управляет последней ошибкой, например FindWindow

  • Во-вторых, мы добавляем определение в наш код, устанавливая атрибут SetLastError в подписи.

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function FindWindow(
                            ByVal lpClassName As String,
                            ByVal zero As IntPtr
    ) As IntPtr
    End Function
    
  • Наконец, мы его используем.

    Dim lpszParentClass As String = "Notepad"
    
    Dim parenthWnd As IntPtr =
        SafePInvoke(Function() FindWindow(lpszParentClass, IntPtr.Zero))
    
    If parenthWnd = IntPtr.Zero Then
        MessageBox.Show(String.Format("Window found with HWND: {0}", CStr(parenthWnd)))
    
    Else
        MessageBox.Show("Window not found.")
    
    End If
    

На данный момент мы видим, что все работает, как и ожидалось, если окно найдено, оно вернет ненулевой Intptr, если окно не найдено, оно вернет Intptr.Zero , и, если функция не работает из-за пустой строки, она выдаст Win32Exception с кодом ошибки 123, который относится к:

ОШИБКА_INVALID_NAME

123 (0x7B)

The filename, directory name, or volume label syntax is incorrect.

Все кажется в порядке.

ВОПРОС

Мне нужно сказать это, чтобы аргументировать причину моего вопроса, я не буду вызывать ничего негативного, но случилось так, что некоторые очень опытные программисты сказали, что моя функция небезопасна, потому что я ошибаюсь во многих вещах о GetLastWin32Error, в эта тема:

https://stackoverflow.com/questions/30878232/check-at-run-time-whether-a-p-invoke-function-has-the-dllimportattribute-setlast/30881540?noredirect=1#comment49821007_30881540

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

Я хотел бы улучшить или, в случае необходимости, полностью удалить и переосмыслить подход универсальной функции SafePinvoke выше, если он действительно не мог работать должным образом в обстоятельствах «X», просто я хотел бы увидеть и узнать, какие обстоятельства могут быть что, предоставив реальный образец кода, который можно протестировать для демонстрации ошибки/конфликта, мой вопрос:

Кто-нибудь может проиллюстрировать на реальном примере кода API-функции, что при передаче через функцию SafePinvoke, описанную выше, она может давать «ложноположительную» ошибку или конфликт другого рода?

Кто-нибудь мог бы направить меня и объяснить, действительно ли функция SafePinvoke безопасна или небезопасна, или ее можно улучшить, и что?, предоставив пример кода, который можно протестировать?.

Я буду очень признателен за любую информацию, которая может помочь мне улучшить этот подход или узнать, что этот подход действительно не будет работать в некоторых обстоятельствах, но, пожалуйста, приведите пример кода, демонстрирующий это.


person ElektroStudios    schedule 17.06.2015    source источник
comment
Я не уверен в этом, но нет ли там DllImportAttribute.SetLastError, чтобы среда выполнения .NET могла выдать Win32Exception с кодом ошибки, если он был установлен? То есть, разве среда выполнения .NET с помощью этого флага пользовательского атрибута уже не делает именно то, что вы делаете в своем методе SafePInvoke?   -  person stakx - no longer contributing    schedule 17.06.2015
comment
@stakx SetLastError=true приводит к тому, что платформа p/invoke вызывает GetLastError сразу после возврата внешней функции. Этот статус ошибки отмечается и затем возвращается GetLastWin32Error.   -  person David Heffernan    schedule 17.06.2015
comment
@DavidHeffernan: Спасибо за объяснение. (Похоже, я путаю это с COM-взаимодействием, где среда выполнения по умолчанию автоматически превращает HRESULTs, указывающую на состояние ошибки, в ComExceptions. Я полагал, что CLR будет делать что-то подобное для вызовов платформы, отличной от COM.)   -  person stakx - no longer contributing    schedule 17.06.2015
comment
Во-первых, ваш способ его использования неполный - поскольку ваш метод однозначно выдает исключение, когда exitCode <> 0, клиентскому коду необходимо использовать Try/Catch. Это не облегчает PInvoke, ИМО. Затем вам нужно быть абсолютно уверенным, что exitCode верен, чтобы это было полезно, а вы нет, иначе вы бы не опубликовали это. Далее, result иногда является данными (например, hwnd или text len), иногда это код ошибки T/F. Каждая функция WinAPI определяет свой собственный возврат, но этот универсальный метод не учитывает этого. Перечитайте комментарии Антона Тихого, связанные с,   -  person Ňɏssa Pøngjǣrdenlarp    schedule 17.06.2015


Ответы (1)


Из документации GetLastError:

В разделе «Возвращаемое значение» документации для каждой функции, которая устанавливает код последней ошибки, указаны условия, при которых функция устанавливает код последней ошибки. Большинство функций, которые устанавливают код последней ошибки потока, устанавливают его при сбое. Однако некоторые функции также устанавливают код последней ошибки при успешном выполнении. Если функция не задокументирована для установки кода последней ошибки, значение, возвращаемое этой функцией, является просто самым последним установленным кодом последней ошибки; некоторые функции устанавливают код последней ошибки равным 0 в случае успеха, а другие нет.

Другими словами, безоговорочный вызов GetLastError является ошибкой. То, что GetLastError возвращает ненулевое значение, не означает, что последний вызов API завершился неудачно. Более того, GetLastError не возвращает ноль, что означает успех.

person David Heffernan    schedule 17.06.2015
comment
Теперь, я вижу, только ту часть документации, которую я не читал. Спасибо за объяснения и извините за первоначальное обсуждение этого вопроса, я вижу, что был неправ. но, тем не менее, вы знаете название функции, которая устанавливает ненулевой код ошибки? вы, с вашим опытом, можете сказать, что это несколько функций, которые действуют так, или их очень много, чтобы принимать во внимание вообще?. - person ElektroStudios; 17.06.2015
comment
Есть функции, которые завершаются успешно и устанавливают для последней ошибки ненулевое значение. Как CreateMutex. Есть примеры, на которые я указал в последнем вопросе, когда функция завершается ошибкой, но GetLastError возвращает 0. И есть другие функции, которые завершаются успешно, и GetLastError возвращает 0, но я не знаю примеров. Видите ли, у меня нет привычки безоговорочно вызывать GetLastError, поэтому я никогда не вижу, чтобы это происходило. - person David Heffernan; 17.06.2015