Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
436 views
in Technique[技术] by (71.8m points)

.net - Using LockFileEX in C#

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...