【问题标题】:Shouldn't GetLastWin32Error be reset if P/Invoke attribute SetLastError = true?如果 P/Invoke 属性 SetLastError = true,是否不应该重置 GetLastWin32Error?
【发布时间】:2014-08-08 15:27:48
【问题描述】:

我注意到Marshal.GetLastWin32Error() 在对Kernal32.CloseHandle(IntPtr p_handle) 执行p/invoke 后的dispose 方法期间返回错误122,即使在导入定义中将导入属性SetLastError 设置为true。我将原因追踪到在句柄关闭之前创建new WindowsIdentity,我想知道处理这个问题的正确方法是什么。

  1. 是否应该仅在 CloseHandle 返回 false 时检查 Win32 错误?
  2. 在 p/调用 CloseHandle 之前强制 SetLastError 为 0?
  3. new WindowsIdentity(DangereousHandle) 之后将LastError 强制设置为0

第 3 点对我来说似乎很不合理,所以我认为这显然不是。

第 2 点似乎最合理,但可能是不必要的激进。我选择这个而不是第 1 点的唯一原因是因为我不确定我是否能保证如果 CloseHandle 返回 false 错误代码将是相关的。功能文档能保证这一点吗?

第 1 点似乎是我应该做的。也就是说,我应该只在 CloseHandle 显式返回 false 时调用 GetLastWin32Error,并假设返回的代码将指示 CloseHandle 失败的原因,而不是一些先前不相关的 Win API 调用。

代码

using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;

namespace Common.InterOp
{
    // CloseHandle http://msdn.microsoft.com/en-ca/library/windows/desktop/ms724211%28v=vs.85%29.aspx

    internal static partial class Kernel32
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [SuppressUnmanagedCodeSecurity]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool CloseHandle(IntPtr handle);
    }
}

这里是关闭句柄被调用的地方

using Common.InterOp;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace Common.InterOp
{
    // http://msdn.microsoft.com/en-us/library/system.security.principal.windowsimpersonationcontext%28v=vs.100%29.aspx

    public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle()
            : base(true)
        { }

        protected override bool ReleaseHandle()
        {
            int Win32Error =
                Marshal.GetLastWin32Error(); // <-- Already has code 122

            var Closed =
                Kernel32.CloseHandle(handle); // <-- has SetLastError=true attribute

            Win32Error =
                Marshal.GetLastWin32Error(); // <-- Still has code 122

            if (!Closed && Win32Error != 0) // This works, but is it correct?
                throw
                    new Win32Exception(Win32Error);

            return Closed;
        }
    }
}

这是在具有 var WinID = new WindowsIdentity(DangerousHandle); 的行上实际生成错误 122 的代码块

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using Common.InterOp;
using Common.Environment;

