Click here to Skip to main content
Click here to Skip to main content

In C#, Use Win32 API to Enumerate File and Directory Quickly

By , 21 Jul 2007
 
Screenshot - enumfile.png

Introduction

This is an article about using Win32 API in C# to enumerate file and directory quickly.

Background

Sometimes we need to enumerate file and especially sub directory. We can use System.IO.DirectoryInfo.GetDirectories or GetFiles to get all file and sub directories. When the father directory has many files and sub directories, this operation is slow and out of control. Sometimes we just want to know if there are some special kind of file or sub directories in a directory. Obviously the System.IO.DirectoryInfo.GetDirectories/GetFiles is inefficient and a waste of time.

So we think about 3 Win32 APIs:

  • FindFirstFile 
  • FindNextFile 
  • FindClose 

Using these 3 Win32 APIs, we can enumerate file and sub directory, efficiently and in a controllable manner.

Using the Code

In C#, we can use the foreach expression to enumerate some kind of serial value. The object which we use for foreach expression must implement Interface System.Collection.IEnumerable. This object contains an enumerator which implements Interface System.Collection.IEnumerator. For friendly use, we package the 3 APIs as IEnumerator.

IEnumerator has 3 members:

  • Reset method to initialize object 
  • MoveNext method to search next file or sub directory
  • Current property returns the file or sub directory that is found

After enumerating, object must call FindClose to free resources. Then I wrote a class name FileDirectoryEnumerable. You can use this class as follows:

string strPath = System.Environment.GetFolderPath
	( System.Environment.SpecialFolder.System );
FileDirectoryEnumerable myEnum = new FileDirectoryEnumerable();
myEnum.SearchPath = strPath ;
myEnum.SearchPattern = "*.exe";
myEnum.SearchForFile = true;
myEnum.SearchForDirectory = false;
myEnum.ReturnStringType = true;
System.Console.WriteLine("----------- search EXE file name ------------------");
int iCount = 0 ;
foreach( string strFileName in myEnum )
{
System.Console.WriteLine( strFileName );
// LOOK ! , you can interrupt freely
if( ( iCount ++ ) > 10) 
break;
}
System.Console.WriteLine("-------------- search DLL file name and length ----------");
myEnum.ReturnStringType = false;
myEnum.SearchPattern = "*.dll";
iCount = 0 ;
foreach( System.IO.FileInfo file in myEnum )
{
System.Console.WriteLine( file.Name + "\tLength:" + file.Length + " bytes ");
// LOOK ! , you can interrupt freely
if( ( iCount ++ ) > 10 )
break;
}
myEnum.Close();
System.Console.WriteLine("------------- Press Enter to exit ------------------");
System.Console.ReadLine();

The source code of FileDirectoryEnumerable is:

