FtpFindFirstFile always returns zero

vincehardwick

New Member
Joined
Feb 4, 2021
Messages
20
Office Version
  1. 365
Platform
  1. Windows
I've hit a brick wall trying to get FTP working in Excel VBA (64-bit Office on 64-bit Windows 10). As an early proof of concept, I'm just trying to list the name of the single text file that I've uploaded to the FTP server.

The sub I'm running is ListFilesOnFTP. hOpen and hConnection both get set to handle values successfully by InternetOpen and InternetConnect respecitvely.

blReturn is set to True by FtpSetCurrentDirectory, indicating that this is not failing.

The problem I have is in EnumFiles - no matter what combination of wildcards I use for lpszSearchfile, FtpFindFirstFile always returns zero, and therefore EnumFiles exits immediately.

Obviously I have provided placeholder values below for strFTPServerIP, strUsername, strPassword and strRemoteDirectory, but I am 100% certain that the IP address and credentials are correct, and that the directory with the provided name does exist under the root of the FTP server.

Any ideas where I'm going wrong here?

Relevant constant and type declarations:

VBA Code:
Private Const MAX_PATH As Integer = 260
Private Const INTERNET_FLAG_RELOAD = &H80000000
Private Const INTERNET_FLAG_NO_CACHE_WRITE = &H4000000
Private Const INTERNET_OPEN_TYPE_PRECONFIG = 0
Private Const INTERNET_DEFAULT_FTP_PORT = 21
Private Const INTERNET_SERVICE_FTP = 1
Private Const INTERNET_FLAG_PASSIVE = &H8000000
Private Const INTERNET_NO_CALLBACK = 0

Private Type FILETIME

    dwLowDateTime As Long
    dwHighDateTime As Long
    
End Type

Private Type WIN32_FIND_DATA

    dwFileAttributes As Long
    ftCreationTime As FILETIME
    ftLastAccessTime As FILETIME
    ftLastWriteTime As FILETIME
    nFileSizeHigh As Long
    nFileSizeLow As Long
    dwReserved0 As Long
    dwReserved1 As Long
    cFileName As String * MAX_PATH
    cAlternate As String * 14
    
End Type

Relevant wininet.dll function declarations (please note - I do have these wrapped in the usual #If VBA7 Then... #Else... #End If conditional compilation structures, with 32-bit compatible declarations in the else clause, but for brevity I have only provided the PtrSafe functions here):

VBA Code:
    Private Declare PtrSafe Function InternetCloseHandle Lib "wininet.dll" ( _
        ByVal hInet As LongPtr) As LongPtr

    Private Declare PtrSafe Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" ( _
        ByVal sAgent As String, _
        ByVal lAccessType As LongPtr, _
        ByVal sProxyName As String, _
        ByVal sProxyBypass As String, _
        ByVal lFlags As LongPtr) As LongPtr

    Private Declare PtrSafe Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" ( _
        ByVal hInternetSession As LongPtr, _
        ByVal sServerName As String, _
        ByVal nServerPort As LongPtr, _
        ByVal sUsername As String, _
        ByVal sPassword As String, _
        ByVal lService As LongPtr, _
        ByVal lFlags As LongPtr, _
        ByVal lContext As LongPtr) As LongPtr

    Private Declare PtrSafe Function FtpSetCurrentDirectory Lib "wininet.dll" Alias "FtpSetCurrentDirectoryA" ( _
        ByVal hFtpSession As LongPtr, _
        ByVal lpszDirectory As String) As Boolean

    Private Declare PtrSafe Function FtpGetCurrentDirectory Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" ( _
        ByVal hFtpSession As LongPtr, _
        ByVal lpszCurrentDirectory As String, _
        ByVal lpdwCurrentDirectory As LongPtr) As LongPtr

    Private Declare PtrSafe Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" ( _
        ByVal hFtpSession As LongPtr, _
        ByVal lpszSearchFile As String, _
        ByRef lpFindFileData As WIN32_FIND_DATA, _
        ByVal dwFlags As LongPtr, _
        ByVal dwContent As LongPtr) As LongPtr

Procedures using the above:

