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.
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
}
}
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 lightweight IDisposable
object for unlocking the file derived from CriticalFinalizerObject
.
private const uint LOCKFILE_EXCLUSIVE_LOCK = 0x00000002;
private static readonly Action WinIOError;
static Win32Native()
{
BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
var winIOErrorMethod = typeof(string).Assembly.GetType("System.IO.__Error").GetMethod("WinIOError", bindingAttr, null, Type.EmptyTypes, null);
WinIOError = (Action)Delegate.CreateDelegate(typeof(Action), winIOErrorMethod);
}
public static void LockFile(SafeFileHandle handle, bool exclusive, long offset, long length, Action action)
{
if (handle == null)
throw new ArgumentNullException("handle");
if (handle.IsInvalid)
throw new ArgumentException("An invalid file handle was specified.", "handle");
if (offset < 0)
throw new ArgumentOutOfRangeException("The offset cannot be negative.", "offset");
if (length < 0)
throw new ArgumentOutOfRangeException("The length cannot be negative.", "length");
if (action == null)
throw new ArgumentNullException("action");
LockFileUnsafe(handle, exclusive, offset, length, action);
}
private static unsafe void LockFileUnsafe(SafeFileHandle handle, bool exclusive, long offset, long length, Action action)
{
Overlapped overlapped = new Overlapped();
overlapped.OffsetHigh = (int)(offset >> 32);
overlapped.OffsetLow = (int)offset;
IOCompletionCallback callback =
(errorCode, numBytes, nativeOverlapped) =>
{
try
{
action();
}
finally
{
Overlapped.Free(nativeOverlapped);
}
};
NativeOverlapped* native = overlapped.Pack(callback, null);
uint flags = exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0;
if (!LockFileEx(handle, flags, 0, (int)length, (int)(length >> 32), native))
{
Overlapped.Free(native);
WinIOError();
}
}
[DllImport("kernel32.dll", SetLastError = true)]
private static unsafe extern bool LockFileEx(SafeFileHandle handle, uint flags, uint mustBeZero, int countLow, int countHigh, NativeOverlapped* overlapped);
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