S.I.V. : Simple program version checking






4.09/5 (2 votes)
May 20, 2004
4 min read

60742

1101
Simplicity Is Virtue: How to check for a new version of your app in a simple manner (over the internet).
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
, andCFtpFileFind
- how to transfer a file over FTP using
CFtpConnection
- how to dynamically load a DLL from your exe and use its functions
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.
//******************************************************** //* FUNCTION: CheckForUpdate //* //* DESCRIPTION: //* Connects to the specified Ftp server, and looks //* for the specified file (szFtpFilename) and reads //* just one string from it. Compares the string with //* the szCurrentVersion and if there is a difference, //* assumes there is a new app version. //* //* PARAMS: //* szFtpServer: FTP server to access //* szFtpUsername: FTP account name //* szFtpPassword: appropriate password //* szFtpFilename: FTP file which holds the //* version info //* szCurrentVersion: version of the app calling //* this function //* szLastVersion: version retrieved from FTP //* valid only if no error occurs //* //* ASSUMES: //* Existance of a valid internet connection. //* AfxSocketInit() has already been called. //* Brains (optional). //* //* RETURNS: //* TRUE only if new version is found //* FALSE if there was an error OR no new version //* //* AUTHOR: T1TAN <t1tan@cmar-net.org> //* //* COPYRIGHT: Copyleft (C) T1TAN 2004 - 3827 //* Copyleft (C) SprdSoft Inc. 2004 - 3827 //* FREE for (ab)use in any form. //* (Leave the headers be) //* //* VERSIONS: //* VERSION AUTHOR DATE NOTES //* ------- ------ ---------- ------------------ //* 1.0 T1TAN 07/05/2004 initial version //******************************************************** BOOL CheckForUpdate( LPCTSTR szFtpServer, LPCTSTR szFtpUsername, LPCTSTR szFtpPassword, LPCTSTR szFtpFilename, LPCTSTR szCurrentVersion, LPTSTR szLastVersion ) { CWaitCursor wait; // zero the last anyway.. ZeroMemory( szLastVersion, sizeof(szLastVersion) ); // get a session CInternetSession* pFtpSession = new CInternetSession(); CFtpConnection* pFtpConnection = NULL; if ( pFtpSession == NULL ) { // DAMN! 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 ) { // no luck today... err->ReportError( MB_OK|MB_ICONSTOP ); err->Delete(); } if ( pFtpConnection == NULL ) { // DAMN AGAIN!! // cleanup pFtpSession->Close(); delete pFtpSession; // you COULD report an error here, but the // try-catch block above does it anyway so // try not to piss off your user with // 3827 message boxes.. // MessageBox( GetDesktopWindow(), // _T("Could not connect to the server."), // _T("Error"), MB_OK|MB_ICONSTOP ); return FALSE; } CFtpFileFind ffind( pFtpConnection ); BOOL isFound = ffind.FindFile( szFtpFilename ); if ( isFound == FALSE ) { // CRAP!! WHERE IS OUR FILE?!?! 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 ) { // DAMN ERRORS 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 ) { // WHATTA HECK?!? ffind.Close(); pFtpConnection->Close(); pFtpSession->Close(); delete pFtpConnection; delete pFtpSession; MessageBox( GetDesktopWindow(), _T("Error opening local file."), _T("Error"), MB_OK|MB_ICONSTOP ); // just in case... 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 ) { // new version available! _tcscpy( szLastVersion, buffer ); // cleanup.. // (i am sometimes impressed with comments // like this one.. "cleanup.." OH REALLY, // and I thought it's an airplane!!) verFile.Close(); ffind.Close(); pFtpConnection->Close(); pFtpSession->Close(); delete pFtpConnection; delete pFtpSession; DeleteFile( LOCAL_FILENAME ); // ok.. return TRUE; } // obviously nothing new here... // copy the current version to last version // so that the caller knows no error occured _tcscpy( szLastVersion, szCurrentVersion ); // cleanup.. (again...) 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 // should be enough for everone (-: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.
//********************* //* VersionInterface.h // typedef the dll function pointer 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; // load our dll HMODULE hDll = LoadLibrary( "chkver.dll" ); if ( hDll ) { // get the function address DLL_CHECK_FOR_UPDATE hFunc = (DLL_CHECK_FOR_UPDATE) GetProcAddress( hDll, "CheckForUpdate" ); if ( hFunc ) { // call the actual function bRetVal = hFunc( szFtpServer, szFtpUsername, szFtpPassword, szFtpFilename, szCurrentVersion, szLastVersion ); } // unload dll FreeLibrary( hDll ); } else { // dll could not be loaded.. // someone is messing with us! (-: 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