VBA Code:
Public Sub EnumFiles(ByVal hConnection As LongPtr)

    Dim pData As WIN32_FIND_DATA
    
    #If VBA7 Then
        Dim hFind As LongPtr, lRet As LongPtr
    #Else
        Dim hFind As Long, lRet As Long
    #End If

    ' Create a buffer
    pData.cFileName = String(MAX_PATH, vbNullChar)

    ' Find the first file
    hFind = FtpFindFirstFile(hConnection, "*.*", pData, INTERNET_FLAG_RELOAD Or INTERNET_FLAG_NO_CACHE_WRITE, 0)
    
    ' If there's no file, then exit sub
    If hFind = 0 Then Exit Sub

    ' Show the filename
    MsgBox Left$(pData.cFileName, InStr(1, pData.cFileName, String(1, 0), vbBinaryCompare) - 1)

    Do
    
        ' Create a buffer
        pData.cFileName = String(MAX_PATH, vbNullChar)
        
        ' Find the next file
        lRet = InternetFindNextFile(hFind, pData)
        
        ' If there's no next file, exit loop
        If lRet = 0 Then Exit Do
        
        ' Show the filename
        MsgBox Left$(pData.cFileName, InStr(1, pData.cFileName, String(1, 0), vbBinaryCompare) - 1)
    
    Loop

    ' Close the search handle
    InternetCloseHandle hFind

End Sub

Public Sub ListFilesOnFTP()

    #If VBA7 Then
        Dim hOpen As LongPtr, hConnection As LongPtr
    #Else
        Dim hOpen As Long, hConnection As Long
    #End If
    
    Dim blReturn As Boolean
    Dim strFTPServerIP As String, strUsername As String, strPassword As String, _
        strRemoteDirectory As String
    
    strFTPServerIP = "12.345.678.901"
    strUsername = "username"
    strPassword = "password"
    strRemoteDirectory = "directory_name/"
    
    ' Open an internet connection
    hOpen = InternetOpen("FTP", _
        INTERNET_OPEN_TYPE_PRECONFIG, _
        vbNullString, _
        vbNullString, _
        0)
    
    hConnection = InternetConnect( _
        hOpen, _
        strFTPServerIP, _
        INTERNET_DEFAULT_FTP_PORT, _
        strUsername, _
        strPassword, _
        INTERNET_SERVICE_FTP, _
        INTERNET_FLAG_PASSIVE, _
        INTERNET_NO_CALLBACK)
    
    blReturn = FtpSetCurrentDirectory(hConnection, strRemoteDirectory)
    
    Call EnumFiles(hConnection)
    
    InternetCloseHandle hConnection
    InternetCloseHandle hOpen

End Sub
 
so I think it would be neither here nor there
Um, doubtful. Using the wrong variable types with API calls is generally not a good idea. If you're lucky, you'll just get strange results; if not, your app is likely to abruptly terminate.
 
Upvote 0

Excel Facts

How to create a cell-sized chart?
Tiny charts, called Sparklines, were added to Excel 2010. Look for Sparklines on the Insert tab.
Um, doubtful. Using the wrong variable types with API calls is generally not a good idea. If you're lucky, you'll just get strange results; if not, your app is likely to abruptly terminate.
So the PtrSafe keyword wouldn't ensure that LongPtrs are passed as Longs or LongLongs according to the bitness of the system the code is compiled on? I thought that was the point of it? Or does that keyword only handle the return type of the function, not its arguments?

Either way, the FTP server I created when exploring this approach has since been deleted from Azure (I gave up and abandoned this approach), so I no longer need the question to be answered, but somebody in the future might struggle with the same thing... so should I leave the thread live? Or mark this post as solution?
 
Upvote 0
So the PtrSafe keyword wouldn't ensure that LongPtrs are passed as Longs or LongLongs according to the bitness of the system the code is compiled on?
No that's exactly what it does. The point is that you should only pass a LongLong on 64bit when it's a pointer/handle (or the API in question specifically expects it). A lot of the arguments in those functions are Long whether you are using 64bit or 32bit.
 
