我注意到Marshal.GetLastWin32Error()
在p / invoke到Kernal32.CloseHandle(IntPtr p_handle)
之后的dispose方法期间返回错误122,即使导入属性SetLastError
在导入定义中设置为true也是如此。在关闭句柄之前,我跟踪原因归结为new WindowsIdentity
的创建,我想知道处理这个问题的正确方法是什么。
new WindowsIdentity(DangereousHandle)
第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);
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.
}
}
}
答案 0 :(得分:3)
第1点似乎就是我应该做的事情。
是的,这完全正确。忘记问题的.NET P / Invoke方面,看看original function description:
返回值
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用 GetLastError 。
说明并没有说当函数成功时调用GetLastError
是有意义的,并且正如您所知,它确实没有意义,因此您不应该调用它在那种情况下。