Skip to content

Commit

Permalink
SafeHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
XgHao committed Apr 10, 2020
1 parent d47f7e8 commit 11ec107
Show file tree
Hide file tree
Showing 8 changed files with 453 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CSharpMore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeDemo", "Attribute\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstract", "Abstract\Abstract.csproj", "{2D426C0D-31EF-4F24-9A79-70B5AB2AFF5F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeHandleDemo", "SafeHandleDemo\SafeHandleDemo.csproj", "{B6D20E34-8E4C-4ED4-B6EF-4DB97FA0E166}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -189,6 +191,10 @@ Global
{2D426C0D-31EF-4F24-9A79-70B5AB2AFF5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D426C0D-31EF-4F24-9A79-70B5AB2AFF5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D426C0D-31EF-4F24-9A79-70B5AB2AFF5F}.Release|Any CPU.Build.0 = Release|Any CPU
{B6D20E34-8E4C-4ED4-B6EF-4DB97FA0E166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6D20E34-8E4C-4ED4-B6EF-4DB97FA0E166}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6D20E34-8E4C-4ED4-B6EF-4DB97FA0E166}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6D20E34-8E4C-4ED4-B6EF-4DB97FA0E166}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
6 changes: 6 additions & 0 deletions SafeHandleDemo/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
147 changes: 147 additions & 0 deletions SafeHandleDemo/MyFileReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SafeHandleDemo
{
/// <summary>
/// The MyFileReader class is a sample class that accesses an operating system resource and implements IDisposable.
/// MyFileReader类是访问操作系统资源并实现IDisposeable的示例类。
/// This is useful to show the types of transformation required to make your resource wrapping classess more resilient.
/// 这有助于显示使资源包装类更具弹性所需的转换类型。
/// Note the Dispose and Finalize implementations.Consider this a simulation of System.IO.FileStream.
/// 注意Dispose和Finalize实现,假设这是对System.IO.FileStream的模拟
/// </summary>
public class MyFileReader : IDisposable
{
/// <summary>
/// _handle is set to null to indicate disposal of this instance.
/// _handle设置为null以指示此实例的处置
/// </summary>
private MySafeFileHandle _handle;

public MyFileReader(string fileName)
{
string fullPath = Path.GetFullPath(fileName);
//如果未对调用堆栈中处于较高位置的所有调用方授予当前实例所指定的权限,则在运行时强制 System.Security.Security.SecurityException
new FileIOPermission(FileIOPermissionAccess.Read, fullPath).Demand();

//Open a file, and save its handle in _handle.
//打开一个文件,并将其句柄保存在_handle中
//Note that the most optimized code turns into two processor instructions:1)a call,and 2)moving the return value into the _handle field.
//注意,最优的代码将转化为两条处理指令:1)调用 2)将返回值移动到“_handle”字段中
//With SafeHandle,the CLR's platform invoke marshaling layer will store the handle into the SafeHandle object in an atomic fashion.
//使用SafeHandle时,CLR平台会调用封送处理层将以原子方式将句柄储存到SafeHandle对象中
//There is still the problem that the SafeHandle object may not be stored in _handle,
//but the real operating system handle value has been safely stored in a critical finalizable object,
//ensuring against leaking the handle even if there is an asynchronous exception.
//这里还有一个问题:SafeHandle对象可能没有被存在_handle中
//但是实际的操作系统句柄值已经安全存储在一个关键的可终结对象中,确保即使存在异常也不会泄漏句柄
MySafeFileHandle tmpHandle;
tmpHandle = NativeMethods.CreateFile(fileName, NativeMethods.GENERIC_READ, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

//An async exception here will cause us to run our finalizer with a null _handle,
//but MySafeFileHandle's ReleaseHandle code will be invoked to free the handle.
//此处异步异常将会导致我们使用空句柄运行终接器,但将调用MySafeFileHandle的ReleaseHandle代码来释放该句柄

//This call to Sleep,run from the fault injection code in Main, will help trigger a race.
//这个调用Sleep,从Main中的错误注入代码运行,将助于出发一场比赛。
//But it will not cause a handle leak because the handle is already stored in a SafeHandle instance.
//但他不会导致句柄泄漏,因为句柄已存储在SafeHandle实例中
//Critical finalization then guarantees that freeing the handle,even during an unexpected AppDomain unload.
//然后,关键终结将确保释放句柄,即使在意外的AppDomain卸载期间也是如此
Thread.Sleep(500);
_handle = tmpHandle; //Make _handle point to a critical f inalizable object.
//使句柄指向一个关键的可终结对象

//Determine if file is opened successfully.
//确保文件打开成功
if (_handle.IsInvalid)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), fileName);
}
}