Upvote 0
No that's exactly what it does. The point is that you should only pass a LongLong on 64bit when it's a pointer/handle (or the API in question specifically expects it). A lot of the arguments in those functions are Long whether you are using 64bit or 32bit.
Ah, I see. Yes I was a bit overzealous with using LongPtr for arguments and not just return types. Thanks for the clarification Rory, my understanding of when to use LongPtr and what for is now improved :) happy to mark this thread as solved if that is the done thing when an answer is no longer required by the OP, similarly happy to leave it live in case someone else encounters the same issue in future.
 
Upvote 0
I'd leave it open as there is no specific solution posted here currently.
 
Upvote 0
Well I started trying your code this morning, with some changes to the API declarations. I stepped through the code, and it wasn't making it past the InternetConnect stage, returning 0 for the hConnection pointer. According to LastDLLError, it wasn't resolving the FTP server, but that's more likely my fault, because I was using a random test FTP server to test it on, so will try and see if I can get access to to a proper one and try again. You've got me curious now... :-)
 
Upvote 0
Well I started trying your code this morning, with some changes to the API declarations. I stepped through the code, and it wasn't making it past the InternetConnect stage, returning 0 for the hConnection pointer. According to LastDLLError, it wasn't resolving the FTP server, but that's more likely my fault, because I was using a random test FTP server to test it on, so will try and see if I can get access to to a proper one and try again. You've got me curious now... :)
That's kind of you to put some work into investigating this, thanks.

However, I have news which may save you some time... (the recent activity prompted me to try to figure this out again).

Although my original FTP server is now long gone, I too just made some changes to the API declarations after Rory's advice and went looking for a test FTP server - found one here: FTP Test - DLP Test

Connected to it in File Explorer, used netstat -abno in command prompt to find the 'foreign address' with port 21 at the end of it, and used that IP in my code, along with the credentials listed on the webpage above.

And then boom...

debug output.jpg


actual directory content.jpg