namespace Common.Authentication
{
    public static partial class Authentication
    {
        /// <summary>
        /// Authenticates the log on credentials of a Windows user.
        /// Returns a WindowsIdentity if authentication succeeds for the specified user; Otherwise returns null.
        /// </summary>
        /// <param name="p_userName">The user's Windows account name.</param>
        /// <param name="p_password">The user's Windows account password.</param>
        /// <param name="p_domainName">The user's domain name or the name of the local machine for non-networked accounts.</param>
        /// <returns>a <seealso cref="WindowsIdentity"/> if authentication succeeds for the specified user; Otherwise returns null.</returns>
        /// <exception cref="System.ArgumentException"></exception>
        /// <exception cref="System.ComponentModel.Win32Exception"></exception>
        /// <exception cref="System.Security.SecurityException"></exception>
        public static WindowsIdentity LogonUser(SecureString p_userName, SecureString p_password, SecureString p_domainName, LogonUserOptions p_logonOptions)
        {
            // The following functions seem related, but it's unclear if or in what scenario they would ever be required
            // DuplicateToken   http://msdn.microsoft.com/en-us/library/windows/desktop/aa446616%28v=vs.85%29.aspx 
            // DuplicateTokenEx http://msdn.microsoft.com/en-us/library/windows/desktop/aa446617%28v=vs.85%29.aspx

            SafeTokenHandle UserAccountToken = null;

            try
            {
                using (var DecryptedUserName = new DecryptAndMarshallSecureString(p_userName))
                using (var DecryptedPassword = new DecryptAndMarshallSecureString(p_password))
                using (var DecryptedDomainName = new DecryptAndMarshallSecureString(p_domainName))
                {
                    // Call LogonUser, passing the unmanaged (and decrypted) copies of the SecureString credentials.
                    bool ReturnValue =
                        AdvApi32.LogonUser(
                            p_userName: DecryptedUserName.GetHandle(),
                            p_domainName: DecryptedDomainName.GetHandle(),
                            p_password: DecryptedPassword.GetHandle(),
                            p_logonType: p_logonOptions.Type,           // LogonType.LOGON32_LOGON_INTERACTIVE,
                            p_logonProvider: p_logonOptions.Provider,   // LogonProvider.LOGON32_PROVIDER_DEFAULT,
                            p_userToken: out UserAccountToken);

                    // Get the Last win32 Error and throw an exception.
                    int Win32Error = Marshal.GetLastWin32Error();
                    if (!ReturnValue && UserAccountToken.DangerousGetHandle() == IntPtr.Zero)
                        throw
                            new Win32Exception(Win32Error);

                    // The token that is passed to the following constructor must  
                    // be a primary token in order to use it for impersonation.

                    var DangerousHandle = UserAccountToken.DangerousGetHandle();

                    Win32Error = Marshal.GetLastWin32Error(); // No error

                    var WinID = new WindowsIdentity(DangerousHandle);

                    Win32Error = Marshal.GetLastWin32Error(); // error 122

                    // Kernel32.SetLastError(0); // Resets error to 0
                    Sleep.For(0);
                    //new Win32Exception(Win32Error); // Also resets error to 0

                    Win32Error = Marshal.GetLastWin32Error(); // Still error 122 unless one of the reset lines above is uncommented

                    return WinID;
                }
            }
            finally
            {
                if (UserAccountToken != null)
                    UserAccountToken.Dispose(); // This calls CloseHandle which is where I first noticed the error being non-zero
            }
        }
    }
}

编辑 2014, 08, 09

根据 hvd 的 cmets 和答案更新了类。

using Common.Attributes;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Runtime.ConstrainedExecution;

namespace Common.InterOp
{
    // SafeHandle.ReleaseHandle Method http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle.releasehandle%28v=vs.110%29.aspx
    // http://msdn.microsoft.com/en-us/library/system.security.principal.windowsimpersonationcontext%28v=vs.100%29.aspx
    // http://stackoverflow.com/questions/25206969/shouldnt-getlastwin32error-be-reset-if-p-invoke-attribute-setlasterror-true

    [jwdebug(2014, 08, 09, "releaseHandleFailed MDA http://msdn.microsoft.com/en-us/library/85eak4a0%28v=vs.110%29.aspx")]
    public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle()
            : base(true)
        { }

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        override protected bool ReleaseHandle()
        {
            // Here, we must obey all rules for constrained execution regions. 

            return
                Kernel32.CloseHandle(handle);

            // If ReleaseHandle failed, it can be reported via the 
            // "releaseHandleFailed" managed debugging assistant (MDA).  This
            // MDA is disabled by default, but can be enabled in a debugger 
            // or during testing to diagnose handle corruption problems. 
            // We do not throw an exception because most code could not recover 
            // from the problem.
        }
    }
}

【问题讨论】:

  • 虽然与您的实际问题无关,但看起来您根本不应该抛出任何异常。 SafeHandle.ReleaseHandle 上的示例甚至使用 CloseHandle,就像您的课程一样:如果失败,它只会返回 false
  • @hvd 太棒了...现在我需要在某个时候阅读 MDA...也许某个晚上我失眠的时候。感谢您提供额外信息!我将使用现在的代码为我的问题添加一个编辑。

标签: c# .net-4.0 pinvoke unmanaged


【解决方案1】:

第 1 点似乎是我应该做的。

是的,完全正确。忘记你问题的 .NET P/Invoke 方面,看看original function description

返回值

如果函数成功,返回值非零。

如果函数失败,返回值为零。要获取扩展错误信息,请调用 GetLastError

描述并没有说当函数成功时调用GetLastError是有意义的,正如你发现的,它确实没有意义,所以你不应该在这种情况下调用它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-03
    相关资源
    最近更新 更多