using System;
namespace EnumerateFile
{
/// <summary>
/// ????????,???? FileDirectoryEnumerator ?????
/// </summary>
/// <remarks>
/// 
/// ?? ??? ( http://www.xdesigner.cn )2006-12-8
/// 
/// ?????????????????
/// 
/// FileDirectoryEnumerable e = new FileDirectoryEnumerable();
/// e.SearchPath = @"c:\";
/// e.ReturnStringType = true ;
/// e.SearchPattern = "*.exe";
/// e.SearchDirectory = false ;
/// e.SearchFile = true;
/// foreach (object name in e)
/// {
/// System.Console.WriteLine(name);
/// }
/// System.Console.ReadLine();
/// 
///</remarks>
public class FileDirectoryEnumerable : System.Collections.IEnumerable
{
private bool bolReturnStringType = true;
/// <summary>
/// ??????????????,???true???????????,
/// ???? System.IO.FileInfo?System.IO.DirectoryInfo??
/// </summary>
public bool ReturnStringType
{
get { return bolReturnStringType; }
set { bolReturnStringType = value; }
}
private string strSearchPattern = "*";
/// <summary>
/// ??????????
/// </summary>
public string SearchPattern
{
get { return strSearchPattern; }
set { strSearchPattern = value; }
}
private string strSearchPath = null;
/// <summary>
/// ????,???????
/// </summary>
public string SearchPath
{
get { return strSearchPath; }
set { strSearchPath = value; }
}
private bool bolSearchForFile = true;
/// <summary>
/// ??????
/// </summary>
public bool SearchForFile
{
get { return bolSearchForFile; }
set { bolSearchForFile = value; }
}
private bool bolSearchForDirectory = true;
/// <summary>
/// ???????
/// </summary>
public bool SearchForDirectory
{
get { return bolSearchForDirectory; }
set { bolSearchForDirectory = value; }
}
private bool bolThrowIOException = true;
/// <summary>
/// ??IO?????????
/// </summary>
public bool ThrowIOException
{
get { return this.bolThrowIOException; }
set { this.bolThrowIOException = value; }
}
/// <summary>
/// ?????????????
/// </summary>
/// <returns>?????</returns>
public System.Collections.IEnumerator GetEnumerator()
{
FileDirectoryEnumerator e = new FileDirectoryEnumerator();
e.ReturnStringType = this.bolReturnStringType;
e.SearchForDirectory = this.bolSearchForDirectory;
e.SearchForFile = this.bolSearchForFile;
e.SearchPath = this.strSearchPath;
e.SearchPattern = this.strSearchPattern;
e.ThrowIOException = this.bolThrowIOException;
myList.Add(e);
return e;
}
/// <summary>
/// ????
/// </summary>
public void Close()
{
foreach (FileDirectoryEnumerator e in myList)
{
e.Close();
}
myList.Clear();
}
private System.Collections.ArrayList myList = new System.Collections.ArrayList();
}//public class FileDirectoryEnumerable : System.Collections.IEnumerable
/// <summary>
/// ?????????
/// </summary>
/// <remarks>????Win32API?? FindFirstFile , FindNextFile 
/// ? FindClose ?????
/// 
/// ????????? FileDirectoryEnumerator 
/// 
/// FileDirectoryEnumerator e = new FileDirectoryEnumerator();
/// e.SearchPath = @"c:\";
/// e.Reset();
/// e.ReturnStringType = true ;
/// while (e.MoveNext())
/// {
/// System.Console.WriteLine
/// ( e.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss")
/// + " " + e.FileLength + " \t" + e.Name );
/// }
/// e.Close();
/// System.Console.ReadLine();
/// 
/// ?? ??? ( http://www.xdesigner.cn )2006-12-8</remarks>
public class FileDirectoryEnumerator : System.Collections.IEnumerator
{
#region ?????????????? **********************************
/// <summary>
/// ????
/// </summary>
private object objCurrentObject = null;
private bool bolIsEmpty = false;
/// <summary>
/// ?????
/// </summary>
public bool IsEmpty
{
get { return bolIsEmpty; }
}
private int intSearchedCount = 0;
/// <summary>
/// ?????????
/// </summary>
public int SearchedCount
{
get { return intSearchedCount; }
}
private bool bolIsFile = true;
/// <summary>
/// ?????????,??true????????,?????
/// </summary>
public bool IsFile
{
get { return bolIsFile; }
}
private int intLastErrorCode = 0;
/// <summary>
/// ???????Win32????
/// </summary>
public int LastErrorCode
{
get { return intLastErrorCode; }
}
/// <summary>
/// ???????
/// </summary>
public string Name
{
get
{
if (this.objCurrentObject != null)
{
if (objCurrentObject is string)
return (string)this.objCurrentObject;
else
return ((System.IO.FileSystemInfo)this.objCurrentObject).Name;
}
return null;
}
}
/// <summary>
/// ??????
/// </summary>
public System.IO.FileAttributes Attributes
{
get { return (System.IO.FileAttributes)myData.dwFileAttributes; }
}
/// <summary>
/// ????????
/// </summary>
public System.DateTime CreationTime
{
get
{
long time = ToLong(myData.ftCreationTime_dwHighDateTime, 
	myData.ftCreationTime_dwLowDateTime);
System.DateTime dtm = System.DateTime.FromFileTimeUtc(time);
return dtm.ToLocalTime();
}
}
/// <summary>
/// ??????????
/// </summary>
public System.DateTime LastAccessTime
{
get
{
long time = ToLong(myData.ftLastAccessTime_dwHighDateTime, 
	myData.ftLastAccessTime_dwLowDateTime);
System.DateTime dtm = System.DateTime.FromFileTimeUtc(time);
return dtm.ToLocalTime();
}
}
/// <summary>
/// ??????????
/// </summary>
public System.DateTime LastWriteTime
{
get
{
long time = ToLong(myData.ftLastWriteTime_dwHighDateTime, 
	myData.ftLastWriteTime_dwLowDateTime);
System.DateTime dtm = System.DateTime.FromFileTimeUtc(time);
return dtm.ToLocalTime();
}
}
/// <summary>
/// ??????,????????????????,???????????0
/// </summary>
public long FileLength
{
get
{
if (this.bolIsFile)
return ToLong(myData.nFileSizeHigh, myData.nFileSizeLow);
else
return 0;
}
}
#endregion
#region ??????????? ****************************************
private bool bolThrowIOException = true;
/// <summary>
/// ??IO?????????
/// </summary>
public bool ThrowIOException
{
get { return this.bolThrowIOException; }
set { this.bolThrowIOException = value; }
}
private bool bolReturnStringType = true;
/// <summary>
/// ??????????????,???true???????????,
/// ???? System.IO.FileInfo?System.IO.DirectoryInfo??
/// </summary>
public bool ReturnStringType
{
get { return bolReturnStringType; }
set { bolReturnStringType = value; }
}
private string strSearchPattern = "*";
/// <summary>
/// ??????????,?????
/// </summary>
public string SearchPattern
{
get { return strSearchPattern; }
set { strSearchPattern = value; }
}
private string strSearchPath = null;
/// <summary>
/// ??????,???????,??????,???????
/// </summary>
public string SearchPath
{
get { return strSearchPath; }
set { strSearchPath = value; }
}
private bool bolSearchForFile = true;
/// <summary>
/// ??????
/// </summary>
public bool SearchForFile
{
get { return bolSearchForFile; }
set { bolSearchForFile = value; }
}
private bool bolSearchForDirectory = true;
/// <summary>
/// ???????
/// </summary>
public bool SearchForDirectory
{
get { return bolSearchForDirectory; }
set { bolSearchForDirectory = value; }
}
#endregion
/// <summary>
/// ????,????
/// </summary>
public void Close()
{
this.CloseHandler();
}
#region IEnumerator ?? **********************************************
/// <summary>
/// ??????
/// </summary>
public object Current
{
get { return objCurrentObject ; }
}
/// <summary>
/// ??????????
/// </summary>
/// <returns>??????</returns>
public bool MoveNext()
{
bool success = false;
while (true)
{
if (this.bolStartSearchFlag)
success = this.SearchNext();
else
success = this.StartSearch();
if (success)
{
if (this.UpdateCurrentObject())
return true;
}
else
{
this.objCurrentObject = null;
return false;
}
}
}
/// <summary>
/// ??????
/// </summary>
public void Reset()
{
if (this.strSearchPath == null)
throw new System.ArgumentNullException("SearchPath can not null");
if (this.strSearchPattern == null || this.strSearchPattern.Length == 0)
this.strSearchPattern = "*";
this.intSearchedCount = 0;
this.objCurrentObject = null;
this.CloseHandler();
this.bolStartSearchFlag = false;
this.bolIsEmpty = false;
this.intLastErrorCode = 0;
}
#endregion
#region ??WIN32API?????? **************************************
[Serializable,
System.Runtime.InteropServices.StructLayout
(System.Runtime.InteropServices.LayoutKind.Sequential,
CharSet = System.Runtime.InteropServices.CharSet.Auto
),
System.Runtime.InteropServices.BestFitMapping(false)]
private struct WIN32_FIND_DATA
{
public int dwFileAttributes;
public int ftCreationTime_dwLowDateTime;
public int ftCreationTime_dwHighDateTime;
public int ftLastAccessTime_dwLowDateTime;
public int ftLastAccessTime_dwHighDateTime;
public int ftLastWriteTime_dwLowDateTime;
public int ftLastWriteTime_dwHighDateTime;
public int nFileSizeHigh;
public int nFileSizeLow;
public int dwReserved0;
public int dwReserved1;
[System.Runtime.InteropServices.MarshalAs
(System.Runtime.InteropServices.UnmanagedType.ByValTStr,
SizeConst = 260)]
public string cFileName;
[System.Runtime.InteropServices.MarshalAs
(System.Runtime.InteropServices.UnmanagedType.ByValTStr,
SizeConst = 14)]
public string cAlternateFileName;
}
[System.Runtime.InteropServices.DllImport
("kernel32.dll",
CharSet = System.Runtime.InteropServices.CharSet.Auto,
SetLastError = true)]
private static extern IntPtr FindFirstFile(string pFileName, 
	ref WIN32_FIND_DATA pFindFileData);
[System.Runtime.InteropServices.DllImport
("kernel32.dll",
CharSet = System.Runtime.InteropServices.CharSet.Auto,
SetLastError = true)]
private static extern bool FindNextFile(IntPtr hndFindFile, 
	ref WIN32_FIND_DATA lpFindFileData);
[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FindClose(IntPtr hndFindFile);
private static long ToLong( int height , int low)
{
long v = ( uint ) height ;
v = v << 0x20;
v = v | ( ( uint )low );
return v;
}
private static void WinIOError(int errorCode, string str)
{
switch (errorCode)
{
case 80:
throw new System.IO.IOException("IO_FileExists :" + str);
case 0x57:
throw new System.IO.IOException("IOError:" + MakeHRFromErrorCode(errorCode));
case 0xce:
throw new System.IO.PathTooLongException("PathTooLong:" + str );
case 2:
throw new System.IO.FileNotFoundException("FileNotFound:" + str);
case 3:
throw new System.IO.DirectoryNotFoundException("PathNotFound:" + str);
case 5:
throw new UnauthorizedAccessException("UnauthorizedAccess:" + str);
case 0x20:
throw new System.IO.IOException("IO_SharingViolation:" + str);
}
throw new System.IO.IOException("IOError:" + MakeHRFromErrorCode(errorCode));
}
private static int MakeHRFromErrorCode(int errorCode)
{
return (-2147024896 | errorCode);
}
#endregion
#region ????? ****************************************************
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
/// <summary>
/// ?????????
/// </summary>
private System.IntPtr intSearchHandler = INVALID_HANDLE_VALUE;
private WIN32_FIND_DATA myData = new WIN32_FIND_DATA();
/// <summary>
/// ??????
/// </summary>
private bool bolStartSearchFlag = false;
/// <summary>
/// ??????
/// </summary>
private void CloseHandler()
{
if (this.intSearchHandler != INVALID_HANDLE_VALUE)
{
FindClose(this.intSearchHandler);
this.intSearchHandler = INVALID_HANDLE_VALUE;
}
}
/// <summary>
/// ????
/// </summary>
/// <returns>??????</returns>
private bool StartSearch()
{
bolStartSearchFlag = true;
bolIsEmpty = false;
objCurrentObject = null;
intLastErrorCode = 0;
string strPath = System.IO.Path.Combine(strSearchPath, this.strSearchPattern);
this.CloseHandler();
intSearchHandler = FindFirstFile(strPath, ref myData);
if (intSearchHandler == INVALID_HANDLE_VALUE)
{
intLastErrorCode = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
if (intLastErrorCode == 2)
{
bolIsEmpty = true;
return false;
}
if( this.bolThrowIOException )
WinIOError( intLastErrorCode , strSearchPath);
else
return false;
}
return true;
}
/// <summary>
/// ?????
/// </summary>
/// <returns>??????</returns>
private bool SearchNext()
{
if (bolStartSearchFlag == false)
return false;
if (bolIsEmpty)
return false;
if (intSearchHandler == INVALID_HANDLE_VALUE)
return false;
intLastErrorCode = 0 ;
if (FindNextFile(intSearchHandler, ref myData) == false)
{
intLastErrorCode = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
this.CloseHandler();
if (intLastErrorCode != 0 && intLastErrorCode != 0x12)
{
if (this.bolThrowIOException)
WinIOError(intLastErrorCode , strSearchPath);
else
return false;
}
return false;
}
return true;
}//private bool SearchNext()
/// <summary>
/// ??????
/// </summary>
/// <returns>??????</returns>
private bool UpdateCurrentObject()
{
if (intSearchHandler == INVALID_HANDLE_VALUE)
return false;
bool Result = false;
this.objCurrentObject = null;
if ((myData.dwFileAttributes & 0x10) == 0)
{
// ???????
this.bolIsFile = true;
if (this.bolSearchForFile)
Result = true;
}
else 
{
// ???????
this.bolIsFile = false;
if (this.bolSearchForDirectory)
{
if (myData.cFileName == "." || myData.cFileName == "..")
Result = false;
else
Result = true;
}
}
if (Result)
{
if (this.bolReturnStringType)
this.objCurrentObject = myData.cFileName;
else
{
string p = System.IO.Path.Combine(this.strSearchPath, myData.cFileName);
if (this.bolIsFile)
{
this.objCurrentObject = new System.IO.FileInfo(p);
}
else
{
this.objCurrentObject = new System.IO.DirectoryInfo(p);
}
}
this.intSearchedCount++;
}
return Result;
}//private bool UpdateCurrentObject()
#endregion
}//public class FileDirectoryEnumerator : System.Collections.IEnumerator
}

History

