Background
I'm trying to implement block file locking in my C# application. The built-in FileStream.Lock
method throws an exception if it is unable to acquire the lock.
The underlying LockFile
method returns a status code however I'd prefer not to use a spin-lock to wait for the file to be unlocked.
Question
Does anyone have any code snippets in C# showing how to properly construct the OVERLAPPED structure with a wait
handle and pass it to LockFileEx
and wait for the operation to complete? I am trying to avoid using the Overlapped.Pack methods partially because they're unsafe but mostly because they require an IOCompletionCallback
which isn't what I'm trying to achieve.
I have the declarations but the construction & use of the OverLapped
structure seems to be a little bit more complicated.
Note: I know I need to manually pin the overlapped structure until the wait completes. My current code looks like:
ManualResetEvent evt = new ManualResetEvent(false);
OVERLAPPED overlapped = new OVERLAPPED();
overlapped.OffsetLow = offsetLow;
overlapped.OffsetHigh = offsetHigh;
overlapped.hEvent = evt.SafeHandle;
GCHandle h = GCHandle.Alloc(overlapped, GCHandleType.Pinned);
int hr = Win32.LockFileEX(_handle, LockFlags.Exclusive, 0, offsetLow, offsetHigh,
GCHandle.ToIntPtr(h));
if(hr == 0)
{
int error = Marshal.GetLastWin32Error();
if(error = Win32.ERROR_IO_PENDING)
{
evt.WaitOne();
}
else
{
//ohpoo
}
}
Resolution
The code that ended up working as I wanted was:
[StructLayout(LayoutKind.Sequential)]
public struct OVERLAPPED
{
public uint internalLow;
public uint internalHigh;
public uint offsetLow;
public uint offsetHigh;
public IntPtr hEvent;
}
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern bool LockFileEx(SafeFileHandle handle, uint flags, uint reserved, uint countLow, uint countHigh, ref OVERLAPPED overlapped);
private const uint LOCKFILE_EXCLUSIVE_LOCK = 0x00000002;
public static void Lock(FileStream stream, ulong offset, ulong count)
{
uint countLow = (uint)count;
uint countHigh = (uint)(count >> 32);
OVERLAPPED overlapped = new OVERLAPPED()
{
internalLow = 0,
internalHigh = 0,
offsetLow = (uint)offset,
offsetHigh = (uint)(offset >> 32),
hEvent = IntPtr.Zero,
};
if (!LockFileEx(stream.SafeFileHandle, LOCKFILE_EXCLUSIVE_LOCK, 0, countLow,
countHigh, ref overlapped))
{
//TODO: throw an exception
}
}
This code will block until the exclusive lock on the region can be acquired.
The following should be "very close" to a good solution. The only part I really don't like is using reflection to access an mscorlib internal method, but that method does an excellent job converting Win32 error codes to an IOException. Since you already have unsafe code for
NativeOverlapped*
, permissions aren't an issue.It could probably be further improved by creating a
SafeFileLockHandle
or similar to provide a lightweightIDisposable
object for unlocking the file derived fromCriticalFinalizerObject
.Reflector has the answer (and pinvoke.net and http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/7217a8d3-d36d-43c9-ad4f-ad638a9ac1de have the declarations)