dtruong98
asked on
Using FindFirstFileW and FindNextFileW
I can't find any good examples on implementing the Unicode WinAPI calls.
I am trying to get a directory listing, but the directory tree may be longer then 255 characters long. Dir and FSO appear to ignore files and paths past the 255 character mark. I understand that FindFirstFileW can list these files for me.
This is the code I have so far.
(I am assuming that only files exist in the directory for the sake of simplicity and the input to the function is something like "c:\windows\")
Public Function GetAllFiles(ByVal DirPath As String) As String()
Dim lFirstRet As Long, lNextRet
Dim typFindData As WIN32_FIND_DATA
Dim lAttr As Long
Dim nextArrayIndex As Long
nextArrayIndex = 0
Dim sFileList() As String
ReDim sFileList(0) As String
Dim searchpath As String
If Right(DirPath, 1) = "\" Then
searchpath = DirPath & "*.*"
Else
searchpath = DirPath
End If
'Get First File
lFirstRet = FindFirstFileW(StrPtr("\\? \" & searchpath), typFindData) ' do I have to make this a pointer???
If lFirstRet <> -1 Then
FileName = StripNull(typFindData.cFil eName) ' HOW SHOULD THIS WORK?
ReDim Preserve sFileList(nextArrayIndex) As String
sFileList(nextArrayIndex) = DirPath & FileName
nextArrayIndex = nextArrayIndex + 1
'Continue searching until all files in directory are found
Do
lNextRet = FindNextFileW(lFirstRet, typFindData)
If lNextRet = ERROR_NO_MORE_FILES Or lNextRet = 0 Then Exit Do
FileName = StripNull(typFindData.cFil eName)
ReDim Preserve sFileList(nextArrayIndex) As String
sFileList(nextArrayIndex) = DirPath & FileName
nextArrayIndex = nextArrayIndex + 1
Loop
End If
FindClose lFirstRet
GetAllFiles = sFileList
End Function
I have two functions that strip the nulls
Function StripTerminator(ByVal strString As String) As String
' returns everything before the first Null
Dim intZeroPos As Integer
intZeroPos = InStr(strString, Chr$(0))
If intZeroPos > 0 Then
StripTerminator = Left$(strString, intZeroPos - 1)
Else
StripTerminator = strString
End If
End Function
Private Function StripNull(ByVal InString As String) As String
'Returns: all character that are NOT a null character
Dim j As Integer
Dim result As String
result = ""
For j = 1 To Len(InString)
If Mid(InString, j, 1) <> vbNullChar Then
result = result & Mid(InString, j, 1)
End If
Next
StripNull = result
End Function
Originally I was going to use the first function, and stop after the first null,
but I am getting filenames that look like
s a m p l e _ f i l e . t x t
with nulls every other character.
so I wrote the second function.
Now if I can read sample_file.txt file
but if the next file is short.doc
then the pointer looks like it points to
short.doce.txt
(it keeps the length of the previous file and only overwrites the first few characters)
Perhaps this is more of a ptr question, but I wonder if I don't need the ptr at all to implement FindFirstFileW
or if there is a good example of how to do this.
Thanks in advance
I am trying to get a directory listing, but the directory tree may be longer then 255 characters long. Dir and FSO appear to ignore files and paths past the 255 character mark. I understand that FindFirstFileW can list these files for me.
This is the code I have so far.
(I am assuming that only files exist in the directory for the sake of simplicity and the input to the function is something like "c:\windows\")
Public Function GetAllFiles(ByVal DirPath As String) As String()
Dim lFirstRet As Long, lNextRet
Dim typFindData As WIN32_FIND_DATA
Dim lAttr As Long
Dim nextArrayIndex As Long
nextArrayIndex = 0
Dim sFileList() As String
ReDim sFileList(0) As String
Dim searchpath As String
If Right(DirPath, 1) = "\" Then
searchpath = DirPath & "*.*"
Else
searchpath = DirPath
End If
'Get First File
lFirstRet = FindFirstFileW(StrPtr("\\?
If lFirstRet <> -1 Then
FileName = StripNull(typFindData.cFil
ReDim Preserve sFileList(nextArrayIndex) As String
sFileList(nextArrayIndex) = DirPath & FileName
nextArrayIndex = nextArrayIndex + 1
'Continue searching until all files in directory are found
Do
lNextRet = FindNextFileW(lFirstRet, typFindData)
If lNextRet = ERROR_NO_MORE_FILES Or lNextRet = 0 Then Exit Do
FileName = StripNull(typFindData.cFil
ReDim Preserve sFileList(nextArrayIndex) As String
sFileList(nextArrayIndex) = DirPath & FileName
nextArrayIndex = nextArrayIndex + 1
Loop
End If
FindClose lFirstRet
GetAllFiles = sFileList
End Function
I have two functions that strip the nulls
Function StripTerminator(ByVal strString As String) As String
' returns everything before the first Null
Dim intZeroPos As Integer
intZeroPos = InStr(strString, Chr$(0))
If intZeroPos > 0 Then
StripTerminator = Left$(strString, intZeroPos - 1)
Else
StripTerminator = strString
End If
End Function
Private Function StripNull(ByVal InString As String) As String
'Returns: all character that are NOT a null character
Dim j As Integer
Dim result As String
result = ""
For j = 1 To Len(InString)
If Mid(InString, j, 1) <> vbNullChar Then
result = result & Mid(InString, j, 1)
End If
Next
StripNull = result
End Function
Originally I was going to use the first function, and stop after the first null,
but I am getting filenames that look like
s a m p l e _ f i l e . t x t
with nulls every other character.
so I wrote the second function.
Now if I can read sample_file.txt file
but if the next file is short.doc
then the pointer looks like it points to
short.doce.txt
(it keeps the length of the previous file and only overwrites the first few characters)
Perhaps this is more of a ptr question, but I wonder if I don't need the ptr at all to implement FindFirstFileW
or if there is a good example of how to do this.
Thanks in advance
ASKER
the len(typFindData.cFileName) starts as 260
the len(StrConv(typFindData.cF ileName, vbFromUnicode)) = 130
I'm not sure why its only removing half of the nulls ...
the len(StrConv(typFindData.cF
I'm not sure why its only removing half of the nulls ...
What does your WIN32_FIND_DATA type structure look like?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Does this solution work on your machine?
Maybe my machine is configured unusually
This runs fine, but the lstrlenA(FindFileData.cFil eName)) returns a 1 each time
*it appears to STOP when it hits the first null character*
Why are my filenames coming in with a NULL every other character?
I am running XP on an intel chip if that matters.
(I also had to move the RtlZeroMemory command inside the loop so that it reset each time so I wouldn't keep the len of the previous filename)
Maybe my machine is configured unusually
This runs fine, but the lstrlenA(FindFileData.cFil
*it appears to STOP when it hits the first null character*
Why are my filenames coming in with a NULL every other character?
I am running XP on an intel chip if that matters.
(I also had to move the RtlZeroMemory command inside the loop so that it reset each time so I wouldn't keep the len of the previous filename)
Yes it works.
<< (I also had to move the RtlZeroMemory command inside the loop
Bad idea, I would change it back.
<< it appears to STOP when it hits the first null character
lstrlenA API gives a length of a string without the leading NULL characters.
<< Why are my filenames coming in with a NULL every other character?
I am un-certain about this situation, the example I provided works perfectly on my computer. If you revised it then it could be displaying regular string as unicode.
Please post the exact code your using as it seems you have alot of questions that are not identical to what I am getting with the code example I posted above.
<< (I also had to move the RtlZeroMemory command inside the loop
Bad idea, I would change it back.
<< it appears to STOP when it hits the first null character
lstrlenA API gives a length of a string without the leading NULL characters.
<< Why are my filenames coming in with a NULL every other character?
I am un-certain about this situation, the example I provided works perfectly on my computer. If you revised it then it could be displaying regular string as unicode.
Please post the exact code your using as it seems you have alot of questions that are not identical to what I am getting with the code example I posted above.
Like I said earlier - the filenames are in Unicode - you will need to convert to ANSI string before testing for Null
If you look at the filename in an MFT record it is as you describe above. Unicode allows for 0-65535 characters. ASCII Characters 0-255 (0x0000 - 0x00FF in unicode) so the '00' (unused) part of the filenames are the 'nulls' that you are finding.
Mike
If you look at the filename in an MFT record it is as you describe above. Unicode allows for 0-65535 characters. ASCII Characters 0-255 (0x0000 - 0x00FF in unicode) so the '00' (unused) part of the filenames are the 'nulls' that you are finding.
Mike
You must use StrConv before calling StripNull.
Following code returns Unicode names OK under NT/2000/XP.
Function returns a variant which holds a string array of the filenames.
'In form:
Option Explicit
Private Sub Form_Load()
Dim vArray As Variant
Dim i As Long
vArray = UnicodeFolderEnum_API(CurD ir)
For i = 0 To UBound(vArray)
Debug.Print vArray(i)
Next
End Sub
'In Module
Option Explicit
Public Type WIN32_FIND_DATA_X
dwFileAttributes As Long
ftCreationTime As Currency
ftLastAccessTime As Currency
ftLastWriteTime As Currency
nFileSizeBig As Currency
dwReserved0 As Long
dwReserved1 As Long
cFileName As String * 260
cAlternate As String * 14
End Type
Public Declare Function FindFirstFileW Lib "kernel32" (ByVal lpFilename As Long, lpWIN32_FIND_DATA As WIN32_FIND_DATA_X) As Long
Public Declare Function FindNextFileW Lib "kernel32" (ByVal hFindFile As Long, lpWIN32_FIND_DATA As WIN32_FIND_DATA_X) As Long
Public Declare Function FindClose Lib "kernel32" (ByVal hFindFile As Long) As Long
Public Function UnicodeFolderEnum_API(ByVa l sPath As String) As Variant
Dim Win32Fd As WIN32_FIND_DATA_X
Dim lHandle As Long
Dim m_FileCount As Long
Dim sFileName As String
Dim sArray() As String
sPath = QualifyPath(sPath)
lHandle = FindFirstFileW(StrPtr(sPat h & "*.*"), Win32Fd)
If lHandle > 0 Then
Do
If (Win32Fd.dwFileAttributes And vbDirectory) = 0 Then
ReDim Preserve sArray(m_FileCount)
sFileName = StripNull(StrConv(Win32Fd. cFileName, vbFromUnicode))
sArray(m_FileCount) = sFileName
m_FileCount = m_FileCount + 1
End If
Loop While FindNextFileW(lHandle, Win32Fd) > 0
End If
FindClose (lHandle)
If m_FileCount > 0 Then
UnicodeFolderEnum_API = sArray
End If
End Function
Public Function StripNull(ByVal StrIn As String) As String
Dim nul As Long
nul = InStr(StrIn, vbNullChar)
Select Case nul
Case Is > 1
StripNull = Left$(StrIn, nul - 1)
Case 1
StripNull = ""
Case 0
StripNull = Trim$(StrIn)
End Select
End Function
Public Function QualifyPath(ByVal Path As String) As String
Dim Delimiter As String ' segmented path delimiter
If InStr(Path, "://") > 0 Then ' it's a URL path
Delimiter = "/" ' use URL path delimiter
Else ' it's a disk based path
Delimiter = "\" ' use disk based path delimiter
End If
Select Case Right$(Path, 1) ' whats last character in path?
Case "/", "\" ' it's one of the valid delimiters
QualifyPath = Path ' use the supplied path
Case Else ' needs a trailing path delimiter
QualifyPath = Path & Delimiter ' append it
End Select
End Function
Following code returns Unicode names OK under NT/2000/XP.
Function returns a variant which holds a string array of the filenames.
'In form:
Option Explicit
Private Sub Form_Load()
Dim vArray As Variant
Dim i As Long
vArray = UnicodeFolderEnum_API(CurD
For i = 0 To UBound(vArray)
Debug.Print vArray(i)
Next
End Sub
'In Module
Option Explicit
Public Type WIN32_FIND_DATA_X
dwFileAttributes As Long
ftCreationTime As Currency
ftLastAccessTime As Currency
ftLastWriteTime As Currency
nFileSizeBig As Currency
dwReserved0 As Long
dwReserved1 As Long
cFileName As String * 260
cAlternate As String * 14
End Type
Public Declare Function FindFirstFileW Lib "kernel32" (ByVal lpFilename As Long, lpWIN32_FIND_DATA As WIN32_FIND_DATA_X) As Long
Public Declare Function FindNextFileW Lib "kernel32" (ByVal hFindFile As Long, lpWIN32_FIND_DATA As WIN32_FIND_DATA_X) As Long
Public Declare Function FindClose Lib "kernel32" (ByVal hFindFile As Long) As Long
Public Function UnicodeFolderEnum_API(ByVa
Dim Win32Fd As WIN32_FIND_DATA_X
Dim lHandle As Long
Dim m_FileCount As Long
Dim sFileName As String
Dim sArray() As String
sPath = QualifyPath(sPath)
lHandle = FindFirstFileW(StrPtr(sPat
If lHandle > 0 Then
Do
If (Win32Fd.dwFileAttributes And vbDirectory) = 0 Then
ReDim Preserve sArray(m_FileCount)
sFileName = StripNull(StrConv(Win32Fd.
sArray(m_FileCount) = sFileName
m_FileCount = m_FileCount + 1
End If
Loop While FindNextFileW(lHandle, Win32Fd) > 0
End If
FindClose (lHandle)
If m_FileCount > 0 Then
UnicodeFolderEnum_API = sArray
End If
End Function
Public Function StripNull(ByVal StrIn As String) As String
Dim nul As Long
nul = InStr(StrIn, vbNullChar)
Select Case nul
Case Is > 1
StripNull = Left$(StrIn, nul - 1)
Case 1
StripNull = ""
Case 0
StripNull = Trim$(StrIn)
End Select
End Function
Public Function QualifyPath(ByVal Path As String) As String
Dim Delimiter As String ' segmented path delimiter
If InStr(Path, "://") > 0 Then ' it's a URL path
Delimiter = "/" ' use URL path delimiter
Else ' it's a disk based path
Delimiter = "\" ' use disk based path delimiter
End If
Select Case Right$(Path, 1) ' whats last character in path?
Case "/", "\" ' it's one of the valid delimiters
QualifyPath = Path ' use the supplied path
Case Else ' needs a trailing path delimiter
QualifyPath = Path & Delimiter ' append it
End Select
End Function
If you are using the WIN32_FIND_DATA that I supplied you with you will notice there shouldn't be a need for a conversion. It will automatically convert it back to ANSI on its way it.
cFileName(1 To (260 + 16) * 2) As Byte
cFileName(1 To (260 + 16) * 2) As Byte
Filenames in NT/XP are in unicode format, if you convert the filenames from unicode to ASCI you should be ok.
Dim strA As String
strA = StrConv(strA, vbFromUnicode)
Look up the strconv function in VB
Mike