On parle Windows, C#, Apple, Android, Js, …

Jouer avec FindFirstFile et FindNextFile

Énumération des fichiers dans .NET est facile. Tout le monde connaît la méthode GetFiles et vous pouvez être tenté d’écrire du code comme ceci :
    var files = Directory.GetFiles(@"c:windowssystem32", "*.dll");
    foreach (var file in files)
    {
        Console.WriteLine(file);
    }

Mais si vous regardez de plus près, vous remarquerez que cette méthode retourne un tableau de chaînes. Cela pourrait être problématique si le répertoire que vous recherchez contient beaucoup de fichiers. La méthode se bloque jusqu’à ce qu’il effectue une recherche et une fois qu’il termine il va charger tous les résultats dans la mémoire. Il serait beaucoup mieux si il vient de rentrer un IEnumerable. C’est exactement ce que la méthode EnumerateFiles dans .NET 4.0. Malheureusement dans .NET 3.5 il n’y a rien pour le travail.

Dans ce post, je vais montrer comment implémenter cette fonctionnalité en utilisant les fonctions FindFirstFile et FindNextFile .

Nous commençons par définir les prototypes de fonction native :

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

Vous remarquerez peut-être la classe SafeFindHandle utilisée dans les signatures de méthode. C’est juste une simple classe dérivée de SafeHandle qui s’assurera que ce handle non managé est correctement fermé :

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

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

Comme la documentation indique le handle créé par FindFirstFile doit être fermé avec fonction FindClose . Et enfin la mise en œuvre de la méthode EnumerateFiles :

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

La méthode pourrait être utilisée comme ceci :

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

Laissez un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.