
Introduction
Welcome to yet another 'Simplicity Is Virtue' article. I think this one might actually be
useful.. :-)
I wanted to create a button somewhere on my small app that would check for a new version of the app online, and report it to the user. Also,
I wanted to keep things as simple as possible, 'coz I really hate complicated apps. The code works like this: connects to the FTP server, downloads a file containing the latest version information, compares it with the one you have now, and if there is a difference, there is a new version of your app.
Since it's a simple piece of code, you probably know all this, but if you're a
beginner (i.e. even bigger n00b than the author of this text), you might learn a couple of things:
- how to use
CInternetSession
, CFtpConnection
, and CFtpFileFind
- how to transfer a file over FTP using
CFtpConnection
- how to dynamically load a DLL from your exe and use its functions
Of course, this article displays only the most basic usage.
The Code
I've put the code in a DLL library, because this way I can take advantage of the same DLL from multiple apps, without recompiling everything. (Isn't this the reason why DLLs exist anyway..??:-)) First, here is the DLL code.
Noob note: If you've never created any DLLs, just go like this: File->New->Projects->MFC AppWizard (dll) and type in the name you want to use (I called mine "chkver", which would stand for "check version" or something..). Click OK, and on the next dialog just click Finish.
BOOL CheckForUpdate(
LPCTSTR szFtpServer,
LPCTSTR szFtpUsername,
LPCTSTR szFtpPassword,
LPCTSTR szFtpFilename,
LPCTSTR szCurrentVersion,
LPTSTR szLastVersion )
{
CWaitCursor wait;
ZeroMemory( szLastVersion, sizeof(szLastVersion) );
CInternetSession* pFtpSession = new CInternetSession();
CFtpConnection* pFtpConnection = NULL;
if ( pFtpSession == NULL )
{
MessageBox( GetDesktopWindow(),
_T("Could not get internet session."),
_T("Error"), MB_OK|MB_ICONSTOP );
return FALSE;
}
try {
pFtpConnection = pFtpSession->GetFtpConnection
( szFtpServer, szFtpUsername, szFtpPassword );
}
catch ( CInternetException *err )
{
err->ReportError( MB_OK|MB_ICONSTOP );
err->Delete();
}
if ( pFtpConnection == NULL )
{
pFtpSession->Close();
delete pFtpSession;
return FALSE;
}
CFtpFileFind ffind( pFtpConnection );
BOOL isFound = ffind.FindFile( szFtpFilename );
if ( isFound == FALSE )
{
ffind.Close();
pFtpConnection->Close();
pFtpSession->Close();
delete pFtpConnection;
delete pFtpSession;
MessageBox( GetDesktopWindow(),
_T("Could not get version information."),
_T("Error"), MB_OK|MB_ICONSTOP );
return FALSE;
}
BOOL bResult = pFtpConnection->GetFile
( szFtpFilename, LOCAL_FILENAME, FALSE );
if ( bResult == 0 )
{
ffind.Close();
pFtpConnection->Close();
pFtpSession->Close();
delete pFtpConnection;
delete pFtpSession;
MessageBox( GetDesktopWindow(),
_T("Could not get version information."),
_T("Error"), MB_OK|MB_ICONSTOP );
return FALSE;
}
CFile verFile;
CFileException error;
bResult = verFile.Open( LOCAL_FILENAME,
CFile::modeRead, &error );
if ( bResult == 0 )
{
ffind.Close();
pFtpConnection->Close();
pFtpSession->Close();
delete pFtpConnection;
delete pFtpSession;
MessageBox( GetDesktopWindow(),
_T("Error opening local file."),
_T("Error"), MB_OK|MB_ICONSTOP );
DeleteFile( LOCAL_FILENAME );
return FALSE;
}
verFile.SeekToBegin();
TCHAR buffer[MAX_STRING];
ZeroMemory( buffer, sizeof(buffer) );
verFile.Read( buffer, MAX_STRING );
if ( _tcscmp( buffer, szCurrentVersion ) != 0 )
{
_tcscpy( szLastVersion, buffer );
verFile.Close();
ffind.Close();
pFtpConnection->Close();
pFtpSession->Close();
delete pFtpConnection;
delete pFtpSession;
DeleteFile( LOCAL_FILENAME );
return TRUE;
}
_tcscpy( szLastVersion, szCurrentVersion );
verFile.Close();
ffind.Close();
pFtpConnection->Close();
pFtpSession->Close();
delete pFtpConnection;
delete pFtpSession;
DeleteFile( LOCAL_FILENAME );
return FALSE;
}
The code is pretty much self-explanatory, so if you have trouble with anything, consult MSDN and there should be no problem. In order for this code to work, you will have to #include <afxinet.h>
&& define two constants at the begginning of the file:
#include <afxinet.h>
#define LOCAL_FILENAME _T( "~tmpsz.dat" )
#define MAX_STRING 200
Needless to say, you can adjust both of these to your liking.
Wrap up
As you can see in the example, I've removed the rest of the DLL stuff (app definition etc.) because it just extra code, but you can leave it be if you wish or need to.
Before jumping to the usage code, you need to export the CheckForUpdate
function from your .def file:
; chkver.def : Declares the module parameters for the DLL.
LIBRARY "chkver"
DESCRIPTION 'chkver Windows Dynamic Link Library'
EXPORTS
; Explicit exports can go here
CheckForUpdate @1
Noob note: If you do not export your function from DLL, you can't use it.
Note that this DLL, when compiled in release version, takes only 8 kb. (Well, you do need to add the linker switch /opt:nowin98 )
Using the code
To use the code, I've created a simple generic class called CVersionInterface
with a single static
member function.
typedef BOOL (*DLL_CHECK_FOR_UPDATE)(LPCTSTR szFtpServer,
LPCTSTR szFtpUsername,
LPCTSTR szFtpPassword,
LPCTSTR szFtpFilename,
LPCTSTR szCurrentVersion,
LPTSTR szLastVersion );
class CVersionInterface
{
public:
CVersionInterface();
virtual ~CVersionInterface();
static BOOL CheckForUpdate(
LPCTSTR szFtpServer,
LPCTSTR szFtpUsername,
LPCTSTR szFtpPassword,
LPCTSTR szFtpFilename,
LPCTSTR szCurrentVersion,
LPTSTR szLastVersion );
};
and the implementation is just as simple:
BOOL CVersionInterface::CheckForUpdate(
LPCTSTR szFtpServer,
LPCTSTR szFtpUsername,
LPCTSTR szFtpPassword,
LPCTSTR szFtpFilename,
LPCTSTR szCurrentVersion,
LPTSTR szLastVersion )
{
BOOL bRetVal = FALSE;
HMODULE hDll = LoadLibrary( "chkver.dll" );
if ( hDll )
{
DLL_CHECK_FOR_UPDATE hFunc = (DLL_CHECK_FOR_UPDATE)
GetProcAddress( hDll, "CheckForUpdate" );
if ( hFunc )
{
bRetVal = hFunc( szFtpServer, szFtpUsername,
szFtpPassword, szFtpFilename,
szCurrentVersion, szLastVersion );
}
FreeLibrary( hDll );
}
else
{
CString text;
text = _T("Module chkver.dll not found.\n");
text += _T("(What the heck did you do with it?)\n");
text += _T("Cannot check for updates.");
text += _T("\n\nP.S. It's all your fault.");
MessageBox( GetDesktopWindow(), text,
_T("Error"), MB_OK|MB_ICONEXCLAMATION );
}
return bRetVal;
}
Of course, you can adjust the class not to use a static member, and to load the DLL in the constructor and keep it loaded during the time your app is running etc. Your call.
Points of Interest & Issues
Well, you don't have to be a guru or voodoo expert to check for an update of your app.. Maybe things could be done in a way so there is no file transfer etc. but I'm kewl with this way. There are probably some bugs so don't be afraid to reveal them. (or let your dumb end user discover it, it's more fun that way!:-))
There is only one slight issue: since detection of a new version relies on string difference, there might be trouble. For example, if your app is in version 1.4 and you forget to update the file and it remains 1.3, update check will report there is a _newer_ version. Also, the strings you use must be completely identical. Even a single newline ('\n'
) char at the end of the string makes the difference, so be carefull.
Copyleftright
As always, everything Copyleft © by T1TAN && SprdSoft Inc. 2004 - 3827, but free for any kind of use, as long as you don't claim the code to be
yours.
I also appreciate e-mails, comments here on CP etc.
(And now a bit of advertising:-)) If you have a couple of minutes, visit my pseudo-company site http://sprdsoft.cmar-net.org and have a good laugh. (people with weird sense of humor will appreciate it even more:-)) You can check out my little proggy called Kewl Tray Tool which will soon have this kind of 'update checking' implemented (as you read this, it might already be there).
Thank you for reading this crap... (-:
And above all, don't play with matches! (-:
History
- 15/05/2004 - initial release