As you can see, FtpGetCurrentDirectory doesn't like one of the parameters I'm passing it (a quick Google suggests that's what LastDllError code 87 means), but FtpSetCurrentDirectory obviously did run OK (the directory was set successfully), so the hFtpSession argument being a LongPtr can't be a huge problem (or it is a problem for FtpGetCurrentDirectory, but weirdly not for FtpSetCurrentDirectory).

@RoryA I tried changing all the parameters in the PtrSafe declarations to Longs, but because some of them are LongPtrs returned by other API calls, to get the code to compile I had to relent and put some parameters back to being LongPtrs.

So either the changes to the API declarations worked (thanks Rory for the heads up), or there was a problem with the previous FTP server that I got rid of which caused FtpFindFirstFile to fail.

Anyway... my working code in full (bear in mind that because this is an open test FTP server, the directories which exist on there are changing all the time... the one in my code below and in the screenshots above is now no longer there!! Just connect to it via File Explorer or FileZilla first to get the name of a directory which currently exists, and set strRemoteDirectory in ListFilesOnFTP to that):

Code:
Option Explicit

Private Const FTP_TRANSFER_TYPE_UNKNOWN = &H0
Private Const FTP_TRANSFER_TYPE_ASCII = &H1
Private Const FTP_TRANSFER_TYPE_BINARY = &H2

Private Const INTERNET_SERVICE_FTP = 1
Private Const INTERNET_SERVICE_HTTP = 3

Private Const INTERNET_OPEN_TYPE_PRECONFIG = 0 ' use registry configuration
Private Const INTERNET_OPEN_TYPE_DIRECT = 1 ' direct to net
Private Const INTERNET_OPEN_TYPE_PROXY = 3 ' via named proxy
Private Const INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY = 4 ' prevent using java/script/INS

Private Const INTERNET_CONNECTION_CONFIGURED = &H40
Private Const INTERNET_CONNECTION_LAN = &H2
Private Const INTERNET_CONNECTION_MODEM = &H1
Private Const INTERNET_CONNECTION_OFFLINE = &H20
Private Const INTERNET_CONNECTION_PROXY = &H4
Private Const INTERNET_RAS_INSTALLED = &H10

Private Const INTERNET_INVALID_PORT_NUMBER = 0
Private Const INTERNET_DEFAULT_FTP_PORT = 21
Private Const INTERNET_DEFAULT_GOPHER_PORT = 70
Private Const INTERNET_DEFAULT_HTTP_PORT = 80
Private Const INTERNET_DEFAULT_HTTPS_PORT = 443
Private Const INTERNET_DEFAULT_SOCKS_PORT = 1080
Private Const INTERNET_NO_CALLBACK = 0

Private Const INTERNET_FLAG_PASSIVE = &H8000000 ' used for FTP connections
Private Const INTERNET_FLAG_RELOAD = &H80000000
Private Const INTERNET_FLAG_NO_CACHE_WRITE = &H4000000

Private Const MAX_PATH As Integer = 260
Private Const GENERIC_READ = &H80000000
Private Const MAXDWORD As Double = (2 ^ 32) - 1
Private Const ERROR_NO_MORE_FILES = 18&

Private Type FILETIME

    dwLowDateTime As Long
    dwHighDateTime As Long
    
End Type

Private Type WIN32_FIND_DATA

    dwFileAttributes As Long
    ftCreationTime As FILETIME
    ftLastAccessTime As FILETIME
    ftLastWriteTime As FILETIME
    nFileSizeHigh As Long
    nFileSizeLow As Long
    dwReserved0 As Long
    dwReserved1 As Long
    cFileName As String * MAX_PATH
    cAlternate As String * 14
    
End Type

#If Win64 Then

    Private Declare PtrSafe Function InternetGetConnectedState Lib "wininet.dll" ( _
        ByRef dwFlags As Long, ByVal dwReserved As Long) As LongPtr
    
    Private Declare PtrSafe Function InternetCheckConnection Lib "wininet.dll" Alias "InternetCheckConnectionA" ( _
        ByVal lpszUrl As String, _
        ByVal dwFlags As Long, _
        ByVal dwReserved As Long) As Boolean
    
    Private Declare PtrSafe Function InternetCloseHandle Lib "wininet.dll" ( _
        ByVal hInet As LongPtr) As LongPtr

    Private Declare PtrSafe Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" ( _
        ByVal sAgent As String, _
        ByVal lAccessType As Long, _
        ByVal sProxyName As String, _
        ByVal sProxyBypass As String, _
        ByVal lFlags As Long) As LongPtr

    Private Declare PtrSafe Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" ( _
        ByVal hInternetSession As LongPtr, _
        ByVal sServerName As String, _
        ByVal nServerPort As Long, _
        ByVal sUsername As String, _
        ByVal sPassword As String, _
        ByVal lService As Long, _
        ByVal lFlags As Long, _
        ByVal lContext As Long) As LongPtr

    Private Declare PtrSafe Function FtpSetCurrentDirectory Lib "wininet.dll" Alias "FtpSetCurrentDirectoryA" ( _
        ByVal hFtpSession As LongPtr, _
        ByVal lpszDirectory As String) As Boolean

    Private Declare PtrSafe Function FtpGetCurrentDirectory Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" ( _
        ByVal hFtpSession As LongPtr, _
        ByVal lpszCurrentDirectory As String, _
        ByVal lpdwCurrentDirectory As LongPtr) As Boolean

    Private Declare PtrSafe Function InternetGetLastResponseInfo Lib "wininet.dll" Alias "InternetGetLastResponseInfoA" ( _
        ByVal lpdwError As Long, _
        ByVal lpszBuffer As String, _
        ByVal lpdwBufferLength As Long) As Boolean

    Private Declare PtrSafe Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" ( _
        ByVal hFtpSession As LongPtr, _
        ByVal lpszSearchFile As String, _
        ByRef lpFindFileData As WIN32_FIND_DATA, _
        ByVal dwFlags As Long, _
        ByVal dwContent As Long) As LongPtr

    Private Declare PtrSafe Function InternetFindNextFile Lib "wininet.dll" Alias "InternetFindNextFileA" ( _
        ByVal hFind As LongPtr, _
        ByRef lpFindData As WIN32_FIND_DATA) As LongPtr

