Playing with FindFirstFile and FindNextFile

Enumerating files in .NET is easy. Everybody knows the GetFiles method and you may be tempted to write code like this :

var files = Directory.GetFiles(@"c:\windows\system32", "*.dll");
    foreach (var file in files)
    {
        Console.WriteLine(file);
    }

But if you look closer you will notice that this method returns an array of strings. This could be problematic if the directory you search contains lots of files. The method will block until it performs the search and once it finishes it will load all the results into memory. It would be much better if it just returned an IEnumerable. That’s exactly what the EnumerateFiles method does in .NET 4.0. Unfortunately in .NET 3.5 there’s nothing for the job.

In this post I will show how to implement this functionality using the FindFirstFile and FindNextFile functions.

We start by defining the native function prototypes:

internal sealed class Win32Native
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct WIN32_FIND_DATA
        {
            public uint dwFileAttributes;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
            public uint nFileSizeHigh;
            public uint nFileSizeLow;
            public uint dwReserved0;
            public uint dwReserved1;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string cFileName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
            public string cAlternateFileName;
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern SafeFindHandle FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool FindNextFile(SafeFindHandle hFindFile, out WIN32_FIND_DATA lpFindFileData);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool FindClose(IntPtr hFindFile);
    }

You may notice the SafeFindHandle class used in the method signatures. This is just a simple class deriving from SafeHandle that will make sure that unmanaged handle is correctly closed:

[SecurityCritical]
    internal class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        [SecurityCritical]
        public SafeFindHandle() : base(true)
        { }

        [SecurityCritical]
        protected override bool ReleaseHandle()
        {
            return Win32Native.FindClose(base.handle);
        }
    }

As the documentation states the handle created by FindFirstFile needs to be closed with FindClose function. And finally the implementation of the EnumerateFiles method:

public static class DirectoryExtensions
    {
        public static IEnumerable<string> EnumerateFiles(string path, string searchPattern)
        {
            // TODO: validate input parameters

            string lpFileName = Path.Combine(path, searchPattern);
            Win32Native.WIN32_FIND_DATA lpFindFileData;
            var handle = Win32Native.FindFirstFile(lpFileName, out lpFindFileData);
            if (handle.IsInvalid)
            {
                int hr = Marshal.GetLastWin32Error();
                if (hr != 2 && hr != 0x12)
                {
                    throw new Win32Exception(hr);
                }
                yield break;
            }

            if (IsFile(lpFindFileData))
            {
                var fileName = Path.Combine(path, lpFindFileData.cFileName);
                yield return fileName;
            }

            while (Win32Native.FindNextFile(handle, out lpFindFileData))
            {
                if (IsFile(lpFindFileData))
                {
                    var fileName = Path.Combine(path, lpFindFileData.cFileName);
                    yield return fileName;
                }
            }

            handle.Dispose();
        }

        private static bool IsFile(Win32Native.WIN32_FIND_DATA data)
        {
            return 0 == (data.dwFileAttributes & 0x10);
        }
    }

The method could be used like this:

class Program
    {
        static void Main(string[] args)
        {
            var files = DirectoryExtensions.EnumerateFiles(@"c:\windows\system32", "*.dll");
            foreach (var file in files)
            {
                Console.WriteLine(file);
            }
        }
    }

Leave a comment

Your email address will not be published. Required fields are marked *

One thought on “Playing with FindFirstFile and FindNextFile”