Link to home
Start Free TrialLog in
Avatar of dtruong98
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.cFileName)                                                       ' 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.cFileName)

                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

Avatar of Mike Montgomery
Mike Montgomery
Flag of United Kingdom of Great Britain and Northern Ireland image

Hi,

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


Avatar of dtruong98
dtruong98

ASKER

the len(typFindData.cFileName) starts as 260
the len(StrConv(typFindData.cFileName, vbFromUnicode)) = 130

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
Avatar of nffvrxqgrcfqvvc
nffvrxqgrcfqvvc

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Does this solution work on your machine?
Maybe my machine is configured unusually

This runs fine, but the lstrlenA(FindFileData.cFileName)) 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)
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.
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
Avatar of Dana Seaman
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(CurDir)
   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(ByVal 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(sPath & "*.*"), 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

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