/// <summary>
/// Follow the Dispose pattern - public nonvirtual
/// 遵循Dispose模式 - public no virtual
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// No finalizer is needed.
/// 不需要终结器
/// The finalizer on SafeHandle will clean up the MySafeFileHandle instance,if it hasn't already been disposed.
/// SafeHandle上的终结器将清理该实例,如果尚未释放MySafeFileHandle实例
/// Howerver,there may be a need for a subclass to introduce a finalizer,so Dispose is properly implemented here.
/// 然而,可能需要一个子类来引入终结器,所以这里正确的实现了Dispose
/// </summary>
/// <param name="disposing"></param>
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
protected virtual void Dispose(bool disposing)
{
//Note there are three interesting states here:
//值得注意的是,这有三种状态
//1).CreateFile failed,_handle contains an invalid handle.
//1)CreateFile失败,_handle包含无效句柄
//2).We called Dispose already, _handle is closed.
//2)我们已经调用了Dispose,句柄已关闭
//3)._handle is null,due to an async exception before calling CreateFile.
//3)_handle为空,因为在调用CreateFile之前发生了异步异常
//Note that the finalizer runs if the constructor fails.
//注意,如果构造函数失败,则终结器将运行
if (_handle != null && !_handle.IsInvalid)
{
//Free the handle
//释放句柄
_handle.Dispose();
}

//SafeHandle records the fact that we've called Dispose.
//SafeHandle记录了我们调用Dispose的事实
}

[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
public byte[] ReadContents(int length)
{
//Is the handle disposed?
//句柄是否释放
if (_handle.IsInvalid)
{
throw new ObjectDisposedException("FileReader is closed");
}

//This sample code will not work for all files.
//示例代码不适用于所有文件
byte[] bytes = new byte[length];
int r = NativeMethods.ReadFile(_handle, bytes, length, out int numRead, IntPtr.Zero);

//Since we removed MyFileReader's finalizer,we no longer need to call GC.KeepAlive here.
//因为我们删除了MyFileReader的终结器,所以不再需要在这里调用GC.KeepAlive。
//Platform invoke will keep the SafeHandle instance alive for the duration of the call.
//平台调用将使SafeHandle实例在调用期间保持活动状态
if (r == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
if (numRead < length)
{
byte[] newBytes = new byte[numRead];
Array.Copy(bytes, newBytes, numRead);
bytes = newBytes;
}
return bytes;
}
}
}
46 changes: 46 additions & 0 deletions SafeHandleDemo/MySafeFileHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;

