Click here to Skip to main content
15,885,985 members
Articles / Desktop Programming / MFC
Article

S.I.V. : Simple program version checking

Rate me:
Please Sign up or sign in to vote.
4.09/5 (2 votes)
19 May 20044 min read 60K   1.1K   27   15
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, 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.

//********************************************************
//*  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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Croatia Croatia
A software developer for the masses..

Comments and Discussions

 
GeneralPretty dangerous code Pin
Bert [Otherside82] Derijckere27-May-04 21:14
Bert [Otherside82] Derijckere27-May-04 21:14 
you use GetDesktopWindow() as a parameter to MessageBox. You really should not do this.

Quote from http://blogs.msdn.com/oldnewthing/archive/2004/02/24/79212.aspx

If UI does indeed need to be displayed, you hang the system.

Why?

* A modal dialog disables its owner.
* Every window is a descendant of the desktop.
* When a window is disabled, all its descendants are also disabled.

Put this together: If the owner of a modal dialog is the desktop, then the desktop becomes disabled, which disables all of its descendants. In other words, it disables every window in the system. Even the one you're trying to display!

//end quote
you should really check out the whole page, and the rest of the articles, very interesting stuff
GeneralRe: Pretty dangerous code Pin
T1TAN29-May-04 9:23
T1TAN29-May-04 9:23 
Generalproxy authentication Pin
darthmaul25-May-04 22:52
darthmaul25-May-04 22:52 
GeneralRe: proxy authentication Pin
T1TAN26-May-04 3:24
T1TAN26-May-04 3:24 
QuestionWhy DLL Exist ? Pin
Blake Miller25-May-04 12:46
Blake Miller25-May-04 12:46 
AnswerRe: Why DLL Exist ? Pin
T1TAN26-May-04 3:26
T1TAN26-May-04 3:26 
QuestionHTTP instead? Pin
Ravi Bhavnani20-May-04 9:23
professionalRavi Bhavnani20-May-04 9:23 
AnswerRe: HTTP instead? Pin
Steve Mayfield20-May-04 11:36
Steve Mayfield20-May-04 11:36 
GeneralRe: HTTP instead? Pin
Ravi Bhavnani20-May-04 11:45
professionalRavi Bhavnani20-May-04 11:45 
GeneralRe: HTTP instead? Pin
T1TAN21-May-04 3:49
T1TAN21-May-04 3:49 
GeneralRe: HTTP instead? Pin
Ravi Bhavnani21-May-04 3:57
professionalRavi Bhavnani21-May-04 3:57 
GeneralRe: HTTP instead? Pin
T1TAN24-May-04 23:51
T1TAN24-May-04 23:51 
GeneralRe: HTTP instead? Pin
Ravi Bhavnani25-May-04 1:20
professionalRavi Bhavnani25-May-04 1:20 
GeneralRe: HTTP instead? Pin
Rome Singh13-Oct-04 17:01
Rome Singh13-Oct-04 17:01 
GeneralRe: HTTP instead? Pin
Ravi Bhavnani14-Oct-04 1:44
professionalRavi Bhavnani14-Oct-04 1:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.