Here is another routine, ported to C++/CLI from OpenNetCF. This routine was much quicker using mixed mode code. The reason? A managed structure was passed to an API call in a loop.
Here's the C# code that we ported:
[DllImport("rapi.dll", CharSet=CharSet.Unicode, SetLastError=true)]
internal extern static IntPtr CeFindFirstFile(string lpFileName, byte[] lpFindFileData);
[DllImport("rapi.dll", CharSet=CharSet.Unicode, SetLastError=true)]
internal static extern int CeFindNextFile(IntPtr hFindFile, byte[] lpFindFileData);
[DllImport("rapi.dll", CharSet=CharSet.Unicode, SetLastError=true)]
internal static extern int CeFindClose(IntPtr hFindFile);
/// <summary>
/// This structure describes a file found by the FindFirstFile or FindNextFile.
/// </summary>
/// <seealso cref="M:OpenNETCF.Desktop.Communication.RAPI.EnumFiles(System.String@)"/>
public class FileInformation //WIN32_FIND_DATA
{
private byte[] data = new byte[560];
/// <summary>
/// Byte representation of FileInformation
/// </summary>
public static implicit operator byte[](FileInformation fi)
{
return fi.data;
}
/// <summary>
/// File attributes of the file found.
/// </summary>
public int FileAttributes
{
get
{
return BitConverter.ToInt32(data, 0);
}
}
/// <summary>
/// UTC time at which the file was created.
/// </summary>
public DateTime CreateTime
{
get
{
long time = BitConverter.ToInt64(data, 4);
return DateTime.FromFileTime(time);
}
}
/// <summary>
/// UTC time at which the file was last accessed.
/// </summary>
public DateTime LastAccessTime
{
get
{
long time = BitConverter.ToInt64(data, 12);
return DateTime.FromFileTime(time);
}
}
/// <summary>
/// UTC time at which the file was modified.
/// </summary>
public DateTime LastWriteTime
{
get
{
long time = BitConverter.ToInt64(data, 20);
return DateTime.FromFileTime(time);
}
}
/// <summary>
/// Size, in bytes, of file
/// </summary>
public long FileSize
{
get
{
return BitConverter.ToInt32(data, 28) + (BitConverter.ToInt32(data, 32) << 32);
}
}
/// <summary>
/// Full name of the file
/// </summary>
public string FileName
{
get
{
return Encoding.Unicode.GetString(data, 40, 256).TrimEnd('\x2A');
}
}
}
/// <summary>
/// Provides an ArrayList of FileInformation classes matching the criteria provided in the FileName parameter
/// </summary>
/// <param name="FileName">Long pointer to a null-terminated string that specifies a valid directory or path and filename which can contain wildcard characters (* and ?).</param>
/// <returns>An array of FileInformation objects</returns>
public FileList EnumFiles(string FileName)
{
CheckConnection();
FileList fl = null;
IntPtr hFile = IntPtr.Zero;
FileInformation fi = new FileInformation();
hFile = CeFindFirstFile(FileName, fi);
if(hFile != (IntPtr)INVALID_HANDLE_VALUE)
{
fl = new FileList();
fl.Add(fi);
fi = new FileInformation();
while(CeFindNextFile(hFile, fi) != 0)
{
fl.Add(fi);
fi = new FileInformation();
}
CeFindClose(hFile);
}
return fl;
}
Here is my initial C++/CLI code. It is not efficient, but it works. I'll present the efficient version in another article.
public ref class FileInformation
{
// Fields
private:
UInt32 nFileAttributes;
DateTime dCreationTime;
DateTime dLastAccessTime;
DateTime dLastWriteTime;
UInt32 nFileSizeHigh;
UInt32 nFileSizeLow;
UInt32 nOID;
String^ szFileName;
// Methods
public:
// Properties
property DateTime^ CreateTime
{
DateTime^ get(void)
{
return dCreationTime;
}
void set(DateTime^ value)
{
dCreationTime = *value;
}
}
property UInt32 FileAttributes
{
UInt32 get(void)
{
return nFileAttributes;
}
void set(UInt32 value)
{
nFileAttributes = value;
}
}
property String^ FileName
{
String^ get(void)
{
return szFileName;
}
void set(String^ value)
{
szFileName = value;
}
}
property long long FileSize
{
long long get(void)
{
return (((long long) nFileSizeHigh << 32) + nFileSizeLow);
}
void set(long long value)
{
nFileSizeHigh = (UInt32)(value >> 32);
nFileSizeLow = (UInt32) (value - ((long long) nFileSizeHigh << 32));
}
}
property DateTime^ LastAccessTime
{
DateTime^ get(void)
{
return dLastAccessTime;
}
void set(DateTime^ value)
{
dLastAccessTime = *value;
}
}
property DateTime^ LastWriteTime
{
DateTime^ get(void)
{
return dLastWriteTime;
}
void set(DateTime^ value)
{
dLastWriteTime = *value;
}
}
};
Here is the primary EnumFiles routine:
FileList^ RAPI::EnumFiles(String^ FileName)
{
CheckConnection();
FileList^ ExistingFiles = nullptr;
pin_ptr pfn = PtrToStringChars(FileName);
LPCTSTR pFileName = pfn;
CE_FIND_DATA fi;
HANDLE hSearch = ::CeFindFirstFile(pFileName, &fi);
if (INVALID_HANDLE_VALUE != hSearch)
{
ExistingFiles = gcnew FileList() ;
do
{
// Convert CE_FIND_DATA to FileInformation
// TODO Clean this up.
// I do not need all of the file information structure
// I can use memcpy to move the data into the FileInformation data buffer.
FileInformation^ mfi = gcnew FileInformation();
__int64 c = (((__int64) fi.ftCreationTime.dwHighDateTime) << 32) + (__int64) fi.ftCreationTime.dwLowDateTime;
mfi->CreateTime = DateTime::FromFileTime(c);
c = (((__int64) fi.ftLastAccessTime.dwHighDateTime) << 32) + (__int64) fi.ftLastAccessTime.dwLowDateTime;
mfi->LastAccessTime = DateTime::FromFileTime(c);
c = (((__int64) fi.ftLastWriteTime.dwHighDateTime) << 32) + (__int64) fi.ftLastWriteTime.dwLowDateTime;
mfi->LastWriteTime = DateTime::FromFileTime(c);
mfi->FileSize = (((__int64)fi.nFileSizeHigh << 32) + fi.nFileSizeLow);
mfi->FileAttributes = fi.dwFileAttributes;
mfi->FileName = gcnew String(fi.cFileName);
// Add managed file info to the list
ExistingFiles->Add(mfi);
}
while (::CeFindNextFile(hSearch, &fi));
::CeFindClose(hSearch);
}
return ExistingFiles;
}