  • 21st July, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

yuan yong fu
Web Developer duchang soft
China China
yuan yong fu of duchang soft , come from CHINA , 2008 Microsoft MVP,Use GDI+,XML/XSLT, site:http://www.cnblogs.com/xdesigner/

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionthank you dudememberMember 661742121-Dec-12 19:53 
what a work !
you're a brother for me !
GeneralYou're kidding me,right? [modified]memberPMBottas26-Jul-12 16:13 
After reading through all of this code and seeing how you go through the whole process of calling FindFirstFile and FindNextFile, I finally get to the very last method in the file, UpdateCurrentObject(), where, after combining the search path with the file name in the WIN32_FIND_DATA structure which was returned from the call to FindFirstfile/FindNextFile, you then, UNBELIEVABLY, instantiate either a FileInfo or DirectoryInfo object which is returned to the caller as the property Current.
 
You do realize that as soon as you go to access any member in either of these classes, they will eventually make a call to the base class method FileSystemInfo.Refresh() which then makes a call to Win32Native.FindFirstFile() to get the VERY SAME DATA THAT YOU RETRIEVED WITH A CALL TO THE VERY SAME METHOD AND THEN THREW AWAY!
 
WHAT AN UTTER WASTE OF TIME...
 
Return your 'MyData' object instead of the Directory/FileInfo object. Everything that you need is right there already!
 
You may want to have a look at this CodeProject article instead. I haven't worked through all of it yet, but it seems a little more 'robust'.

modified 26-Jul-12 22:43pm.

GeneralSystem_IO errormemberThere is always the way to do it, but I don't know18-Oct-07 18:43 
I have a liitle project, serach and print pdf files from directory, I compare file name against database records, if match, then print.
I ofein get a error message like System_IO error, file fullpath?...", do you have any idea, what could be the cause?
Thank you for the input.
 

GeneralThe comments rockmemberEvgenyAronov23-Jul-07 0:45 
There are even regions named that way.....
 

GeneralRe: The comments rockmemberRoland Lee25-Jul-07 5:49 
They're chinese characters..
GeneralThis is not needed.memberJoseph Guadagno21-Jul-07 18:38 
Why do this when you have the System.Io namespace? It has a Directory, Folder and File objects for enumeration.
 
Here are some of my articles.
Hope this helps!
Joseph Guadagno
http://www.josephguadagno.net

GeneralRe: This is not needed.memberyfyuan21-Jul-07 20:08 
But when a directory has more than 10000 files or sub directory , the System.IO.Directory.GetFiles/GetDirectories is slow and uncontrollable , the application will freeze .
 
yfyuan

GeneralRe: This is not needed.memberRi Qen-Sin22-Jul-07 3:15 
That is why we use multi-threading. P/Invoke has its overhead too.
 
ROFLOLMFAO

GeneralRe: This is not needed.memberyfyuan22-Jul-07 3:45 
for example , in windows explorer.exe, at the left is directory tree , when you expand a directory ,the tree will show all sub directory , the application will judge each of sub directory has it's owner sub directory , if has , add expand handle before the tree node , else do not add expand handle , there we only to know is a direcotry has sub directory , but System.IO.Directory.GetDirectories return all sub direcotry , This is waste .
 
yfyuan

GeneralRe: This is not needed.memberWilliam H Gates III24-Jul-08 21:42 
Right. Keep in mind that we're talking about 10k files in one folder here. What's the use of multi-threading when you can't report the progress or even cancel the enumeration? So your user can resize / move the form about while they wait? Genius!
GeneralRe: This is absolutely needed.memberseriousconsult6-Feb-09 6:08 
yfyuan is right about this. System.IO is deficient.
 
Multithreading is not as simple a solution as Win32 for this problem and will likely result in more bugs than this approach. The ideal is prob. to combine Win32 with multithreading at the UI layer.
 
Any multithreaded examples that address this same problem on codeproject?
 
http://seriousconsult.blogspot.com/
GeneralRe: This is absolutely needed.memberhussein kaddoura16-Jun-10 11:03 
No only this is absolutely needed but was deemed necessary enough by Microsoft to add DirectoryInfo.EnumerateFiles in .Net 4. This solution would still be needed for all lower frameworks.
GeneralRe: This is absolutely needed.memberMember 119396916-Sep-10 10:12 
In a normal situation you dont need it while programming in dotnet but then when you come across cases where you need to crawl a network drive where the file patch length is going beyond 260 characters you get into trouble with regular Dotnet classes like FileInfo DirectoryInfo etc. They start giving PathTooLongException. so far i did not find a work around in dotnet and the only way i see to move forward is to use Unicode windows API. Having a wrapper like this helps.
GeneralRe: This is absolutely needed.member_henke_20-Apr-11 1:15 
If you guys are interested you could contribute to Castle Transaction Services (http://github.com/haf/Castle.Services.Transaction which aims to provide a new file API that enables both transactional files and un-transacted files -- I take pull-requests.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130617.1 | Last Updated 21 Jul 2007
Article Copyright 2007 by yuan yong fu
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid