C# で FindFile

C# を久々に使ったので復習コード。


```csharp
static IEnumerable<FoundFileData> FindFile(string dir, string file = "*", bool recursive = false)
{
    string search = Path.Combine(dir, "*");
    if (search.StartsWith(@"\\") == false && 250 < search.Length)
    {
        search.Insert(0, @"\\?\");
    }

    int error;
    WIN32_FIND_DATA fd = new WIN32_FIND_DATA();

    using (var h = new FindFileHandle(search, fd))
    {
        if (h.IsInvalid)
        {
            error = Marshal.GetLastWin32Error();

#if false
            switch ((Win32Error)error)
            {
                case Win32Error.ERROR_PATH_NOT_FOUND:
                case Win32Error.ERROR_FILE_NOT_FOUND:
                case Win32Error.ERROR_NO_TOKEN:
                    // UNC を指定したときに発生
                    // 存在しない UNC を指定したときに発生
                    break;
                default:
                    throw new System.ComponentModel.Win32Exception();
            }
#endif
        }
        else
        {
            do
            {
                if (PathMatchSpecEx(fd.fileName, file, PMSF_MULTIPLE) == S_OK)
                {
                    yield return new FoundFileData(dir, fd);
                }

                if (recursive && fd.fileName != "." && fd.fileName != ".." && (fd.fileAttributes & FILE_ATTRIBUTES_DIRECTORY) != 0)
                {
                    IEnumerable<FoundFileData> sub = FindFile(Path.Combine(dir, fd.fileName), file, true);
                    var e = sub.GetEnumerator();
                    while (e.MoveNext())
                    {
                        yield return e.Current;
                    }
                }
            } while (h.FindNext(fd));

            error = Marshal.GetLastWin32Error();
            if (error != (int)Win32Error.ERROR_NO_MORE_FILES)
            {
                throw new System.ComponentModel.Win32Exception();
            }
        }
    }

    yield break;
}

static void Main(string[] args)
{
    try
    {
        foreach (FoundFileData fd in FindFile(@"c:\Users\AsladaGSX\Downloads", "*.zip; *.exe", true))
        {
            Debug.WriteLine(Path.Combine(fd.dir, fd.fileName));
        }

    }
    catch (Exception e)
    {
        Debug.WriteLine(e.Message);
    }
}
```

以下、p/Invoke

```
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class WIN32_FIND_DATA
{
    public UInt32 fileAttributes = 0;
    // creationTime was an embedded FILETIME structure.
    public UInt32 creationTime_lowDateTime = 0;
    public UInt32 creationTime_highDateTime = 0;
    // lastAccessTime was an embedded FILETIME structure.
    public UInt32 lastAccessTime_lowDateTime = 0;
    public UInt32 lastAccessTime_highDateTime = 0;
    // lastWriteTime was an embedded FILETIME structure.
    public UInt32 lastWriteTime_lowDateTime = 0;
    public UInt32 lastWriteTime_highDateTime = 0;
    public UInt32 nFileSizeHigh = 0;
    public UInt32 nFileSizeLow = 0;
    public UInt32 dwReserved0 = 0;
    public UInt32 dwReserved1 = 0;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public String fileName = null;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
    public String alternateFileName = null;
}

public class FoundFileData
{
    public string dir { get; private set; }
    public string fileName { get; private set; }
    /* めんどくさい。略。 */

    internal FoundFileData(string dir, WIN32_FIND_DATA fd)
    {
        this.dir = dir;
        this.fileName = fd.fileName;
    }
}

const UInt32 FILE_ATTRIBUTES_DIRECTORY = 0x00000010;

class FindFileHandle : SafeHandle
{
    private static IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);

    [DllImport("Kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
    private static extern IntPtr FindFirstFile(string fileName, [In, Out] WIN32_FIND_DATA findFileData);

    [DllImport("Kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
    private static extern int FindClose(IntPtr hFindFile);

    [DllImport("Kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
    private static extern int FindNextFile(IntPtr hFindFile, [In, Out] WIN32_FIND_DATA findFileData);

    public FindFileHandle(string search, [In, Out] WIN32_FIND_DATA findFileData) : base(INVALID_HANDLE_VALUE, true)
    {
        this.handle = FindFirstFile(search, findFileData);
    }

    public override bool IsInvalid
    {
        get { return handle == INVALID_HANDLE_VALUE; }
    }

    protected override bool ReleaseHandle()
    {
        if (handle != IntPtr.Zero && handle != INVALID_HANDLE_VALUE)
        {
            return 0 != FindClose(handle);
        }
        return true;
    }

    public bool FindNext([In, Out] WIN32_FIND_DATA fd)
    {
        return 0 != FindNextFile(handle, fd);
    }
}

[DllImport("Shlwapi.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
private static extern UInt32 PathMatchSpecEx(string pszFile, string pszSpec, UInt32 dwFlags);
private const UInt32
    //  The pszSpec parameter points to a single file name pattern to be matched.
    PMSF_NORMAL = 0x00000000,
    //  The pszSpec parameter points to a semicolon-delimited list of file name patterns to be matched.
    PMSF_MULTIPLE = 0x00000001,
    //  If PMSF_NORMAL is used, ignore leading spaces in the string pointed to by pszSpec.
    //  If PMSF_MULTIPLE is used, ignore leading spaces in each file type contained in the string pointed to by pszSpec.
    //  This flag can be combined with PMSF_NORMAL and PMSF_MULTIPLE.
    PMSF_DONT_STRIP_SPACES = 0x00010000;

// HRESULT value.
const UInt32 
    S_OK = 0,
    S_FALSE = 1;
'''