如果P / Invoke属性SetLastError = true,是否应该重置GetLastWin32Error?

时间:2014-08-08 15:27:49

标签: c# .net-4.0 pinvoke unmanaged

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

  1. 如果CloseHandle返回false,我应该只检查Win32错误吗?
  2. 在p /调用CloseHandle之前强制将SetLastError设置为0?
  3. new WindowsIdentity(DangereousHandle)
  4. 后强制将SetLastError设置为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);
        }
    }
    

    这里是关闭Hand 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
                }
            }
        }
    }
    

    EDIT 2014,08,09

    根据hvd的评论和答案更新课程。

    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.
            }
        }
    }
    

1 个答案:

答案 0 :(得分:3)

  

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

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

  

返回值

     

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

     

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

说明并没有说当函数成功时调用GetLastError是有意义的,并且正如您所知,它确实没有意义,因此您不应该调用它在那种情况下。