【问题标题】:MSDN SafeHandle exampleMSDN SafeHandle 示例
【发布时间】:2018-09-05 20:09:31
【问题描述】:

也许是一个愚蠢的问题...我是 C# 和 .Net 的新手。

In the example for the SafeHandle class (C#) on MSDN,代码让我有点摸不着头脑。

[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal class MySafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    private MySafeFileHandle()
      : base(true)
    {}
    // other code here
}

[SuppressUnmanagedCodeSecurity()]
internal static class NativeMethods
{
    // other code...

    // Allocate a file object in the kernel, then return a handle to it.
    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    internal extern static MySafeFileHandle CreateFile(String fileName,
       int dwDesiredAccess, System.IO.FileShare dwShareMode,
       IntPtr securityAttrs_MustBeZero, System.IO.FileMode    
       dwCreationDisposition, int dwFlagsAndAttributes, 
       IntPtr hTemplateFile_MustBeZero);

    // other code...
}

// Later in the code the handle is created like this:
MySafeFileHandle tmpHandle;
tmpHandle = NativeMethods.CreateFile(fileName, NativeMethods.GENERIC_READ,
            FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

我的问题是: C 函数 CreateFile 中的 Win32 HANDLE 如何进入受保护的 MySafeFileHandle 对象 IntPtr "handle" 变量? MySafeFileHandle 的构造函数是私有的,甚至不接受 IntPtr 作为参数!

CreateFile 声明上方的评论说了一些关于

…CLR 的平台编组层将以原子方式将句柄存储到 SafeHandle 对象中。

我不确定我确切地知道这意味着什么,谁能解释一下吗?

【问题讨论】:

  • 基本上,这很神奇。运行时“知道”SafeHandle 以及如何将IntPtrs 填充到其中。当然,运行时不受构造函数规则的约束。
  • 同样,C# 编译器可以创建不合法的 C# 代码。仅仅因为必须遵守规则并不意味着其他人必须这样做。
  • CreateFile() 不返回安全句柄。它是一个纯粹的非托管函数,不知道有关 .NET 对象的 bean。但是 [DllImport] 声明说确实如此。现在是 pinvoke marshaller 的工作,即 CLR 中进行本机函数调用的代码块,将 IntPtr 转换为 MySafeFileHandle 对象。它知道很多转换技巧,这只是其中之一。
  • 顺便说一句,这根本不是一个愚蠢的问题。围绕某些非托管对象的互操作是基本运行时 imo 中最复杂的东西。一些使已知句柄类型以直观方式表现的终结器技巧变得……讨厌。幸运的是,这只会在处理 CLR 知道的句柄类型时成为典型用户的问题。 (例如自定义硬件中真正有限资源的句柄)

标签: c# .net


【解决方案1】:

简短的回答:这很神奇。运行时知道如何正确地将非托管句柄(只是指针大小的值)转换为 SafeHandle 并返回。

长答案:这是足够先进的技术。具体来说,ILSafeHandleMarshaler 是(非托管!)类,负责来回封送SafeHandles。 source code 有用地总结了这个过程:

// 1) create local for new safehandle
// 2) prealloc a safehandle
// 3) create local to hold returned handle
// 4) [byref] add byref IntPtr to native sig
// 5) [byref] pass address of local as last arg
// 6) store return value in safehandle

它发出的将非托管句柄加载到安全句柄中的代码实际上是托管代码,尽管托管代码很乐意忽略可访问性。它获取并调用默认构造函数来创建一个新实例:

MethodDesc* pMDCtor = pMT->GetDefaultConstructor();
pslIL->EmitNEWOBJ(pslIL->GetToken(pMDCtor), 0);
pslIL->EmitSTLOC(dwReturnHandleLocal);   

然后直接设置SafeHandle.handle字段:

mdToken tkNativeHandleField = 
    pslPostIL->GetToken(MscorlibBinder::GetField(FIELD__SAFE_HANDLE__HANDLE));
...

// 6) store return value in safehandle
pslCleanupIL->EmitLDLOC(dwReturnHandleLocal);
pslCleanupIL->EmitLDLOC(dwReturnNativeHandleLocal);
pslCleanupIL->EmitSTFLD(tkNativeHandleField);

构造函数和 handle 字段实际上都不可访问,但此代码不受可见性检查。

【讨论】:

  • 我删除了我的答案,因为我认为它可能具有误导性,但我认为值得注意的是,任何 C# 代码都相对容易忽略可访问性。这可以通过unsafe 和指针算法(如 C++)或通过reflection 来实现。
  • @zzxyz:当然——不安全的托管代码可以为所欲为,包括在任意内存上乱涂乱画。但是,同样重要的是要注意,大多数复杂的封送处理不是用托管代码编写的(甚至不是不安全的),因为它需要以一种有趣的方式与垃圾收集器和托管运行时的状态进行交互。有些东西不能用纯托管代码编写(甚至是不安全的)并且仍然是正确的。这段编组会发出一些托管指令,但我省略了围绕它的一堆有趣的非托管内容。
  • 优秀。感谢您的评论。这对我来说有点澄清。希望它也对OP有所帮助。而且你必须在同一个句子中使用omitemit
  • 感谢您的回答!我一直在环顾四周,看看是否有一些我不知道的花哨的隐式赋值/转换。不过,这可以解释一些事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-24
相关资源
最近更新 更多