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?
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 SafeHandle
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With