最近看到 CFtpFileFind GetFileURL 不能正确返回路径
在VS2008下测试了一下确实有BUG, 多字节版本下是正常的, 而UNICODE版本下没有后面的文件名
大概调试追踪了一下, 先分析GetFileURL
CString CFtpFileFind::GetFileURL() const
{
ASSERT_VALID(this);
ASSERT(m_hContext != NULL);
CString str;
if (m_hContext != NULL)
{
str += _afxURLftp;
str += m_pConnection->GetServerName();
str += GetFilePath();
}
return str;
}
再追踪到 GetFilePath
CString CFileFind::GetFilePath() const
{
ASSERT(m_hContext != NULL);
ASSERT_VALID(this);
CString strResult = m_strRoot;
LPCTSTR pszResult;
LPCTSTR pchLast;
pszResult = strResult;
pchLast = _tcsdec( pszResult, pszResult+strResult.GetLength() );
ENSURE(pchLast!=NULL);
if ((*pchLast != _T('\\')) && (*pchLast != _T('/')))
strResult += m_chDirSeparator;
strResult += GetFileName(); //在这里发现GetFileName返回正常,但是+=后没有变化
return strResult;
}
继续单步进入 += 操作时发现, this(strResult 字符串) 只有一个字符 '/', 但是长度却是2, 也就是后面一个字符是‘\0’, 这个字符导致追加的字符串都无法显示。看来问题是在前面的 m_strRoot 上。
在 inet.cpp 的 CFtpFileFind::FindFile 中可以找到 m_strRoot 赋值的位置,设置断点, 也确实是停在 m_strRoot = strCWD;
BOOL CFtpFileFind::FindFile(LPCTSTR pstrName /* = NULL */,
DWORD dwFlags /* = INTERNET_FLAG_RELOAD */)
{
……
CString strCWD;
m_pConnection->GetCurrentDirectory(strCWD);
if (pstrRoot == NULL)
{
if (m_pConnection->SetCurrentDirectory(pstrName))
{
……
}
else
m_strRoot = strCWD;
}
……
}
问题看来在m_pConnection->GetCurrentDirectory,再次设置断点进入 CFtpConnection::GetCurrentDirectory,
BOOL CFtpConnection::GetCurrentDirectory(CString& strDirName) const
{
ASSERT_VALID(this);
ASSERT(m_hConnection != NULL);
DWORD dwLen = INTERNET_MAX_PATH_LENGTH;
LPTSTR pstrTarget = strDirName.GetBufferSetLength(dwLen);
BOOL bRet =FtpGetCurrentDirectory(m_hConnection, pstrTarget, &dwLen);
if (bRet)
strDirName.ReleaseBuffer(dwLen);
else
strDirName.ReleaseBuffer(0);
return bRet;
}
在FtpGetCurrentDirectory上设置断点, 发现返回的字符只有‘/’, 多字节版本dwLen返回1,而Unicode版本返回2,
关键就在这个2上, 导致字符串后面有一个0, 使得后面的字符再进行 += 的字符串被截断; 而 CFtpFileFind::FindFile 中的m_strRoot 就用了这个返回值做根路径,那么后面的追加操作自然会失败。
找到问题了,那想办法把m_strRoot弄正常就好了呗, 看 CFtpConnection 的函数申明, GetCurrentDirectory 都不是虚函数,再看CFtpFileFind::FindFile 的声明 virtual BOOL FindFile(LPCTSTR pstrName = NULL, DWORD dwFlags = INTERNET_FLAG_RELOAD); 可以从这里做突破口了, 我这里是从CFtpFileFind派生了一个新类, 然后FindFile中处理了一下
好了废话不多说了, 直接上好用的代码了
#include <afxinet.h>
class CMyFtpFileFind : public CFtpFileFind
{
public:
explicit CMyFtpFileFind(CFtpConnection* pConnection, DWORD_PTR dwContext = 1)
: CFtpFileFind(pConnection, dwContext)
{
}
virtual BOOL FindFile(LPCTSTR pstrName = NULL,
DWORD dwFlags = INTERNET_FLAG_RELOAD)
{
BOOL bRet = __super::FindFile(pstrName, dwFlags);
m_strRoot.ReleaseBuffer(); //
return bRet;
}
};
void CDlg4Dlg::OnBnClickedButton1()
{
#undef _tprintf_s
#define _tprintf_s TRACE
CInternetSession sess(_T("My FTP Session"));
CFtpConnection* pConnect = NULL;
try
{
// Request a connection to ftp.microsoft.com. Default
// parameters mean that we'll try with username = ANONYMOUS
// and password set to the machine name @ domain name
pConnect = sess.GetFtpConnection(_T("192.168.0.186"));
// use a file find object to enumerate files
CMyFtpFileFind finder(pConnect);
// start looping
BOOL bWorking = finder.FindFile(_T("*"));
while (bWorking)
{
bWorking = finder.FindNextFile();
_tprintf_s(_T("%s\n"), (LPCTSTR)finder.GetFileURL());
}
}
catch (CInternetException* pEx)
{
TCHAR sz[1024];
pEx->GetErrorMessage(sz, 1024);
_tprintf_s(_T("ERROR! %s\n"), sz);
pEx->Delete();
}
// if the connection is open, close it
if (pConnect != NULL)
{
pConnect->Close();
delete pConnect;
}
}