#Else

    Private Declare Function InternetGetConnectedState Lib "wininet.dll" ( _
        ByRef dwflags As Long, ByVal dwReserved As Long) As Long
    
    Private Declare Function InternetCheckConnection Lib "wininet.dll" Alias "InternetCheckConnectionA" ( _
        ByVal lpszUrl As String, _
        ByVal dwFlags As Long, _
        ByVal dwReserved As Long) As Boolean
    
    Private Declare Function InternetCloseHandle Lib "wininet.dll" ( _
        ByVal hInet As Long) As Long

    Private Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" ( _
        ByVal sAgent As String, _
        ByVal lAccessType As Long, _
        ByVal sProxyName As String, _
        ByVal sProxyBypass As String, _
        ByVal lFlags As Long) As Long

    Private Declare Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" ( _
        ByVal hInternetSession As Long, _
        ByVal sServerName As String, _
        ByVal nServerPort As Integer, _
        ByVal sUserName As String, _
        ByVal sPassword As String, _
        ByVal lService As Long, _
        ByVal lFlags As Long, _
        ByVal lContext As Long) As Long

    Private Declare Function FtpSetCurrentDirectory Lib "wininet.dll" Alias "FtpSetCurrentDirectoryA" ( _
        ByVal hFtpSession As Long, _
        ByVal lpszDirectory As String) As Boolean
    
    Private Declare Function FtpGetCurrentDirectory Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" ( _
        ByVal hFtpSession As Long, _
        ByVal lpszCurrentDirectory As String, _
        lpdwCurrentDirectory As Long) As Boolean

    Private Declare Function InternetGetLastResponseInfo Lib "wininet.dll" Alias "InternetGetLastResponseInfoA" ( _
        lpdwError As Long, _
        ByVal lpszBuffer As String, _
        lpdwBufferLength As Long) As Boolean

    Private Declare Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" ( _
        ByVal hFtpSession As Long, _
        ByVal lpszSearchFile As String, _
        lpFindFileData As WIN32_FIND_DATA, _
        ByVal dwFlags As Long, _
        ByVal dwContent As Long) As Long

    Private Declare Function InternetFindNextFile Lib "wininet.dll" Alias "InternetFindNextFileA" ( _
        ByVal hFind As Long, _
        lpFindData As WIN32_FIND_DATA) As Long

#End If

Public Sub EnumFiles(ByVal hConnection As LongPtr)

    Dim pData As WIN32_FIND_DATA
    
    #If Win64 Then
    
        Dim hFind As LongPtr, lRet As LongPtr
    
    #Else
    
        Dim hFind As Long, lRet As Long
    
    #End If

    ' Create a buffer
    pData.cFileName = String(MAX_PATH, vbNullChar)

    ' Find the first file
    hFind = FtpFindFirstFile(hConnection, "*.*", pData, INTERNET_FLAG_RELOAD Or INTERNET_FLAG_NO_CACHE_WRITE, 0)
    
    ' If there's no file, then exit sub
    If hFind = 0 Then Exit Sub

    ' Show the filename
    Debug.Print vbNewLine & "FILES FOUND:" & vbNewLine & Left$(pData.cFileName, InStr(1, pData.cFileName, String(1, 0), vbBinaryCompare) - 1)

    Do
    
        ' Create a buffer
        pData.cFileName = String(MAX_PATH, vbNullChar)
        
        ' Find the next file
        lRet = InternetFindNextFile(hFind, pData)
        
        ' If there's no next file, exit loop
        If lRet = 0 Then Exit Do
        
        ' Show the filename
        Debug.Print Left$(pData.cFileName, InStr(1, pData.cFileName, String(1, 0), vbBinaryCompare) - 1)
    
    Loop

    ' Close the search handle
    InternetCloseHandle hFind

End Sub

