views:

384

answers:

2

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, int flags, int reserved, int countLow, int countHigh, OVERLAPPED overlapped);

private const int ExclusiveLock = 0x00000002;

public void Lock(long offset, long count)
{
    int countLow = (int)count;
    int countHigh = (int)(count >> 32);

    OVERLAPPED l = new OVERLAPPED()
    { 
        internalLow = 0,
        internalHigh = 0,
        offsetLow = (uint)(int)offset,
        offsetHigh = (uint)(int)(offset >> 32),
        hEvent = IntPtr.Zero,
    };

    if (!LockFileEx(_handle, ExclusiveLock, 0, countLow, countHigh, l))
    {
        //TODO: throw an exception
    }
}

This code will block until the exclusive lock on the region can be acquired.

A: 

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)

Ruben Bartelink
A: 

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);
280Z28
Do you know if its possible to not use the overlapped.Pack routines? I'm really trying to avoid unsafe if I possibly can. What I'm really trying to do is get at the wait handle in the overlapped structure and Wait on it.
JeffreyABecker
You probably don't want to. The Overlapped methods do *a lot* of work for you. http://msdn.microsoft.com/en-us/magazine/cc163415.aspx
280Z28
If they did what I wanted, I'd have a much harder time not using them just because they're unsafe. The bigger problem is that they dont fundamentally do what I want. None of the Win32 file locking apis that I can find are blocking. I am really trying to fudge blocking locks out of non-blocking ones by using waithandles.
JeffreyABecker
Blocking locks are worthless without a timeout parameter. Also, don't tie up a .NET thread when you can pass the overlapped operation to the Win32 thread pool and get a notification once the asynchronous operation is complete.
280Z28
@280Z28 - *"Blocking locks are worthless without a timeout parameter."* So `EnterCriticalSection` is worthless? The C# `lock` statement is worthless?
Daniel Earwicker
@Earwicker: I guess I should revise that to say "Synchronized code must be known to never deadlock. If you can't enforce this through static analysis of your code, you must use a timeout parameter or provide a mechanism on another non-blocked thread to cancel the operation." Asynchronous Windows API calls fall into the latter two.
280Z28