LoginSignup
2
2

More than 5 years have passed since last update.

C# で FindFile

Posted at

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

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;
'''
2
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2