Public Sub ListFilesOnFTP()

    #If Win64 Then

        Dim hOpen As LongPtr, hConnection As LongPtr, lngCurrentDirLength As LongPtr

    #Else
    
        Dim hOpen As Long, hConnection As Long, lngCurrentDirLength As Long
    
    #End If
    
    Dim fConnectionTestFlags As Long
    Dim blCheckConnection As Boolean
    Dim blCheckGetDirSuccess As Boolean, blCheckSetDirSuccess As Boolean
    Dim strFTPServerIP As String, strUsername As String, strPassword As String
    Dim strRemoteDirectory As String, strCurrentDirectory As String
    
    strFTPServerIP = "35.163.228.146"
    strUsername = "dlpuser"
    strPassword = "rNrKYTX9g7z3RgJRmxWuGHbeu"
    strRemoteDirectory = "File"
    
    ' Open an internet connection
    hOpen = InternetOpen("FTP Client", _
        INTERNET_OPEN_TYPE_DIRECT, _
        vbNullString, _
        vbNullString, _
        0)
    
    hConnection = InternetConnect( _
        hOpen, _
        strFTPServerIP, _
        INTERNET_DEFAULT_FTP_PORT, _
        strUsername, _
        strPassword, _
        INTERNET_SERVICE_FTP, _
        INTERNET_FLAG_PASSIVE, _
        INTERNET_NO_CALLBACK)
    
    blCheckConnection = InternetCheckConnection(strFTPServerIP, 0, 0)
    
    If blCheckConnection Then
    
        InternetGetConnectedState fConnectionTestFlags, 0
        
        ' Debug.Print "INTERNET_CONNECTION_CONFIGURED = " & CBool((fConnectionTestFlags And INTERNET_CONNECTION_CONFIGURED) > 0)
        ' Debug.Print "INTERNET_CONNECTION_LAN = " & CBool((fConnectionTestFlags And INTERNET_CONNECTION_LAN) > 0)
        ' Debug.Print "INTERNET_CONNECTION_MODEM = " & CBool((fConnectionTestFlags And INTERNET_CONNECTION_MODEM) > 0)
        ' Debug.Print "INTERNET_CONNECTION_OFFLINE = " & CBool((fConnectionTestFlags And INTERNET_CONNECTION_OFFLINE) > 0)
        ' Debug.Print "INTERNET_CONNECTION_PROXY = " & CBool((fConnectionTestFlags And INTERNET_CONNECTION_PROXY) > 0)
        ' Debug.Print "INTERNET_RAS_INSTALLED = " & CBool((fConnectionTestFlags And INTERNET_RAS_INSTALLED) > 0)
    
        blCheckSetDirSuccess = FtpSetCurrentDirectory(hConnection, strRemoteDirectory)
        
        Debug.Print "Set current directory successful = " & blCheckSetDirSuccess
        
        If blCheckSetDirSuccess Then
        
            ' Create buffer
            strCurrentDirectory = String(MAX_PATH, vbNullChar)

            blCheckGetDirSuccess = FtpGetCurrentDirectory(hConnection, strCurrentDirectory, lngCurrentDirLength)
            
            If blCheckGetDirSuccess Then
            
                Debug.Print "Current directory = " & strCurrentDirectory
            
            Else
            
                Debug.Print "Get current directory call failed - " & GetError
                Debug.Print "LastDllError code = " & Err.LastDllError
            
            End If
        
            Call EnumFiles(hConnection)
            
        End If
    
    End If
    
    InternetCloseHandle hConnection
    InternetCloseHandle hOpen

End Sub

Private Function GetError() As String

    Dim lngErrorCode As Long, strError As String, lngBufferLength As Long
    Dim blGetInfoSuccess As Boolean
    
    ' Get the required buffer size
    InternetGetLastResponseInfo lngErrorCode, strError, lngBufferLength
    
    ' Create a buffer
    strError = String(lngBufferLength, 0)
    
    ' Retrieve the last response info
    blGetInfoSuccess = InternetGetLastResponseInfo(lngErrorCode, strError, lngBufferLength)
    
    If blGetInfoSuccess Then
    
        GetError = "Error code " & CStr(lngErrorCode) & ": " & strError
    
    Else
    
        GetError = "error information could not be retrieved"
    
    End If

End Function
 
Last edited:
Upvote 0
Solution
This is great. Thank you for sharing your working code.
BTW, on just a quick scan, the API declarations look correct - as a general (though not always correct) rule, arguments starting with lower case 'h' indicate a pointer and so need to be LongPtr, whereas Flags, etc, do not. If you were using the Unicode version of the API (with the function name ending with W rather than A), a string argument would need to be converted from Long to LongPtr too, but your APIs all seem to be Ansi-based, so that's not really an issue.
I'll give it a go over the weekend. Thanks again for humoring my curiosity, even though you've moved on from using VBA for your project.
 
