Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSDN SafeHandle example

Tags:

c#

.net

Parhaps a silly question... I'm new to C# and .Net.

In the example for the SafeHandle class (C#) on MSDN, the code made me scratch my head a bit.

[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);

My question is: How does the Win32 HANDLE from the C function CreateFile get into the MySafeFileHandle objects protected IntPtr "handle" variable? The constructor of MySafeFileHandle is private and doesn't even take an IntPtr as an argument!

The comment just over the CreateFile statement says something about

… the CLR's platform marshalling layer will store the handle into the SafeHandle object in an atomic fashion.

I'm not sure I know exactly what this means, can anyone explain please?

like image 952
Karl Hansson Avatar asked Sep 04 '18 18:09

Karl Hansson


1 Answers

Short answer: it's magic. The runtime knows how to properly convert unmanaged handles (which are just pointer-sized values) to SafeHandle and back.

Long answer: it's sufficiently advanced technology. Specifically, ILSafeHandleMarshaler is the (unmanaged!) class that takes care of marshaling SafeHandles back and forth. The source code helpfully summarizes the process:

// 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

The code it emits to load the unmanaged handle into the safe handle is actually managed code, albeit managed code that happily ignores accessibility. It gets and invokes the default constructor to create a new instance:

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

And then it directly sets the SafeHandle.handle field:

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);

Neither the constructor nor the handle field are actually accessible, but this code isn't subject to visibility checks.

like image 129
Jeroen Mostert Avatar answered Oct 17 '22 13:10

Jeroen Mostert