|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis article describes the usage of the
BodyRecently, I had a need to develop an FTP client as a sub-function of a large C# Windows Form application. I quickly discovered that FTP support was not part of the FCL. I decided that the simplest way to address my needs was to develop a wrapper around the I started out by just creating a purely unmanaged C++ .dll and just exporting a couple of functions, and then using PInvoke to call the functions from C#. I found this to be both inelegant and largely infeasible when it came to enumerating directory contents and returning these contents to a C# application. The solution I finally settled on was to create a managed C++ assembly that would interact with Sending / Receiving filesSending and receiving files is supported right out of the box by bSent = FTP_Wrapper.SendFTPFile(Mode, Convert.ToInt32(szFTPServerPort),
szFTPServerName, szFTPUserName,
szFTPUserPassword, szFTPFile,
szFTPDestFile,
ref szErrorMsg);
if (!bSent)
Console.WriteLine(szErrorMsg);
Enumerating Directory ContentsGetting the contents of an FTP directory is pretty straightforward using Enter the namespace FTP_Wrap
{
public __gc class DirectoryContents;
public __gc class DirectoryItem
{
public:
System::String* m_Name;
System::String* m_PathName;
int m_Type; // 0 = file, 1 = subdirectory;
long m_Size; // file size in bytes
bool Equals(System::Object* pObj);
};
public __gc class DirectoryContents
{
public:
DirectoryContents();
virtual ~DirectoryContents() {};
bool LoadList(int SourceType, System::String* pServerNm,
int ServerPort, System::String* pUserNm,
System::String* pPwd, System::String* pRemoteDir,
System::String* pMask, System::String*& pErrorMsg);
System::Collections::ArrayList* m_DirectoryList;
};
}
Using the static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
//Do the FTP dir command and recurse all sub folders:
ShowDirContents("/");
}
static void ShowDirContents(String Dir)
{
FTP_Wrap.DirectoryContents dc = new DirectoryContents();
String ErrorMsg = "";
bool Loaded = dc.LoadList("localhost", 21, "USERID", "PASSWORD",
Dir, "*", ref ErrorMsg);
if (!Loaded)
{
Console.WriteLine(ErrorMsg);
return;
}
foreach (DirectoryItem item in dc.m_DirectoryList)
{
if (item.m_Type == 0) //files
{
Console.WriteLine("File: " + item.m_PathName + " Size: " + item.m_Size);
}
else //sub directories
ShowDirContents(item.m_PathName);
}
}
CFTPConnection::CommandOne additional challenge I faced in implementing my client functionality was that one of the FTP machine types that would typically be interfaced with did not support truly standard FTP. As a result, using After digging around a little more, I noticed that there is a set of 'server' commands that can be sent to an FTP server using the In order to get the directory contents using a server command, I issued a NLST command to the server and parsed the response. Here's what happens at the TCP/IP level: Send NLST through port 21 to the server:
Get response to NLST from server to my local port 20:
Code from directorycontents.cpp: // Issue the command to the server:
CString dir(pRemoteDir);
InternetFile* pFile = NULL;
System::String* pRes = NULL;
BOOL bChg = pFtpConn->SetCurrentDirectory(dir);
CString strDir;
pFtpConn->GetCurrentDirectory(strDir);
File = pFtpConn->Command(_T("NLST"), CFtpConnection::CmdRespRead,
FTP_TRANSFER_TYPE_BINARY);
//Process the response (the file list):
ULONGLONG a = pFile->GetLength();
char readBuf[256];
unsigned int rd = 0;
System::Text::StringBuilder* pSb = new System::Text::StringBuilder();
do
{
rd = pFile->Read(readBuf, 256);
if (rd > 0)
{
System::String* pStr = new System::String(readBuf);
pSb->Append(pStr, 0, rd);
}
} while (rd > 0);
pRes = pSb->ToString();
// ok, parse the response:
System::String* pDelim = "\r\n";
System::Text::RegularExpressions::Regex* pRegex =
new System::Text::RegularExpressions::Regex(pDelim);
System::String* parts[] = pRegex->Split(pRes);
for (int n = 0; n < parts->Length; n++)
{
String* pItemNm = parts[n];
if (pItemNm->Length == 0)
continue;
DirectoryItem* pItem = new DirectoryItem;
if (pItemNm->EndsWith("/"))
{
pItem->m_Type = 1; // a directory
String* pTemp = pItemNm->Substring(0, pItemNm->Length - 1);
int nNameBegin = pTemp->LastIndexOf("/");
pItem->m_Name = pTemp->Substring(nNameBegin + 1);
System::Text::StringBuilder* pSb = new System::Text::StringBuilder();
pSb->Append(pRemoteDir);
pSb->Append(pItem->m_Name);
pSb->Append("/");
pItem->m_PathName = pSb->ToString();
}
else
{
pItem->m_Type = 0; // a file
int nNameBegin = pItemNm->LastIndexOf("/");
pItem->m_Name = pItemNm->Substring(nNameBegin + 1);
System::Text::StringBuilder* pSb = new System::Text::StringBuilder();
pSb->Append(pRemoteDir);
pSb->Append(pItem->m_Name);
pItem->m_PathName = pSb->ToString();
}
pItem->m_Size = 0;
m_DirectoryList->Add(pItem);
}
The big thing to note is that the Command method returns a The sample applicationI've included a very simple stripped down sample application to show the functionality of the classes. In order to play with the The sample app will save your FTP settings (port, server name, most recent file names, etc.) to the current user registry under the software key. ConclusionThese classes solved what I was trying to do. I hope they can help someone here. ReferencesI used numerous resources for figuring out how to use the
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||