Upvote 0
As you can see, FtpGetCurrentDirectory doesn't like one of the parameters I'm passing it (a quick Google suggests that's what LastDllError code 87 means), but FtpSetCurrentDirectory obviously did run OK (the directory was set successfully), so the hFtpSession argument being a LongPtr can't be a huge problem (or it is a problem for FtpGetCurrentDirectory, but weirdly not for FtpSetCurrentDirectory).

Your declaration of FtpGetCurrentDirectory:
VBA Code:
    Private Declare PtrSafe Function FtpGetCurrentDirectory Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" ( _
        ByVal hFtpSession As LongPtr, _
        ByVal lpszCurrentDirectory As String, _
        ByVal lpdwCurrentDirectory As LongPtr) As Boolean
says that the 3rd argument, lpdwCurrentDirectory, is a pointer passed by value, i.e. the address of a (Long) variable.

Your variables and call to FtpGetCurrentDirectory:
VBA Code:
    #If Win64 Then

        Dim hOpen As LongPtr, hConnection As LongPtr, lngCurrentDirLength As LongPtr

    #Else
   
        Dim hOpen As Long, hConnection As Long, lngCurrentDirLength As Long
   
    #End If

           ' Create buffer
            strCurrentDirectory = String(MAX_PATH, vbNullChar)

            blCheckGetDirSuccess = FtpGetCurrentDirectory(hConnection, strCurrentDirectory, lngCurrentDirLength)
           
            If blCheckGetDirSuccess Then
           
                Debug.Print "Current directory = " & strCurrentDirectory
The problem is that the 3rd argument, lngCurrentDirLength, which should be an address, is undefined (zero) and therefore the API has nowhere to write the length of the directory string.

The correct code, given your declarations of the API and lngCurrentDirLength, should be:
VBA Code:
        If blCheckSetDirSuccess Then
       
            ' Create buffer
            strCurrentDirectory = String(MAX_PATH, vbNullChar)
            Dim dirLen As Long
            dirLen = MAX_PATH
            lngCurrentDirLength = VarPtr(dirLen)
           
            blCheckGetDirSuccess = FtpGetCurrentDirectory(hConnection, strCurrentDirectory, lngCurrentDirLength)
           
            If blCheckGetDirSuccess Then
           
                strCurrentDirectory = Left(strCurrentDirectory, dirLen)
                Debug.Print "Current directory = " & strCurrentDirectory

Alternative 1 - change lngCurrentDirLength to a Long and pass its address to the API using the VarPtr() function, and then you don't need the extra dirLen variable:
VBA Code:
    Dim lngCurrentDirLength As Long


            ' Create buffer
            strCurrentDirectory = String(MAX_PATH, vbNullChar)
            lngCurrentDirLength = MAX_PATH
           
            blCheckGetDirSuccess = FtpGetCurrentDirectory(hConnection, strCurrentDirectory, VarPtr(lngCurrentDirLength))
           
            If blCheckGetDirSuccess Then
           
                strCurrentDirectory = Left(strCurrentDirectory, lngCurrentDirLength)
                Debug.Print "Current directory = " & strCurrentDirectory

Alternative 2 - change the 3rd argument of the FtpGetCurrentDirectory declaration to a Long passed by reference, so that VBA will automatically pass the address of a (Long) variable and then you don't need VarPtr() to get its address:
VBA Code:
    Private Declare PtrSafe Function FtpGetCurrentDirectory Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" ( _
        ByVal hFtpSession As LongPtr, _
        ByVal lpszCurrentDirectory As String, _
        ByRef lpdwCurrentDirectory As Long) As Boolean
and change lngCurrentDirLength to a Long and the call to the API would be:
VBA Code:
    Dim lngCurrentDirLength As Long


            ' Create buffer
            strCurrentDirectory = String(MAX_PATH, vbNullChar)
            lngCurrentDirLength = MAX_PATH
           
            blCheckGetDirSuccess = FtpGetCurrentDirectory(hConnection, strCurrentDirectory, lngCurrentDirLength)
           
            If blCheckGetDirSuccess Then
           
                strCurrentDirectory = Left(strCurrentDirectory, lngCurrentDirLength)
                Debug.Print "Current directory = " & strCurrentDirectory
 
Last edited:
Upvote 0

Forum statistics

Threads
1,224,822
Messages
6,181,165
Members
453,021
Latest member
Justyna P

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top