namespace SafeHandleDemo
{
[SecurityPermission(SecurityAction.InheritanceDemand,UnmanagedCode = true)] //要求继承此类或重写方法类已被授予指定的权限。声明调用非托管代码的权限
[SecurityPermission(SecurityAction.Demand,UnmanagedCode = true)] //要求调用堆栈中的所有高级调用方已被授予指定的权限。声明调用非托管代码的权限
class MySafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
/// <summary>
/// Create a SafeHandle,informing the base class that this SafeHandle instance "owns" the handle,
/// and therefore should call our ReleaseHandle method when the SafeHandle is no longer in use.
/// 创建一个SafeHandle,通知基类这个SafeHandle实例“拥有”这个句柄,
/// 因此SafeHandle不再使用时,其应该调用ReleaseHandle方法
/// </summary>
private MySafeFileHandle() : base(true) //若要在终止阶段可靠地释放此句柄,则为 true;若要阻止可靠释放(不建议使用),则为 false。
{

}

/// <summary>
/// 执行释放句柄所需的代码
/// 【协议】:在遇到异常时,该方法可能会失败但是不会损坏状态,且会返回调用方法成败
/// </summary>
/// <returns></returns>
[ReliabilityContract(Consistency.WillNotCorruptState,Cer.MayFail)]
protected override bool ReleaseHandle()
{
//Here,we must obey all rules for constrained execution regins
//这里,我们必须准守约束执行区域的所有规则
return NativeMethods.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 diagonse handle corruption problems.
//We do not throw an exception because most code could not recover from the problem.
//如果ReleaseHandle失败,可以通过“ReleaseHandleFailed”(MDA)托管调试助手报告,
//默认情况下,此MDA被禁用,但是可以在调试器中启用,也可以在测试期间启用,已诊断处理损问题。
//我们不会抛出异常,因为大多数代码无法从问题中恢复。
}
}
}
61 changes: 61 additions & 0 deletions SafeHandleDemo/NativeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace SafeHandleDemo
{
[SuppressUnmanagedCodeSecurity()] //允许托管代码在不进行堆栈审核的情况下调用到非托管代码
static class NativeMethods
{
/// <summary>
/// Win32 constants for accessing files.
/// 用于访问文件的Win32常量
/// </summary>
internal const int GENERIC_READ = unchecked((int)0x80000000);

/// <summary>
/// Allocate a file object in the kernel, then return a handle to it
/// 在内核中分配一个文件对象,然后返回一个句柄
/// </summary>
/// <param name="fileName"></param>
/// <param name="dwDesireAccess"></param>
/// <param name="dwShareMode"></param>
/// <param name="securityAttrs_MustBeZero"></param>
/// <param name="dwCreateionDisposition"></param>
/// <param name="dwFlagsAndAttributes"></param>
/// <param name="hTemplateFile_MustBeZero"></param>
/// <returns></returns>
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
internal extern static MySafeFileHandle CreateFile(string fileName, int dwDesireAccess, FileShare dwShareMode, IntPtr securityAttrs_MustBeZero, FileMode dwCreateionDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile_MustBeZero);

/// <summary>
/// Use the file handle
/// 使用文件句柄
/// </summary>
/// <param name="handle"></param>
/// <param name="bytes"></param>
/// <param name="numBytesToRead"></param>
/// <param name="numBytesRead"></param>
/// <param name="overlapped_MustBeZero"></param>
/// <returns></returns>
[DllImport("kernel32", SetLastError = true)]
internal extern static int ReadFile(MySafeFileHandle handle, byte[] bytes, int numBytesToRead, out int numBytesRead, IntPtr overlapped_MustBeZero);

/// <summary>
/// Free the kernel's file object(close the file)
/// 释放内核的文件对象(关闭文件)
/// 【协议】:在遇到异常时,该方法可能会失败但保证不损坏状态,返回调用方法是成功还是失败
/// </summary>
/// <param name="handle"></param>
/// <returns></returns>
[DllImport("kernel32", SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState,Cer.MayFail)]
internal extern static bool CloseHandle(IntPtr handle);
}
}
95 changes: 95 additions & 0 deletions SafeHandleDemo/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SafeHandleDemo
{
class Program
{
//Testing harness that injects faults.
//测试注入故障的保护
private static bool _printToConsole = false;
private static bool _workerStarted = false;

private static void Usage()
{
Console.WriteLine("Usage:");
//Assume that application is named HexViwer.
//假设应用程序名为HexViwer
Console.WriteLine("HexViwer <fileName> [-fault]");
Console.WriteLine(" -falut Runs hex viwer repeatedly, injecting faults.");
}

private static void ViewInHex(object fileName)
{
_workerStarted = true;
byte[] bytes;

using (MyFileReader reader = new MyFileReader(fileName.ToString()))
{
bytes = reader.ReadContents(20);
}

if (_printToConsole)
{
//Print up to 20 bytes.
//最多打印20个字节
int printNBytes = Math.Min(20, bytes.Length);
Console.WriteLine($"First {printNBytes} bytes of {fileName} in hex");
for (int i = 0; i < printNBytes; i++)
{
Console.WriteLine($"{bytes[i]:x}");
}
Console.WriteLine();
}
}

static void Main(string[] args)
{
if (args.Length == 0 || args.Length > 2 || args[0] == "-?" || args[0] == "/?")
{
Usage();
return;
}

string fileName = args[0];
bool injectFaultMode = args.Length > 1;
if (!injectFaultMode)
{
_printToConsole = true;
ViewInHex(fileName);
}
else
{
Console.WriteLine("Injecting faults - watch handle count in perfmon (press Ctrl-C when done)");
int numIterations = 0;
while (true)
{
_workerStarted = false;
Thread t = new Thread(ViewInHex);
t.Start(fileName);
Thread.Sleep(1);
while (!_workerStarted)
{
Thread.Sleep(0);
}
t.Abort();
numIterations++;
if (numIterations % 10 == 0)
{
GC.Collect();
}
if (numIterations % 1000 == 0)
{
Console.WriteLine(numIterations);
}
}
}
}
}
}
Loading

0 comments on commit 11ec107

Please sign in to comment.